Skip to content

Commit 69ed443

Browse files
authored
Merge pull request #24 from VenusProtocol/feat/vpd-349
[VPD-349] PriceDeviationSentinel Contract
2 parents 03b2091 + ee220df commit 69ed443

File tree

66 files changed

+23680
-1099
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+23680
-1099
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ $ yarn hardhat export --network <network-name> --export ./deployments/<network-n
7171
## Source Code Verification
7272

7373
In order to verify the source code of already deployed contracts, run:
74-
`npx hardhat etherscan-verify --network <network_name>`
74+
`npx hardhat verify --network <network-name> <contract-address> <constructor-arg1> <constructor-arg2>`
7575

7676
Make sure you have added `ETHERSCAN_API_KEY` in `.env` file.
7777

246 KB
Binary file not shown.

contracts/DeviationSentinel/DeviationSentinel.sol

Lines changed: 518 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
pragma solidity ^0.8.25;
3+
import { IPancakeV3Pool } from "../../Interfaces/IPancakeV3Pool.sol";
4+
import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
5+
import { AccessControlledV8 } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol";
6+
import { FixedPoint96 } from "../../Libraries/FixedPoint96.sol";
7+
import { FullMath } from "../../Libraries/FullMath.sol";
8+
9+
/**
10+
* @title PancakeSwapOracle
11+
* @author Venus
12+
* @notice Oracle contract for fetching asset prices from PancakeSwap V3
13+
*/
14+
contract PancakeSwapOracle is AccessControlledV8 {
15+
/// @notice Resilient Oracle for getting reference token prices
16+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
17+
ResilientOracleInterface public immutable RESILIENT_ORACLE;
18+
19+
/// @notice Mapping of token addresses to their pool addresses
20+
mapping(address => address) public tokenPools;
21+
22+
/// @notice Emitted when a token's pool configuration is updated
23+
/// @param token The token address
24+
/// @param pool The pool address
25+
event PoolConfigUpdated(address indexed token, address indexed pool);
26+
27+
/// @notice Thrown when a zero address is provided
28+
error ZeroAddress();
29+
30+
/// @notice Thrown when an invalid pool address is provided
31+
error InvalidPool();
32+
33+
/// @notice Thrown when token is not configured
34+
error TokenNotConfigured();
35+
36+
/// @notice Constructor for PancakeSwapPriceOracle
37+
/// @param resilientOracle_ Address of the resilient oracle
38+
/// @custom:oz-upgrades-unsafe-allow constructor
39+
constructor(ResilientOracleInterface resilientOracle_) {
40+
RESILIENT_ORACLE = resilientOracle_;
41+
42+
// Note that the contract is upgradeable. Use initialize() or reinitializers
43+
// to set the state variables.
44+
_disableInitializers();
45+
}
46+
47+
/// @notice Initialize the contract
48+
/// @param accessControlManager_ Address of the access control manager
49+
function initialize(address accessControlManager_) external initializer {
50+
__AccessControlled_init(accessControlManager_);
51+
}
52+
53+
/// @notice Set pool configuration for a token
54+
/// @param token Address of the token
55+
/// @param pool Address of the PancakeSwap V3 pool
56+
/// @custom:event Emits PoolConfigUpdated event
57+
/// @custom:error ZeroAddress is thrown when token or pool address is zero
58+
function setPoolConfig(address token, address pool) external {
59+
_checkAccessAllowed("setPoolConfig(address,address)");
60+
61+
if (token == address(0) || pool == address(0)) revert ZeroAddress();
62+
63+
tokenPools[token] = pool;
64+
emit PoolConfigUpdated(token, pool);
65+
}
66+
67+
/// @notice Get the price of an asset from PancakeSwap V3
68+
/// @param asset Address of the asset
69+
/// @return price Price in (36 - asset decimals) format, same as ResilientOracle
70+
/// @custom:error TokenNotConfigured is thrown when asset has no pool configured
71+
function getPrice(address asset) external view returns (uint256 price) {
72+
address pool = tokenPools[asset];
73+
if (pool == address(0)) revert TokenNotConfigured();
74+
75+
return _getPancakeSwapV3Price(pool, asset);
76+
}
77+
78+
/// @notice Get token price from PancakeSwap V3 pool
79+
/// @param pool PancakeSwap V3 pool address
80+
/// @param token Target token address
81+
/// @return price Price in (36 - token decimals) format
82+
function _getPancakeSwapV3Price(address pool, address token) internal view returns (uint256 price) {
83+
IPancakeV3Pool v3Pool = IPancakeV3Pool(pool);
84+
address token0 = v3Pool.token0();
85+
address token1 = v3Pool.token1();
86+
(uint160 sqrtPriceX96, , , , , , ) = v3Pool.slot0();
87+
88+
uint256 priceX96 = FullMath.mulDiv(sqrtPriceX96, sqrtPriceX96, FixedPoint96.Q96);
89+
90+
address referenceToken;
91+
bool targetIsToken0;
92+
93+
if (token == token0) {
94+
targetIsToken0 = true;
95+
referenceToken = token1;
96+
} else if (token == token1) {
97+
targetIsToken0 = false;
98+
referenceToken = token0;
99+
} else {
100+
revert InvalidPool();
101+
}
102+
103+
uint256 referencePrice = RESILIENT_ORACLE.getPrice(referenceToken);
104+
105+
if (targetIsToken0) {
106+
price = FullMath.mulDiv(referencePrice, priceX96, FixedPoint96.Q96);
107+
} else {
108+
price = FullMath.mulDiv(referencePrice, FixedPoint96.Q96, priceX96);
109+
}
110+
}
111+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
pragma solidity ^0.8.25;
3+
4+
import { OracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
5+
import { AccessControlledV8 } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol";
6+
7+
/**
8+
* @title SentinelOracle
9+
* @author Venus
10+
* @notice Aggregator oracle that routes price requests to appropriate DEX oracles
11+
*/
12+
contract SentinelOracle is AccessControlledV8 {
13+
/// @notice Configuration for token price source
14+
/// @param oracle Address of the DEX oracle to use for this token
15+
struct TokenConfig {
16+
address oracle;
17+
}
18+
19+
/// @notice Mapping of token addresses to their oracle configuration
20+
mapping(address => TokenConfig) public tokenConfigs;
21+
22+
/// @notice Mapping of token addresses to their direct prices (0 means not set)
23+
mapping(address => uint256) public directPrices;
24+
25+
/// @notice Emitted when a token's oracle configuration is updated
26+
/// @param token The token address
27+
/// @param oracle The oracle address
28+
event TokenOracleConfigUpdated(address indexed token, address indexed oracle);
29+
30+
/// @notice Emitted when a token's direct price is set or unset
31+
/// @param token The token address
32+
/// @param price The direct price (0 means unset)
33+
event DirectPriceUpdated(address indexed token, uint256 price);
34+
35+
/// @notice Thrown when a zero address is provided
36+
error ZeroAddress();
37+
38+
/// @notice Thrown when token is not configured
39+
error TokenNotConfigured();
40+
41+
/// @notice Constructor for PriceSentinelOracle
42+
/// @custom:oz-upgrades-unsafe-allow constructor
43+
constructor() {
44+
// Note that the contract is upgradeable. Use initialize() or reinitializers
45+
// to set the state variables.
46+
_disableInitializers();
47+
}
48+
49+
/// @notice Initialize the contract
50+
/// @param accessControlManager_ Address of the access control manager
51+
function initialize(address accessControlManager_) external initializer {
52+
__AccessControlled_init(accessControlManager_);
53+
}
54+
55+
/// @notice Set oracle configuration for a token
56+
/// @param token Address of the token
57+
/// @param oracle Address of the DEX oracle to use
58+
/// @custom:event Emits TokenOracleConfigUpdated event
59+
/// @custom:error ZeroAddress is thrown when token or oracle address is zero
60+
function setTokenOracleConfig(address token, address oracle) external {
61+
_checkAccessAllowed("setTokenOracleConfig(address,address)");
62+
63+
if (token == address(0)) revert ZeroAddress();
64+
if (oracle == address(0)) revert ZeroAddress();
65+
66+
tokenConfigs[token] = TokenConfig({ oracle: oracle });
67+
emit TokenOracleConfigUpdated(token, oracle);
68+
}
69+
70+
/// @notice Set direct price for a token
71+
/// @param token Address of the token
72+
/// @param price Direct price to set (set to 0 to unset and use DEX oracle)
73+
/// @dev When a direct price is set (non-zero), it will be used instead of fetching from DEX oracle
74+
/// @custom:event Emits DirectPriceUpdated event
75+
/// @custom:error ZeroAddress is thrown when token address is zero
76+
function setDirectPrice(address token, uint256 price) external {
77+
_checkAccessAllowed("setDirectPrice(address,uint256)");
78+
79+
if (token == address(0)) revert ZeroAddress();
80+
81+
directPrices[token] = price;
82+
emit DirectPriceUpdated(token, price);
83+
}
84+
85+
/// @notice Get the price of an asset from the configured DEX oracle
86+
/// @param asset Address of the asset
87+
/// @return price Price in (36 - asset decimals) format, same as ResilientOracle
88+
/// @custom:error TokenNotConfigured is thrown when asset has no oracle configured
89+
function getPrice(address asset) external view returns (uint256 price) {
90+
// Check if direct price is set first
91+
uint256 directPrice = directPrices[asset];
92+
if (directPrice != 0) {
93+
return directPrice;
94+
}
95+
96+
// Otherwise, fetch from DEX oracle
97+
TokenConfig memory config = tokenConfigs[asset];
98+
if (config.oracle == address(0)) revert TokenNotConfigured();
99+
100+
return OracleInterface(config.oracle).getPrice(asset);
101+
}
102+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
pragma solidity ^0.8.25;
3+
import { IUniswapV3Pool } from "../../Interfaces/IUniswapV3Pool.sol";
4+
import { ResilientOracleInterface } from "@venusprotocol/oracle/contracts/interfaces/OracleInterface.sol";
5+
import { AccessControlledV8 } from "@venusprotocol/governance-contracts/contracts/Governance/AccessControlledV8.sol";
6+
import { FixedPoint96 } from "../../Libraries/FixedPoint96.sol";
7+
import { FullMath } from "../../Libraries/FullMath.sol";
8+
9+
/**
10+
* @title UniswapOracle
11+
* @author Venus
12+
* @notice Oracle contract for fetching asset prices from Uniswap V3
13+
*/
14+
contract UniswapOracle is AccessControlledV8 {
15+
/// @notice Resilient Oracle for getting reference token prices
16+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
17+
ResilientOracleInterface public immutable RESILIENT_ORACLE;
18+
19+
/// @notice Mapping of token addresses to their pool addresses
20+
mapping(address => address) public tokenPools;
21+
22+
/// @notice Emitted when a token's pool configuration is updated
23+
/// @param token The token address
24+
/// @param pool The pool address
25+
event PoolConfigUpdated(address indexed token, address indexed pool);
26+
27+
/// @notice Thrown when a zero address is provided
28+
error ZeroAddress();
29+
30+
/// @notice Thrown when an invalid pool address is provided
31+
error InvalidPool();
32+
33+
/// @notice Thrown when token is not configured
34+
error TokenNotConfigured();
35+
36+
/// @notice Constructor for UniswapPriceOracle
37+
/// @param resilientOracle_ Address of the resilient oracle
38+
/// @custom:oz-upgrades-unsafe-allow constructor
39+
constructor(ResilientOracleInterface resilientOracle_) {
40+
RESILIENT_ORACLE = resilientOracle_;
41+
42+
// Note that the contract is upgradeable. Use initialize() or reinitializers
43+
// to set the state variables.
44+
_disableInitializers();
45+
}
46+
47+
/// @notice Initialize the contract
48+
/// @param accessControlManager_ Address of the access control manager
49+
function initialize(address accessControlManager_) external initializer {
50+
__AccessControlled_init(accessControlManager_);
51+
}
52+
53+
/// @notice Set pool configuration for a token
54+
/// @param token Address of the token
55+
/// @param pool Address of the Uniswap V3 pool
56+
/// @custom:event Emits PoolConfigUpdated event
57+
/// @custom:error ZeroAddress is thrown when token or pool address is zero
58+
function setPoolConfig(address token, address pool) external {
59+
_checkAccessAllowed("setPoolConfig(address,address)");
60+
61+
if (token == address(0) || pool == address(0)) revert ZeroAddress();
62+
63+
tokenPools[token] = pool;
64+
emit PoolConfigUpdated(token, pool);
65+
}
66+
67+
/// @notice Get the price of an asset from Uniswap V3
68+
/// @param asset Address of the asset
69+
/// @return price Price in (36 - asset decimals) format, same as ResilientOracle
70+
/// @custom:error TokenNotConfigured is thrown when asset has no pool configured
71+
function getPrice(address asset) external view returns (uint256 price) {
72+
address pool = tokenPools[asset];
73+
if (pool == address(0)) revert TokenNotConfigured();
74+
75+
return _getUniswapV3Price(pool, asset);
76+
}
77+
78+
/// @notice Get token price from Uniswap V3 pool
79+
/// @param pool Uniswap V3 pool address
80+
/// @param token Target token address
81+
/// @return price Price in (36 - token decimals) format
82+
function _getUniswapV3Price(address pool, address token) internal view returns (uint256 price) {
83+
IUniswapV3Pool v3Pool = IUniswapV3Pool(pool);
84+
address token0 = v3Pool.token0();
85+
address token1 = v3Pool.token1();
86+
(uint160 sqrtPriceX96, , , , , , ) = v3Pool.slot0();
87+
88+
uint256 priceX96 = FullMath.mulDiv(sqrtPriceX96, sqrtPriceX96, FixedPoint96.Q96);
89+
90+
address referenceToken;
91+
bool targetIsToken0;
92+
93+
if (token == token0) {
94+
targetIsToken0 = true;
95+
referenceToken = token1;
96+
} else if (token == token1) {
97+
targetIsToken0 = false;
98+
referenceToken = token0;
99+
} else {
100+
revert InvalidPool();
101+
}
102+
103+
uint256 referencePrice = RESILIENT_ORACLE.getPrice(referenceToken);
104+
105+
if (targetIsToken0) {
106+
price = FullMath.mulDiv(referencePrice, priceX96, FixedPoint96.Q96);
107+
} else {
108+
price = FullMath.mulDiv(referencePrice, FixedPoint96.Q96, priceX96);
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)