Skip to content

Commit 07cb309

Browse files
aritkulovaArvolear
andauthored
Feat/uniswap v3 oracle (#73)
* 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]>
1 parent ddaf3b0 commit 07cb309

34 files changed

+5505
-12307
lines changed

.env.example

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,2 @@
11
# keys
22
COINMARKETCAP_KEY=COINMARKETCAP API KEY
3-
4-
# By default 'ethers-v6'
5-
TYPECHAIN_TARGET=TYPECHAIN TARGET
6-
7-
# Set 'false' to not automatically generate types
8-
TYPECHAIN_FORCE=TYPECHAIN FORCE

.prettierrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"plugins": ["prettier-plugin-solidity"],
23
"overrides": [
34
{
45
"files": "*.sol",

.solhint.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
"extends": "solhint:recommended",
33
"plugins": ["prettier"],
44
"rules": {
5+
"prettier/prettier": "warn",
56
"reentrancy": "off",
6-
"prettier/prettier": "off",
77
"modifier-name-mixedcase": "off",
88
"no-empty-blocks": "off",
99
"func-visibility": "off",

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The library consists of modules and utilities that are built with a help of [Ope
1717
- Optimized [**Incremental Merkle Tree**](https://github.com/runtimeverification/deposit-contract-verification/blob/master/deposit-contract-verification.pdf) data structure
1818
- Novel **ReturnDataProxy** contract
1919
- Lightweight **SBT** implementation
20-
- Flexible UniswapV2 oracle
20+
- Flexible UniswapV2 and UniswapV3 oracles
2121
- Utilities to ease work with ERC20 decimals, arrays, sets and ZK proofs
2222

2323
## Overview
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity >=0.5.0 <0.8.0;
3+
4+
import {UniswapV3PoolMock} from "./UniswapV3PoolMock.sol";
5+
6+
contract UniswapV3FactoryMock {
7+
mapping(address => mapping(address => mapping(uint24 => address))) public getPool;
8+
9+
function createPool(
10+
address tokenA_,
11+
address tokenB_,
12+
uint24 fee_
13+
) external returns (address pool_) {
14+
(address token0_, address token1_) = tokenA_ < tokenB_
15+
? (tokenA_, tokenB_)
16+
: (tokenB_, tokenA_);
17+
18+
pool_ = _deploy(token0_, token1_, fee_);
19+
20+
getPool[token0_][token1_][fee_] = pool_;
21+
getPool[token1_][token0_][fee_] = pool_;
22+
}
23+
24+
function _deploy(
25+
address token0_,
26+
address token1_,
27+
uint24 fee_
28+
) internal returns (address pool_) {
29+
pool_ = address(
30+
new UniswapV3PoolMock{salt: keccak256(abi.encode(token0_, token1_, fee_))}()
31+
);
32+
}
33+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity >=0.5.0 <0.8.0;
3+
4+
import {Oracle} from "@uniswap/v3-core/contracts/libraries/Oracle.sol";
5+
import {TickMath} from "@uniswap/v3-core/contracts/libraries/TickMath.sol";
6+
7+
contract UniswapV3PoolMock {
8+
using Oracle for Oracle.Observation[65535];
9+
10+
struct Slot0 {
11+
uint160 sqrtPriceX96;
12+
int24 tick;
13+
uint16 observationIndex;
14+
uint16 observationCardinality;
15+
uint16 observationCardinalityNext;
16+
uint8 feeProtocol;
17+
bool unlocked;
18+
}
19+
20+
Slot0 public slot0;
21+
Oracle.Observation[65535] public observations;
22+
23+
function initialize(uint160 sqrtPriceX96_) external {
24+
int24 tick_ = TickMath.getTickAtSqrtRatio(sqrtPriceX96_);
25+
26+
(uint16 cardinality_, uint16 cardinalityNext_) = observations.initialize(
27+
_blockTimestamp()
28+
);
29+
30+
slot0 = Slot0({
31+
sqrtPriceX96: sqrtPriceX96_,
32+
tick: tick_,
33+
observationIndex: 0,
34+
observationCardinality: cardinality_,
35+
observationCardinalityNext: cardinalityNext_,
36+
feeProtocol: 0,
37+
unlocked: true
38+
});
39+
}
40+
41+
function addObservation(int24 tick_) external {
42+
(slot0.observationIndex, slot0.observationCardinality) = observations.write(
43+
slot0.observationIndex,
44+
_blockTimestamp(),
45+
slot0.tick,
46+
0,
47+
slot0.observationCardinality,
48+
slot0.observationCardinalityNext
49+
);
50+
51+
slot0.tick = tick_;
52+
}
53+
54+
function increaseObservationCardinalityNext(uint16 observationCardinalityNext_) external {
55+
slot0.observationCardinalityNext = observations.grow(
56+
slot0.observationCardinalityNext,
57+
observationCardinalityNext_
58+
);
59+
}
60+
61+
function observe(
62+
uint32[] calldata secondAgos_
63+
)
64+
external
65+
view
66+
returns (
67+
int56[] memory tickCumulatives_,
68+
uint160[] memory secondsPerLiquidityCumulativeX128s_
69+
)
70+
{
71+
return
72+
observations.observe(
73+
_blockTimestamp(),
74+
secondAgos_,
75+
slot0.tick,
76+
slot0.observationIndex,
77+
0,
78+
slot0.observationCardinality
79+
);
80+
}
81+
82+
function _blockTimestamp() internal view virtual returns (uint32) {
83+
return uint32(block.timestamp); // truncation is desired in original contract
84+
}
85+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity >=0.5.0 <0.8.0;
3+
4+
import {IUniswapV3Factory} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
5+
import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
6+
import {FullMath} from "@uniswap/v3-core/contracts/libraries/FullMath.sol";
7+
import {OracleLibrary} from "@uniswap/v3-periphery/contracts/libraries/OracleLibrary.sol";
8+
9+
/**
10+
* @notice UniswapV3Oracle module
11+
*
12+
* A contract for retrieving prices from Uniswap V3 pools.
13+
*
14+
* Works by calculating the time-weighted average tick as difference between two tickCumulatives
15+
* divided by number of second between them, tickCumulatives are taken from the newest observation
16+
* and from the one nearest to required time.
17+
*
18+
* Price is obtained as 1.0001 in power of this tick.
19+
*
20+
* In case required period of time is unreachable, tick is taken from oldest available observation.
21+
*/
22+
contract UniswapV3Oracle {
23+
using FullMath for *;
24+
25+
IUniswapV3Factory public immutable uniswapV3Factory;
26+
27+
/**
28+
* @dev contract is not an Initializable because the compiler version is <0.8.0
29+
*/
30+
constructor(address uniswapV3Factory_) {
31+
uniswapV3Factory = IUniswapV3Factory(uniswapV3Factory_);
32+
}
33+
34+
/**
35+
* @notice The function to retrieve the price of a token following the configured route
36+
* @dev The function returns price in quote token decimals. If amount is zero, returns (0, 0)
37+
* @param path_ the path of token address, the last one is token in which price will be returned
38+
* @param fees_ the array of fees for particular pools
39+
* @param amount_ the amount of baseToken_
40+
* @param period_ the time period
41+
* @return amount_ the price of start token in quote token
42+
* @return minPeriod_ the oldest period for which there is an observation in case period_ time ago there was no observation
43+
*/
44+
function getPriceOfTokenInToken(
45+
address[] memory path_,
46+
uint24[] memory fees_,
47+
uint128 amount_,
48+
uint32 period_
49+
) public view returns (uint128, uint32) {
50+
uint256 pathLength_ = path_.length;
51+
52+
require(pathLength_ > 1, "UniswapV3Oracle: invalid path");
53+
require(pathLength_ == fees_.length + 1, "UniswapV3Oracle: path/fee lengths do not match");
54+
require(
55+
block.timestamp > period_,
56+
"UniswapV3Oracle: period larger than current timestamp"
57+
);
58+
require(period_ > 0, "UniswapV3Oracle: period can't be 0");
59+
60+
if (amount_ == 0) {
61+
return (0, 0);
62+
}
63+
64+
uint32 minPeriod_ = period_;
65+
66+
for (uint256 i = 0; i < pathLength_ - 1; i++) {
67+
uint32 currentPeriod_;
68+
(amount_, currentPeriod_) = _getPriceOfTokenInToken(
69+
path_[i],
70+
path_[i + 1],
71+
amount_,
72+
fees_[i],
73+
period_
74+
);
75+
76+
minPeriod_ = uint32(minPeriod_ < currentPeriod_ ? minPeriod_ : currentPeriod_);
77+
}
78+
79+
return (amount_, minPeriod_);
80+
}
81+
82+
/**
83+
* @notice The private function to get the price of a token inside a pool
84+
* @dev Price is expected to fit into uint128
85+
*/
86+
function _getPriceOfTokenInToken(
87+
address baseToken_,
88+
address quoteToken_,
89+
uint128 amount_,
90+
uint24 fee_,
91+
uint32 period_
92+
) private view returns (uint128, uint32) {
93+
if (baseToken_ == quoteToken_) {
94+
return (amount_, period_);
95+
}
96+
97+
address pool_ = uniswapV3Factory.getPool(baseToken_, quoteToken_, fee_);
98+
99+
require(pool_ != address(0), "UniswapV3Oracle: such pool doesn't exist");
100+
101+
uint32 longestPeriod_ = OracleLibrary.getOldestObservationSecondsAgo(pool_);
102+
103+
require(
104+
longestPeriod_ != 0,
105+
"UniswapV3Oracle: the oldest observation is on the current block"
106+
);
107+
108+
period_ = uint32(period_ < longestPeriod_ ? period_ : longestPeriod_);
109+
110+
(int24 arithmeticMeanTick, ) = OracleLibrary.consult(pool_, period_);
111+
112+
return (
113+
uint128(
114+
OracleLibrary.getQuoteAtTick(arithmeticMeanTick, amount_, baseToken_, quoteToken_)
115+
),
116+
period_
117+
);
118+
}
119+
}

contracts/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@solarity/solidity-lib",
3-
"version": "2.6.9",
3+
"version": "2.6.10",
44
"license": "MIT",
55
"author": "Distributed Lab",
66
"readme": "README.md",
@@ -24,6 +24,8 @@
2424
"@openzeppelin/contracts": "4.9.2",
2525
"@openzeppelin/contracts-upgradeable": "4.9.2",
2626
"@uniswap/v2-core": "1.0.1",
27-
"@uniswap/v2-periphery": "1.1.0-beta.0"
27+
"@uniswap/v2-periphery": "1.1.0-beta.0",
28+
"@uniswap/v3-core": "1.0.1",
29+
"@uniswap/v3-periphery": "1.4.4"
2830
}
2931
}

hardhat.config.ts

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import "@nomiclabs/hardhat-web3";
2-
import "@nomiclabs/hardhat-truffle5";
31
import "@nomicfoundation/hardhat-ethers";
42
import "@nomicfoundation/hardhat-chai-matchers";
53
import "@nomicfoundation/hardhat-network-helpers";
@@ -15,36 +13,38 @@ import { HardhatUserConfig } from "hardhat/config";
1513
import * as dotenv from "dotenv";
1614
dotenv.config();
1715

18-
function typechainTarget() {
19-
const target = process.env.TYPECHAIN_TARGET;
20-
21-
return target === "" || target === undefined ? "ethers-v6" : target;
22-
}
23-
24-
function forceTypechain() {
25-
return process.env.TYPECHAIN_FORCE === "false";
26-
}
27-
2816
const config: HardhatUserConfig = {
2917
networks: {
3018
hardhat: {
3119
initialDate: "1970-01-01T00:00:00Z",
3220
},
3321
localhost: {
3422
url: "http://127.0.0.1:8545",
35-
initialDate: "1970-01-01T00:00:00Z",
3623
gasMultiplier: 1.2,
3724
},
3825
},
3926
solidity: {
40-
version: "0.8.20",
41-
settings: {
42-
optimizer: {
43-
enabled: true,
44-
runs: 200,
27+
compilers: [
28+
{
29+
version: "0.8.20",
30+
settings: {
31+
optimizer: {
32+
enabled: true,
33+
runs: 200,
34+
},
35+
evmVersion: "paris",
36+
},
4537
},
46-
evmVersion: "paris",
47-
},
38+
{
39+
version: "0.7.6",
40+
settings: {
41+
optimizer: {
42+
enabled: true,
43+
runs: 200,
44+
},
45+
},
46+
},
47+
],
4848
},
4949
markup: {
5050
onlyFiles: ["./contracts/"],
@@ -65,11 +65,10 @@ const config: HardhatUserConfig = {
6565
coinmarketcap: `${process.env.COINMARKETCAP_KEY}`,
6666
},
6767
typechain: {
68-
outDir: `generated-types/${typechainTarget().split("-")[0]}`,
69-
target: typechainTarget(),
68+
outDir: "generated-types/ethers",
69+
target: "ethers-v6",
7070
alwaysGenerateOverloads: true,
7171
discriminateTypes: true,
72-
dontOverrideCompile: forceTypechain(),
7372
},
7473
};
7574

0 commit comments

Comments
 (0)