Dapp Use Cases
Price Within Ticks
Introduction
Assertions
- Dapp Use Cases
- Implementation / Address Change
- Owner Change
- Constant Product
- Lending Health Factor
- Sum of all positions
- Timelock Verification
- Oracle Validation
- TWAP Deviation
- ERC4626 Assets to Shares
- ERC4626 Deposit and Withdraw
- Fee Calculations
- Price Within Ticks
- Liquidation Health Factor
- Panic State Validation
- Harvest Increases Balance
- Tokens Borrowed Invariant
- ERC20 Drain
- Ether Drain
- Farcaster Message Validity
- Farcaster Unique Username
- Farcaster Rate Limits
- Correct Withdraw
- Intra-tx Oracle Deviation
- Previous Hacks Assertions
Dapp Use Cases
Price Within Ticks
Make sure that the price is within the tick range
Use Case
In some AMMs, the price is represented by a tick. A tick is a unit of measurement for the price of an asset. It is important to make sure that the price is within the tick range.
Explanation
Check the exact boundaries of the tick range before and after a trade to make sure that the price is within the tick range.
Code Example
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import {Assertion} from "../../lib/credible-std/src/Assertion.sol";
// Uniswap v3 pool style interface
interface IUniswapV3Pool {
function slot0()
external
view
returns (
uint160 sqrtPriceX96,
int24 tick,
uint16 observationIndex,
uint16 observationCardinality,
uint16 observationCardinalityNext,
uint8 feeProtocol,
bool unlocked
);
function tickSpacing() external view returns (int24);
function tickToPrice(int24 tick) external view returns (uint256);
}
// Check that the price is within the tick bounds
contract PriceWithinTicksAssertion is Assertion {
IUniswapV3Pool public pool = IUniswapV3Pool(address(0xbeef));
// Uniswap V3 tick bounds
int24 constant MIN_TICK = -887272;
int24 constant MAX_TICK = 887272;
function fnSelectors() external pure override returns (bytes4[] memory assertions) {
assertions = new bytes4[](1); // Define the number of triggers
assertions[0] = this.priceWithinTicks.selector; // Define the trigger
}
// Check that the price is within the tick bounds and that the tick is divisible by the tick spacing
function priceWithinTicks() external {
// Get pre-swap state
ph.forkPreState();
(, int24 preTick,,,,,) = pool.slot0();
// Get post-swap state
ph.forkPostState();
(, int24 postTick,,,,,) = pool.slot0();
int24 spacing = pool.tickSpacing();
// Check 1: Tick must be within global bounds
require(postTick >= MIN_TICK && postTick <= MAX_TICK, "Tick outside global bounds");
// Check 2: Tick must be divisible by tickSpacing
require(postTick % spacing == 0, "Tick not aligned with spacing");
// Check 3: Tick movement should be reasonable
int24 tickDelta = postTick - preTick;
require(tickDelta < 1000 && tickDelta > -1000, "Suspicious tick movement");
// Check price impact
uint256 prePrice = pool.tickToPrice(preTick);
uint256 postPrice = pool.tickToPrice(postTick);
uint256 priceChange;
if (postPrice > prePrice) {
priceChange = ((postPrice - prePrice) * 10000) / prePrice;
} else {
priceChange = ((prePrice - postPrice) * 10000) / prePrice;
}
// Alert on large price impacts (e.g., > 10%)
require(priceChange <= 1000, "Price impact too high"); // Should be what the protocol defines
}
}
On this page