Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions contracts/external/IQuoter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.15;
pragma abicoder v2;

/// @title QuoterV2 Interface
/// @notice Supports quoting the calculated amounts from exact input or exact output swaps.
/// @notice For each pool also tells you the number of initialized ticks crossed and the sqrt price of the pool after the swap.
/// @dev These functions are not marked view because they rely on calling non-view functions and reverting
/// to compute the result. They are also not gas efficient and should not be called on-chain.
interface IQuoter {
/// @notice Returns the amount out received for a given exact input swap without executing the swap
/// @param path The path of the swap, i.e. each token pair and the pool fee
/// @param amountIn The amount of the first token to swap
/// @return amountOut The amount of the last token that would be received
/// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path
/// @return initializedTicksCrossedList List of number of initialized ticks loaded
function quoteExactInput(
bytes memory path,
uint256 amountIn
)
external
view
returns (
uint256 amountOut,
uint160[] memory sqrtPriceX96AfterList,
uint32[] memory initializedTicksCrossedList,
uint256 gasEstimate
);

struct QuoteExactInputSingleWithPoolParams {
address tokenIn;
address tokenOut;
uint256 amountIn;
address pool;
uint24 fee;
uint160 sqrtPriceLimitX96;
}

/// @notice Returns the amount out received for a given exact input but for a swap of a single pool
/// @param params The params for the quote, encoded as `quoteExactInputSingleWithPool`
/// tokenIn The token being swapped in
/// amountIn The desired input amount
/// tokenOut The token being swapped out
/// fee The fee of the pool to consider for the pair
/// pool The address of the pool to consider for the pair
/// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
/// @return amountOut The amount of `tokenOut` that would be received
/// @return sqrtPriceX96After The sqrt price of the pool after the swap
/// @return initializedTicksCrossed The number of initialized ticks loaded
function quoteExactInputSingleWithPool(
QuoteExactInputSingleWithPoolParams memory params
)
external
view
returns (
uint256 amountOut,
uint160 sqrtPriceX96After,
uint32 initializedTicksCrossed,
uint256 gasEstimate
);

struct QuoteExactInputSingleParams {
address tokenIn;
address tokenOut;
uint256 amountIn;
uint24 fee;
uint160 sqrtPriceLimitX96;
}

/// @notice Returns the amount out received for a given exact input but for a swap of a single pool
/// @param params The params for the quote, encoded as `QuoteExactInputSingleParams`
/// tokenIn The token being swapped in
/// amountIn The desired input amount
/// tokenOut The token being swapped out
/// fee The fee of the token pool to consider for the pair
/// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
/// @return amountOut The amount of `tokenOut` that would be received
/// @return sqrtPriceX96After The sqrt price of the pool after the swap
/// @return initializedTicksCrossed The number of initialized ticks loaded
function quoteExactInputSingle(
QuoteExactInputSingleParams memory params
)
external
view
returns (
uint256 amountOut,
uint160 sqrtPriceX96After,
uint32 initializedTicksCrossed,
uint256 gasEstimate
);

struct QuoteExactOutputSingleWithPoolParams {
address tokenIn;
address tokenOut;
uint256 amount;
uint24 fee;
address pool;
uint160 sqrtPriceLimitX96;
}

/// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool
/// @param params The params for the quote, encoded as `QuoteExactOutputSingleWithPoolParams`
/// tokenIn The token being swapped in
/// tokenOut The token being swapped out
/// amount The desired output amount
/// fee The fee of the token pool to consider for the pair
/// pool The address of the pool to consider for the pair
/// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
/// @return amountIn The amount required as the input for the swap in order to receive `amountOut`
/// @return sqrtPriceX96After The sqrt price of the pool after the swap
/// @return initializedTicksCrossed The number of initialized ticks loaded
function quoteExactOutputSingleWithPool(
QuoteExactOutputSingleWithPoolParams memory params
)
external
view
returns (
uint256 amountIn,
uint160 sqrtPriceX96After,
uint32 initializedTicksCrossed,
uint256 gasEstimate
);

struct QuoteExactOutputSingleParams {
address tokenIn;
address tokenOut;
uint256 amount;
uint24 fee;
uint160 sqrtPriceLimitX96;
}

/// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool
/// @param params The params for the quote, encoded as `QuoteExactOutputSingleParams`
/// tokenIn The token being swapped in
/// tokenOut The token being swapped out
/// amountOut The desired output amount
/// fee The fee of the token pool to consider for the pair
/// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
/// @return amountIn The amount required as the input for the swap in order to receive `amountOut`
/// @return sqrtPriceX96After The sqrt price of the pool after the swap
/// @return initializedTicksCrossed The number of initialized ticks loaded
function quoteExactOutputSingle(
QuoteExactOutputSingleParams memory params
)
external
view
returns (
uint256 amountIn,
uint160 sqrtPriceX96After,
uint32 initializedTicksCrossed,
uint256 gasEstimate
);

/// @notice Returns the amount in required for a given exact output swap without executing the swap
/// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
/// @param amountOut The amount of the last token to receive
/// @return amountIn The amount of first token required to be paid
/// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path
/// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
function quoteExactOutput(
bytes memory path,
uint256 amountOut
)
external
view
returns (
uint256 amountIn,
uint160[] memory sqrtPriceX96AfterList,
uint32[] memory initializedTicksCrossedList,
uint256 gasEstimate
);
}
94 changes: 94 additions & 0 deletions contracts/l2/UniswapPriceOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity 0.8.15;

import '@openzeppelin/contracts/access/Ownable.sol';
import '../external/IQuoter.sol';
import '../interfaces/IPriceOracle.sol';

interface IUniswapV3Pool {
function slot0()
external
view
returns (
uint160 sqrtPriceX96,
int24 tick,
uint16 observationIndex,
uint16 observationCardinality,
uint16 observationCardinalityNext,
uint8 feeProtocol,
bool unlocked
);

function token0() external view returns (address);
function token1() external view returns (address);
}

contract UniswapPriceOracle is Ownable, IPriceOracle {
IQuoter public quoter; // 0x222ca98f00ed15b1fae10b61c277703a194cf5d2, https://github.com/Uniswap/view-quoter-v3/tree/master
mapping(bytes32 => uint24) public poolFees;

constructor(address _quoterAddress) Ownable() {
quoter = IQuoter(_quoterAddress);
}

function setQuoter(address _quoterAddress) external onlyOwner {
quoter = IQuoter(_quoterAddress);
}

function setPoolFee(address fromToken, address toToken, uint24 _poolFee) external onlyOwner {
bytes32 poolkey = _concatAddresses(fromToken, toToken);
poolFees[poolkey] = _poolFee;
}

function getPoolFee(address fromToken, address toToken) public view returns (uint24) {
bytes32 poolkey = _concatAddresses(fromToken, toToken);
return poolFees[poolkey] == 0 ? 3000 : poolFees[poolkey];
}

function getAssetPrice(
address fromToken,
address toToken
) external view override returns (uint256) {
return _convertPrice(fromToken, toToken, 1);
}

function convertPrice(
address fromToken,
address toToken,
uint256 amount
) external view override returns (uint256) {
require(amount > 0, 'Amount must be greater than 0');
return _convertPrice(fromToken, toToken, amount);
}

// usdc: 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913
// sqt: 0x858c50c3af1913b0e849afdb74617388a1a5340d

function _convertPrice(
address fromToken,
address toToken,
uint256 amount
) internal view returns (uint256) {
// Simulate the swap using the QuoterV2 contract
IQuoter.QuoteExactInputSingleParams memory params = IQuoter.QuoteExactInputSingleParams({
tokenIn: fromToken,
tokenOut: toToken,
amountIn: amount,
fee: getPoolFee(fromToken, toToken),
sqrtPriceLimitX96: 0 // No price limit
});

(uint256 amountOut, , , ) = quoter.quoteExactInputSingle(params);

return amountOut;
}

function _concatAddresses(address addr1, address addr2) internal pure returns (bytes32) {
if (addr1 > addr2) {
(addr1, addr2) = (addr2, addr1);
}
return (bytes32(bytes20(addr1)) << 96) | bytes32(bytes20(addr2));
}
}
5 changes: 5 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,11 @@ task('publishChild', 'verify and publish contracts on etherscan')
address: deployment.L2Vesting.innerAddress,
constructorArguments: [],
});
//UniswapPriceOracle
await hre.run('verify:verify', {
address: deployment.UniswapPriceOracle.address,
constructorArguments: contractsConfig[taskArgs.networkpair].UniswapPriceOracle,
});
} catch (err) {
console.log(err);
}
Expand Down
6 changes: 6 additions & 0 deletions publish/mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@
"address": "0x915FbbfF55775B6243A3edB42BE08554C44DE45a",
"bytecodeHash": "5994844f015a7b599b6e5a10a50cec76be5e60fa20439f0fdd2cfd49df3a15ed",
"lastUpdate": "Tue, 16 Apr 2024 05:16:47 GMT"
},
"UniswapPriceOracle": {
"innerAddress": "",
"address": "0xDACAfD570e55eD179f2A37c6A1B20fD4FceC62AE",
"bytecodeHash": "4f491154f798bcad9bb40e8a538e43bab1e6a2cce5468504d776a44d3926e721",
"lastUpdate": "Wed, 19 Mar 2025 01:21:59 GMT"
}
}
}
2 changes: 2 additions & 0 deletions scripts/config/contracts.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default {
RewardsBooster: [utils.parseEther('6.3419584'), utils.parseEther('1000')], // _issuancePerBlock, _minimumDeploymentBooster
L2SQToken: ['0x4200000000000000000000000000000000000010', '0x09395a2A58DB45db0da254c7EAa5AC469D8bDc85'], // l2bridge, l1token
PriceOracle: [10, 3600],
UniswapPriceOracle: ['0x222ca98f00ed15b1fae10b61c277703a194cf5d2'],
},
kepler: {
InflationController: [0, '0x34c35136ECe9CBD6DfDf2F896C6e29be01587c0C'], // inflationRate, inflationDestination
Expand Down Expand Up @@ -64,6 +65,7 @@ export default {
// base: 2s a block, 31536000/2 = 15768000 blocks a year, 1% rewards = 100000000 / 15768000 = about 6.3419584 SQT per block
RewardsBooster: [utils.parseEther('6.34'), utils.parseEther('10000')], // _issuancePerBlock, _minimumDeploymentBooster
L2SQToken: ['0x4200000000000000000000000000000000000010', '0xE6E15Ffc71AbDAe8D34D65bB695959fbd6c15435'], // l2bridge, l1token
UniswapPriceOracle: ['0x222ca98f00ed15b1fae10b61c277703a194cf5d2'],
},
local: {
InflationController: [1000, '0x4ae8fcdddc859e2984ce0b8f4ef490d61a7a9b7f'], // inflationRate, inflationDestination
Expand Down
7 changes: 6 additions & 1 deletion scripts/deployContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
L2SQToken,
AirdropperLite,
L2Vesting,
UniswapPriceOracle,
} from '../src';
import { Config, ContractConfig, Contracts, UPGRADEBAL_CONTRACTS } from './contracts';
import { l1StandardBridge } from './L1StandardBridge';
Expand Down Expand Up @@ -522,7 +523,11 @@ export async function deployContracts(
proxyAdmin,
initConfig: [settingsAddress],
});
logger?.info('🤞 L2Vesting');

//deploy uniswapPriceOracle contract
const uniswapPriceOracle = await deployContract<UniswapPriceOracle>('UniswapPriceOracle', 'child', {
deployConfig: [...config['UniswapPriceOracle']],
});

// Register addresses on settings contract
logger?.info('🤞 Set settings addresses');
Expand Down
2 changes: 2 additions & 0 deletions src/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import SQTRedeem from './artifacts/contracts/SQTRedeem.sol/SQTRedeem.json';
import L2SQToken from './artifacts/contracts/l2/L2SQToken.sol/L2SQToken.json';
import AirdropperLite from './artifacts/contracts/root/AirdropperLite.sol/AirdropperLite.json';
import L2Vesting from './artifacts/contracts/l2/L2Vesting.sol/L2Vesting.json';
import UniswapPriceOracle from './artifacts/contracts/l2/UniswapPriceOracle.sol/UniswapPriceOracle.json';

export default {
Settings,
Expand Down Expand Up @@ -73,4 +74,5 @@ export default {
L2SQToken,
AirdropperLite,
L2Vesting,
UniswapPriceOracle,
};
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
L2SQToken__factory,
AirdropperLite__factory,
L2Vesting__factory,
UniswapPriceOracle__factory,
} from './typechain';

export type SubqueryNetwork = 'testnet' | 'testnet-mumbai' | 'mainnet' | 'local';
Expand Down Expand Up @@ -137,6 +138,7 @@ export const CONTRACT_FACTORY: Record<ContractName, FactoryContstructor> = {
L2SQToken: L2SQToken__factory,
AirdropperLite: AirdropperLite__factory,
L2Vesting: L2Vesting__factory,
UniswapPriceOracle: UniswapPriceOracle__factory,
};

export enum SQContracts {
Expand Down
1 change: 1 addition & 0 deletions test/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const deployContracts = async (wallet: Wallet, wallet1: Wallet, treasury
ConsumerRegistry: [],
PriceOracle: [],
RewardsBooster: [utils.parseEther('10').toString(), utils.parseEther('10000').toString()], // _issuancePerBlock, _minimumDeploymentBooster
UniswapPriceOracle: ['0x222ca98f00ed15b1fae10b61c277703a194cf5d2'],
});
await contracts.settings.setContractAddress(SQContracts.Treasury, treasury.address);

Expand Down