-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* UniswapV3Oracle added * fix tests * Fixed tests * Update UniswapV3Oracle.test.ts * coverage 100% for oracle v3 * removed UniswapV3PoolDeployer mock * renaming * codestyle mock optimization * minor fixes oracle lib moved to mocks * Update UniswapV3Oracle.test.ts * period check moved * review fixes * Update UniswapV3Oracle.sol * small logic fixes * fixed decimals logic no need to deploy tokens in tests more precise tests * changed ito uint128 price fixes in findOldestObservation added unchecked to tickHelper * review fixes _findOldestObservation -> getOldestObservationSecondsAgo in TickHelper private getPrice returns uint128 now * moved uni libraries to vendor folder * fixes test became more readable * imoroved tests vendor to original codestyle * plus test case for tick logic * test optimization reason strings in vendor returned to original * versions test optimization * test update * Update UniswapV3Oracle.test.ts * Update UniswapV3Oracle.test.ts * Update UniswapV3Oracle.test.ts * added bignumber.js to dependencies * dependencies update * oracleV3 with compiler version <0.8.0 uniswap libs are used through imports now removed vendor folder updated decription * Update UniswapV3Oracle.sol * package fixes --------- Co-authored-by: Artem Chystiakov <[email protected]>
- Loading branch information
1 parent
ddaf3b0
commit 07cb309
Showing
34 changed files
with
5,505 additions
and
12,307 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,2 @@ | ||
# keys | ||
COINMARKETCAP_KEY=COINMARKETCAP API KEY | ||
|
||
# By default 'ethers-v6' | ||
TYPECHAIN_TARGET=TYPECHAIN TARGET | ||
|
||
# Set 'false' to not automatically generate types | ||
TYPECHAIN_FORCE=TYPECHAIN FORCE |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
{ | ||
"plugins": ["prettier-plugin-solidity"], | ||
"overrides": [ | ||
{ | ||
"files": "*.sol", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
contracts/mock/oracles/uniswap-v3/UniswapV3FactoryMock.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.5.0 <0.8.0; | ||
|
||
import {UniswapV3PoolMock} from "./UniswapV3PoolMock.sol"; | ||
|
||
contract UniswapV3FactoryMock { | ||
mapping(address => mapping(address => mapping(uint24 => address))) public getPool; | ||
|
||
function createPool( | ||
address tokenA_, | ||
address tokenB_, | ||
uint24 fee_ | ||
) external returns (address pool_) { | ||
(address token0_, address token1_) = tokenA_ < tokenB_ | ||
? (tokenA_, tokenB_) | ||
: (tokenB_, tokenA_); | ||
|
||
pool_ = _deploy(token0_, token1_, fee_); | ||
|
||
getPool[token0_][token1_][fee_] = pool_; | ||
getPool[token1_][token0_][fee_] = pool_; | ||
} | ||
|
||
function _deploy( | ||
address token0_, | ||
address token1_, | ||
uint24 fee_ | ||
) internal returns (address pool_) { | ||
pool_ = address( | ||
new UniswapV3PoolMock{salt: keccak256(abi.encode(token0_, token1_, fee_))}() | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.5.0 <0.8.0; | ||
|
||
import {Oracle} from "@uniswap/v3-core/contracts/libraries/Oracle.sol"; | ||
import {TickMath} from "@uniswap/v3-core/contracts/libraries/TickMath.sol"; | ||
|
||
contract UniswapV3PoolMock { | ||
using Oracle for Oracle.Observation[65535]; | ||
|
||
struct Slot0 { | ||
uint160 sqrtPriceX96; | ||
int24 tick; | ||
uint16 observationIndex; | ||
uint16 observationCardinality; | ||
uint16 observationCardinalityNext; | ||
uint8 feeProtocol; | ||
bool unlocked; | ||
} | ||
|
||
Slot0 public slot0; | ||
Oracle.Observation[65535] public observations; | ||
|
||
function initialize(uint160 sqrtPriceX96_) external { | ||
int24 tick_ = TickMath.getTickAtSqrtRatio(sqrtPriceX96_); | ||
|
||
(uint16 cardinality_, uint16 cardinalityNext_) = observations.initialize( | ||
_blockTimestamp() | ||
); | ||
|
||
slot0 = Slot0({ | ||
sqrtPriceX96: sqrtPriceX96_, | ||
tick: tick_, | ||
observationIndex: 0, | ||
observationCardinality: cardinality_, | ||
observationCardinalityNext: cardinalityNext_, | ||
feeProtocol: 0, | ||
unlocked: true | ||
}); | ||
} | ||
|
||
function addObservation(int24 tick_) external { | ||
(slot0.observationIndex, slot0.observationCardinality) = observations.write( | ||
slot0.observationIndex, | ||
_blockTimestamp(), | ||
slot0.tick, | ||
0, | ||
slot0.observationCardinality, | ||
slot0.observationCardinalityNext | ||
); | ||
|
||
slot0.tick = tick_; | ||
} | ||
|
||
function increaseObservationCardinalityNext(uint16 observationCardinalityNext_) external { | ||
slot0.observationCardinalityNext = observations.grow( | ||
slot0.observationCardinalityNext, | ||
observationCardinalityNext_ | ||
); | ||
} | ||
|
||
function observe( | ||
uint32[] calldata secondAgos_ | ||
) | ||
external | ||
view | ||
returns ( | ||
int56[] memory tickCumulatives_, | ||
uint160[] memory secondsPerLiquidityCumulativeX128s_ | ||
) | ||
{ | ||
return | ||
observations.observe( | ||
_blockTimestamp(), | ||
secondAgos_, | ||
slot0.tick, | ||
slot0.observationIndex, | ||
0, | ||
slot0.observationCardinality | ||
); | ||
} | ||
|
||
function _blockTimestamp() internal view virtual returns (uint32) { | ||
return uint32(block.timestamp); // truncation is desired in original contract | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.5.0 <0.8.0; | ||
|
||
import {IUniswapV3Factory} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; | ||
import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; | ||
import {FullMath} from "@uniswap/v3-core/contracts/libraries/FullMath.sol"; | ||
import {OracleLibrary} from "@uniswap/v3-periphery/contracts/libraries/OracleLibrary.sol"; | ||
|
||
/** | ||
* @notice UniswapV3Oracle module | ||
* | ||
* A contract for retrieving prices from Uniswap V3 pools. | ||
* | ||
* Works by calculating the time-weighted average tick as difference between two tickCumulatives | ||
* divided by number of second between them, tickCumulatives are taken from the newest observation | ||
* and from the one nearest to required time. | ||
* | ||
* Price is obtained as 1.0001 in power of this tick. | ||
* | ||
* In case required period of time is unreachable, tick is taken from oldest available observation. | ||
*/ | ||
contract UniswapV3Oracle { | ||
using FullMath for *; | ||
|
||
IUniswapV3Factory public immutable uniswapV3Factory; | ||
|
||
/** | ||
* @dev contract is not an Initializable because the compiler version is <0.8.0 | ||
*/ | ||
constructor(address uniswapV3Factory_) { | ||
uniswapV3Factory = IUniswapV3Factory(uniswapV3Factory_); | ||
} | ||
|
||
/** | ||
* @notice The function to retrieve the price of a token following the configured route | ||
* @dev The function returns price in quote token decimals. If amount is zero, returns (0, 0) | ||
* @param path_ the path of token address, the last one is token in which price will be returned | ||
* @param fees_ the array of fees for particular pools | ||
* @param amount_ the amount of baseToken_ | ||
* @param period_ the time period | ||
* @return amount_ the price of start token in quote token | ||
* @return minPeriod_ the oldest period for which there is an observation in case period_ time ago there was no observation | ||
*/ | ||
function getPriceOfTokenInToken( | ||
address[] memory path_, | ||
uint24[] memory fees_, | ||
uint128 amount_, | ||
uint32 period_ | ||
) public view returns (uint128, uint32) { | ||
uint256 pathLength_ = path_.length; | ||
|
||
require(pathLength_ > 1, "UniswapV3Oracle: invalid path"); | ||
require(pathLength_ == fees_.length + 1, "UniswapV3Oracle: path/fee lengths do not match"); | ||
require( | ||
block.timestamp > period_, | ||
"UniswapV3Oracle: period larger than current timestamp" | ||
); | ||
require(period_ > 0, "UniswapV3Oracle: period can't be 0"); | ||
|
||
if (amount_ == 0) { | ||
return (0, 0); | ||
} | ||
|
||
uint32 minPeriod_ = period_; | ||
|
||
for (uint256 i = 0; i < pathLength_ - 1; i++) { | ||
uint32 currentPeriod_; | ||
(amount_, currentPeriod_) = _getPriceOfTokenInToken( | ||
path_[i], | ||
path_[i + 1], | ||
amount_, | ||
fees_[i], | ||
period_ | ||
); | ||
|
||
minPeriod_ = uint32(minPeriod_ < currentPeriod_ ? minPeriod_ : currentPeriod_); | ||
} | ||
|
||
return (amount_, minPeriod_); | ||
} | ||
|
||
/** | ||
* @notice The private function to get the price of a token inside a pool | ||
* @dev Price is expected to fit into uint128 | ||
*/ | ||
function _getPriceOfTokenInToken( | ||
address baseToken_, | ||
address quoteToken_, | ||
uint128 amount_, | ||
uint24 fee_, | ||
uint32 period_ | ||
) private view returns (uint128, uint32) { | ||
if (baseToken_ == quoteToken_) { | ||
return (amount_, period_); | ||
} | ||
|
||
address pool_ = uniswapV3Factory.getPool(baseToken_, quoteToken_, fee_); | ||
|
||
require(pool_ != address(0), "UniswapV3Oracle: such pool doesn't exist"); | ||
|
||
uint32 longestPeriod_ = OracleLibrary.getOldestObservationSecondsAgo(pool_); | ||
|
||
require( | ||
longestPeriod_ != 0, | ||
"UniswapV3Oracle: the oldest observation is on the current block" | ||
); | ||
|
||
period_ = uint32(period_ < longestPeriod_ ? period_ : longestPeriod_); | ||
|
||
(int24 arithmeticMeanTick, ) = OracleLibrary.consult(pool_, period_); | ||
|
||
return ( | ||
uint128( | ||
OracleLibrary.getQuoteAtTick(arithmeticMeanTick, amount_, baseToken_, quoteToken_) | ||
), | ||
period_ | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.