Skip to content

Commit

Permalink
Position observer
Browse files Browse the repository at this point in the history
  • Loading branch information
silvesterdrago committed Oct 22, 2023
1 parent 7dbfcd6 commit dfe24b3
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "lib/safe-contracts"]
path = lib/safe-contracts
url = https://github.com/safe-global/safe-contracts
[submodule "lib/uniswap-periphery"]
path = lib/uniswap-periphery
url = https://github.com/Uniswap/v3-periphery
3 changes: 2 additions & 1 deletion src/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ address constant WBTC_POLYGON = 0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6;
address constant WETH_POLYGON = 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619;

address constant UNISWAP_PERMIT2_POLYGON = 0x000000000022D473030F116dDEE9F6B43aC78BA3;

address constant UNISWAP_POSITION_MANAGER = 0xC36442b4a4522E871399CD717aBDD847Ab11FE88;
address constant UNISWAP_V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
//Goerli
address constant GOERLI_SAFE_PROXY_FACTORY = 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2;
address constant GOERLI_SAFE_LOGIC_SINGLETON = 0x3E5c63644E683549055b9Be8653de26E0B4CD36E;
Expand Down
92 changes: 92 additions & 0 deletions src/PositionObserver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "./Constants.sol";
import "./interfaces/INonfungiblePositionManager.sol";
import "./interfaces/IUniswapV3Factory.sol";
import "./interfaces/IUniswapV3Pool.sol";
import "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol";

contract PositionObserver {
address public safe;
constructor(
address _safe
){
safe = _safe;
}

function positionCount() external view returns (uint256) {
return INonfungiblePositionManager(UNISWAP_POSITION_MANAGER).balanceOf(address(safe));
}

function tokenIdByIndex(uint256 index) external view returns (uint256) {
return INonfungiblePositionManager(UNISWAP_POSITION_MANAGER).tokenOfOwnerByIndex(address(safe), index);
}

function poolAddress(uint256 tokenId) external view returns (address) {
(
uint96 nonce,
address operator,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
) = INonfungiblePositionManager(UNISWAP_POSITION_MANAGER).positions(tokenId);
return IUniswapV3Factory(UNISWAP_V3_FACTORY).getPool(token0, token1, fee);
}

function calculatePrice(int56 tick0, int56 tick1, int56 secs, uint256 decimals0, uint256 decimals1) internal pure returns (uint256) {
int56 exponent = (tick0 - tick1) / secs;
uint256 base = 10001;
uint256 price = 10000;
while (exponent > 0) {
if (exponent & 1 == 1) {
price = (price * base) / 10000;
}
base = (base * base) / 10000;
exponent >>= 1;
}
price *= 10 ** 18;
if (decimals0 > decimals1) {
price /= 10 ** (decimals0 - decimals1);
} else {
price *= 10 ** (decimals1 - decimals0);
}
return price;
}

function positionPriceByTokenId(uint256 tokenId) external view returns (uint256) {
(
uint96 nonce,
address operator,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
) = INonfungiblePositionManager(UNISWAP_POSITION_MANAGER).positions(tokenId);
uint8 token0Decimals = IERC20Metadata(token0).decimals();
uint8 token1Decimals = IERC20Metadata(token1).decimals();
address pool = IUniswapV3Factory(UNISWAP_V3_FACTORY).getPool(token0, token1, fee);
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = 0;
secondsAgos[1] = 1;
(
int56[] memory tickCumulatives,
uint160[] memory secondsPerLiquidityCumulativeX128s
) = IUniswapV3Pool(pool).observe(secondsAgos);
require(tickCumulatives.length == 2);
return calculatePrice(tickCumulatives[0], tickCumulatives[1], 1, token0Decimals, token1Decimals);
}
}
26 changes: 26 additions & 0 deletions src/interfaces/INonfungiblePositionManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

interface INonfungiblePositionManager {
function positions(uint256 tokenId)
external
view
returns (
uint96 nonce,
address operator,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
);

function balanceOf(address owner) external view returns (uint256);

function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
}
12 changes: 12 additions & 0 deletions src/interfaces/IUniswapV3Factory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

interface IUniswapV3Factory {

function getPool(
address tokenA,
address tokenB,
uint24 fee
) external view returns (address pool);

}
9 changes: 9 additions & 0 deletions src/interfaces/IUniswapV3Pool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

interface IUniswapV3Pool {
function observe(uint32[] calldata secondsAgos)
external
view
returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);
}
35 changes: 35 additions & 0 deletions test/PositionObserver.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
pragma solidity ^0.8.0;

import {Test, console2} from "forge-std/Test.sol";
import {PositionObserver} from "../src/PositionObserver.sol";

contract PositionObserverTest is Test {
PositionObserver observer;

address constant SAFE_ADDRESS = 0xD4928171A52f420855Bf59bcaF3C7077F42298D5;

function setUp() public {
observer = new PositionObserver(address(SAFE_ADDRESS));
}

function testPositionCount() public {
assertEq(observer.positionCount(), 2);
}

function testTokenIdByIndex() public {
assertEq(observer.tokenIdByIndex(0), 1071846);
assertEq(observer.tokenIdByIndex(1), 1081648);
}

function testPoolAddress() public {
assertEq(observer.poolAddress(1071846), 0x0e44cEb592AcFC5D3F09D996302eB4C499ff8c10);
assertEq(observer.poolAddress(1081648), 0x8Fc5e02d85891BA2855af1904dfc5cf1d82e4a44);
}

function testPositionPrice() public {
console2.log("price", observer.positionPriceByTokenId(1071846));
console2.log("price", observer.positionPriceByTokenId(1081648));
// assertEq(observer.positionPriceByTokenId(1071846), 1000000000000000000);
// assertEq(observer.positionPriceByTokenId(1081648), 1000000000000000000);
}
}

0 comments on commit dfe24b3

Please sign in to comment.