Skip to content
Open
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
**/node_modules


# rust target folders
**/target

packages/contracts/generated
packages/contracts/deployments/hardhat
packages/contracts/deployments/tenderly
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-Licence-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

interface IUniswapPricingLibraryV2 {


struct PoolRouteConfig {
address pool;
bool zeroForOne;
uint32 twapInterval;
uint256 token0Decimals;
uint256 token1Decimals;
}



function getUniswapPriceRatioForPoolRoutes(
PoolRouteConfig[] memory poolRoutes
) external view returns (uint256 priceRatio);


function getUniswapPriceRatioForPool(
PoolRouteConfig memory poolRoute
) external view returns (uint256 priceRatio);


function getUniswapPriceRatioForPool(
IUniswapPricingLibraryV2.PoolRouteConfig memory _poolRouteConfig,
uint128 expansion_factor
) external view returns (uint256 priceRatio);

}
181 changes: 181 additions & 0 deletions packages/contracts/contracts/libraries/UniswapPricingLibraryV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
pragma solidity >=0.8.0 <0.9.0;
// SPDX-License-Identifier: MIT



import {IUniswapPricingLibraryV2} from "../interfaces/IUniswapPricingLibraryV2.sol";

import "@openzeppelin/contracts/utils/math/Math.sol";


import "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";

// Libraries
import { MathUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol";

import "../interfaces/uniswap/IUniswapV3Pool.sol";

import "../libraries/uniswap/TickMath.sol";
import "../libraries/uniswap/FixedPoint96.sol";
import "../libraries/uniswap/FullMath.sol";


import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";


/*

Only do decimal expansion if it is an ERC20 not anything else !!

*/

library UniswapPricingLibraryV2
{

uint256 constant STANDARD_EXPANSION_FACTOR = 1e18;


function getUniswapPriceRatioForPoolRoutes(
IUniswapPricingLibraryV2.PoolRouteConfig[] memory poolRoutes
) public view returns (uint256 priceRatio) {
require(poolRoutes.length <= 2, "invalid pool routes length");

if (poolRoutes.length == 2) {
uint256 pool0PriceRatio = getUniswapPriceRatioForPool(
poolRoutes[0]
);

uint256 pool1PriceRatio = getUniswapPriceRatioForPool(
poolRoutes[1]
);

return
FullMath.mulDiv(
pool0PriceRatio,
pool1PriceRatio,
STANDARD_EXPANSION_FACTOR
);
} else if (poolRoutes.length == 1) {
return getUniswapPriceRatioForPool(poolRoutes[0]);
}

//else return 0
}

/*
The resultant product is expanded by STANDARD_EXPANSION_FACTOR one time
*/
function getUniswapPriceRatioForPool(
IUniswapPricingLibraryV2.PoolRouteConfig memory _poolRouteConfig
) public view returns (uint256 priceRatio) {



// this is expanded by 2**96 or 1e28
uint160 sqrtPriceX96 = getSqrtTwapX96(
_poolRouteConfig.pool,
_poolRouteConfig.twapInterval
);


bool invert = ! _poolRouteConfig.zeroForOne;

//this output will be expanded by 1e18
return getQuoteFromSqrtRatioX96( sqrtPriceX96 , uint128( STANDARD_EXPANSION_FACTOR ), invert) ;



}


function getUniswapPriceRatioForPool(
IUniswapPricingLibraryV2.PoolRouteConfig memory _poolRouteConfig,
uint128 expansion_factor
) public view returns (uint256 priceRatio) {


// this is expanded by 2**96 or 1e28
uint160 sqrtPriceX96 = getSqrtTwapX96(
_poolRouteConfig.pool,
_poolRouteConfig.twapInterval
);


bool invert = ! _poolRouteConfig.zeroForOne;

//this output will be expanded by expansion_factor
return getQuoteFromSqrtRatioX96( sqrtPriceX96 , expansion_factor , invert) ;



}


//taken directly from uniswap oracle lib
/**
* @dev Calculates the amount of quote token received for a given amount of base token
* based on the square root of the price ratio (sqrtRatioX96).
*
* @param sqrtRatioX96 The square root of the price ratio(in terms of token1/token0) between two tokens, encoded as a Q64.96 value.
* @param baseAmount The amount of the base token for which the quote is to be calculated. Specify 1e18 for a price(quoteAmount) with 18 decimals of precision.
* @param inverse Specifies the direction of the price quote. If true then baseAmount must be the amount of token1, if false then baseAmount must be the amount for token0
*
* @return quoteAmount The calculated amount of the quote token for the specified baseAmount
*/

function getQuoteFromSqrtRatioX96(
uint160 sqrtRatioX96,
uint128 baseAmount,
bool inverse
) internal pure returns (uint256 quoteAmount) {
// Calculate quoteAmount with better precision if it doesn't overflow when multiplied by itself
if (sqrtRatioX96 <= type(uint128).max) {
uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96;
quoteAmount = !inverse
? FullMath.mulDiv(ratioX192, baseAmount, 1 << 192)
: FullMath.mulDiv(1 << 192, baseAmount, ratioX192);
} else {
uint256 ratioX128 = FullMath.mulDiv(
sqrtRatioX96,
sqrtRatioX96,
1 << 64
);
quoteAmount = !inverse
? FullMath.mulDiv(ratioX128, baseAmount, 1 << 128)
: FullMath.mulDiv(1 << 128, baseAmount, ratioX128);
}
}


function getSqrtTwapX96(address uniswapV3Pool, uint32 twapInterval)
internal
view
returns (uint160 sqrtPriceX96)
{
if (twapInterval == 0) {
// return the current price if twapInterval == 0
(sqrtPriceX96, , , , , , ) = IUniswapV3Pool(uniswapV3Pool).slot0();
} else {
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = twapInterval + 1; // from (before)
secondsAgos[1] = 1; // one block prior

(int56[] memory tickCumulatives, ) = IUniswapV3Pool(uniswapV3Pool)
.observe(secondsAgos);

// tick(imprecise as it's an integer) to price
sqrtPriceX96 = TickMath.getSqrtRatioAtTick(
int24(
(tickCumulatives[1] - tickCumulatives[0]) /
int32(twapInterval)
)
);
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DeployFunction } from 'hardhat-deploy/dist/types'

const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const uniswapPricingLibraryV2 = await hre.deployments.deploy('UniswapPricingLibraryV2', {
from: deployer,
})
}

// tags and deployment
deployFn.id = 'teller-v2:uniswap-pricing-library-v2'
deployFn.tags = ['teller-v2', 'teller-v2:uniswap-pricing-library-v2']
deployFn.dependencies = ['']


deployFn.skip = async (hre) => {
return !hre.network.live || !['sepolia', 'polygon' , 'base','arbitrum','mainnet','mainnet_live_fork'].includes(hre.network.name)
}
export default deployFn
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { DeployFunction } from 'hardhat-deploy/dist/types'

const deployFn: DeployFunction = async (hre) => {
hre.log('----------')
hre.log('')
hre.log('Lendergroups: Proposing upgrade...')



const lenderCommitmentGroupBeaconProxy = await hre.contracts.get('LenderCommitmentGroupBeacon')

const tellerV2 = await hre.contracts.get('TellerV2')
const SmartCommitmentForwarder = await hre.contracts.get(
'SmartCommitmentForwarder'
)
const tellerV2Address = await tellerV2.getAddress()

const smartCommitmentForwarderAddress =
await SmartCommitmentForwarder.getAddress()

let uniswapV3FactoryAddress: string
switch (hre.network.name) {
case 'mainnet':
case 'goerli':
case 'arbitrum':
case 'optimism':
case 'polygon':
case 'localhost':
uniswapV3FactoryAddress = '0x1F98431c8aD98523631AE4a59f267346ea31F984'
break
case 'base':
uniswapV3FactoryAddress = '0x33128a8fC17869897dcE68Ed026d694621f6FDfD'
break
case 'sepolia':
uniswapV3FactoryAddress = '0x0227628f3F023bb0B980b67D528571c95c6DaC1c'
break
default:
throw new Error('No swap factory address found for this network')
}
const uniswapPricingLibraryV2 = await hre.deployments.get('uniswapPricingLibraryV2')



//this is why the owner of the beacon should be timelock controller !
// so we can upgrade it like this . Using a proposal. This actually goes AROUND the proxy admin, interestingly.
await hre.upgrades.proposeBatchTimelock({
title: 'LenderGroups: PoolOracle View',
description: `
# LenderGroups

* A patch to update the uniswap pricing library v2.
`,
_steps: [
{
beacon: lenderCommitmentGroupBeaconProxy,
implFactory: await hre.ethers.getContractFactory('LenderCommitmentGroup_Smart', {
libraries: {
uniswapPricingLibraryV2: uniswapPricingLibraryV2.address,
},
}),

opts: {
// unsafeSkipStorageCheck: true,
unsafeAllow: [
'constructor',
'state-variable-immutable',
'external-library-linking',
],
constructorArgs: [
tellerV2Address,
smartCommitmentForwarderAddress,
uniswapV3FactoryAddress,

],
},
},
],
})

hre.log('done.')
hre.log('')
hre.log('----------')

return true
}

// tags and deployment
deployFn.id = 'lender-commitment-group-beacon:upgrade-uniswap-price-lib-v2'
deployFn.tags = ['lender-commitment-group-beacon']
deployFn.dependencies = [
'teller-v2:deploy',
'teller-v2:uniswap-pricing-library-v2',
'smart-commitment-forwarder:deploy',
'teller-v2:uniswap-pricing-library',
'lender-commitment-group-beacon:deploy'
]

deployFn.skip = async (hre) => {
return !hre.network.live || !['sepolia','polygon'].includes(hre.network.name)
}
export default deployFn
Loading