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
6 changes: 1 addition & 5 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
TestOscillonHook:test_beforeSwap_AppliesPolicyLadder_WhenUSDCDepegs() (gas: 433433)
TestOscillonHook:test_beforeSwap_AppliesPolicyLadder_WhenUSDTDepegs() (gas: 426490)
TestOscillonHook:test_beforeSwap_Reverts_WhenCallerIsNotPoolManager() (gas: 16056)
TestOscillonHook:test_beforeSwap_Reverts_WhenExactOutputExceedsDeepDepegCap() (gas: 94780)
TestOscillonHook:test_beforeSwap_Reverts_WhenInputStableIsAbovePegByFreezeThreshold() (gas: 74754)
OscillonHookBasicTest:test_swap_WhenStableDropsTo089_UsesMaxFee() (gas: 278293)
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
"v4-periphery/=lib/v4-periphery/",
"v4-core-test/=lib/v4-core/test/",
"hookmate/=lib/hookmate/src/"
]
],
"solidity.compileUsingRemoteVersion": "v0.8.26+commit.8a97fa7a"
}
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@

Inventory-risk protection hook for Uniswap v4 stable pools.

/\*

- ╔══════════════════════════════════════════════════════════════════╗
- ║ OscillonHook.sol v2.0 — Multi-Pool ║
- ║ ║
- ║ ARCHITECTURE CHANGE vs v1.x: ║
- ║ v1: immutable ORACLE0/ORACLE1/STABLE0/STABLE1 ║
- ║ → hardcoded to ONE pool forever at deploy ║
- ║ ║
- ║ v2: mapping(PoolId => PoolConfig) ║
- ║ → owner calls registerPool() for each stable pair ║
- ║ → supports USDC/USDT, USDC/DAI, USDT/DAI, ║
- ║ USDC/crvUSD, any stable pair with Chainlink feeds ║
- ║ ║
- ║ FREEZE REMOVED: ║
- ║ v1: severe depeg → revert PoolFrozen() → pool bricked ║
- ║ v2: severe depeg → fee capped at MAX_FEE_PIPS (50 bps) ║
- ║ swaps still flow, LPs still protected, nothing breaks ║
- ║ ║
- ║ Supported pools (register after deploy): ║
- ║ • USDC / USDT ║
- ║ • USDC / DAI ║
- ║ • USDT / DAI ║
- ║ • USDC / crvUSD ║
- ║ • any stable pair with a Chainlink USD feed ║
- ╚══════════════════════════════════════════════════════════════════╝
\*/

## Overview

`OscillonHook` is a `beforeSwap` hook that protects LP inventory during stablecoin depeg events.
Expand Down
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ openzeppelin-contracts/=lib/v4-core/lib/openzeppelin-contracts/
permit2/=lib/v4-periphery/lib/permit2/
solmate/=lib/v4-core/lib/solmate/
v4-core/=lib/v4-core/src/
v4-core-test/=lib/v4-core/test/
v4-hooks-public/=lib/v4-hooks-public/
v4-periphery/=lib/v4-periphery/
180 changes: 143 additions & 37 deletions script/DeployOscillonHook.s.sol
Original file line number Diff line number Diff line change
@@ -1,50 +1,156 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {Script} from "forge-std/Script.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
/*
* DeployOscillonHook.s.sol
*
* Deploys OscillonHook and registers 4 stable pools.
*
* Run:
* forge script script/DeployOscillonHook.s.sol:DeployOscillonHookScript \
* --rpc-url $ARBITRUM_RPC \
* --broadcast --verify
*/

import {Script, console2} from "forge-std/Script.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol";
import {HookMiner} from "v4-hooks-public/src/utils/HookMiner.sol";
import {OscillonHook, IChainlinkOracle} from "../src/OscillonHook.sol";

/// @notice Mines and deploys OscillonHook at a valid hook-flagged address.
/// @dev Required env vars:
/// - POOL_MANAGER
/// - ORACLE0
/// - STABLE0
/// - STABLE0_DECIMALS
/// - ORACLE1
/// - STABLE1
/// - STABLE1_DECIMALS
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {OscillonHook} from "../src/OscillonHook.sol";

contract DeployOscillonHookScript is Script {
address constant CREATE2_DEPLOYER = address(0x4e59b44847b379578588920cA78FbF26c0B4956C);
// Foundry deterministic CREATE2 deployer proxy
address constant CREATE2_DEPLOYER =
0x4e59b44847b379578588920cA78FbF26c0B4956C;

// Arbitrum v4 PoolManager from Uniswap v4 deployments
address constant ARBITRUM_POOL_MANAGER =
0x360E68faCcca8cA495c1B759Fd9EEe466db9FB32;

// Stablecoins
address constant USDC = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831;
address constant USDT = 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9;
address constant DAI = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1;
address constant CRVUSD = 0x498Bf2B1e120FeD3ad3D42EA2165E9b73f99C1e5;

// Chainlink USD feeds on Arbitrum
address constant CL_USDC_USD = 0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3;
address constant CL_USDT_USD = 0x3f3f5dF88dC9F13eac63DF89EC16ef6e7E25DdE7;
address constant CL_DAI_USD = 0xc5C8E77B397E531B8EC06BFb0048328B30E9eCfB;
address constant CL_CRVUSD_USD = 0x0a32255dd4BB6177C994bAAc73E0606fDD568f66;

function run() external {
IPoolManager manager = IPoolManager(vm.envAddress("POOL_MANAGER"));
address oracle0 = vm.envAddress("ORACLE0");
address stable0 = vm.envAddress("STABLE0");
uint8 stable0Decimals = uint8(vm.envUint("STABLE0_DECIMALS"));
address oracle1 = vm.envAddress("ORACLE1");
address stable1 = vm.envAddress("STABLE1");
uint8 stable1Decimals = uint8(vm.envUint("STABLE1_DECIMALS"));
address poolManager = vm.envOr(
"POOL_MANAGER",
ARBITRUM_POOL_MANAGER
);
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(deployerKey);

vm.startBroadcast(deployerKey);

uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG);
bytes memory constructorArgs =
abi.encode(manager, oracle0, stable0, stable0Decimals, oracle1, stable1, stable1Decimals);

(address hookAddress, bytes32 salt) =
HookMiner.find(CREATE2_DEPLOYER, flags, type(OscillonHook).creationCode, constructorArgs);

vm.broadcast();
OscillonHook deployed = new OscillonHook{salt: salt}(
manager,
IChainlinkOracle(oracle0),
stable0,
stable0Decimals,
IChainlinkOracle(oracle1),
stable1,
stable1Decimals

(address hookAddr, bytes32 salt) = HookMiner.find(
CREATE2_DEPLOYER,
flags,
type(OscillonHook).creationCode,
abi.encode(IPoolManager(poolManager))
);

OscillonHook hook = new OscillonHook{salt: salt}(
IPoolManager(poolManager)
);
require(address(deployed) == hookAddress, "hook address mismatch");

require(address(hook) == hookAddr, "Hook address mismatch");
console2.log("OscillonHook deployed at:", address(hook));
console2.log("PoolManager:", poolManager);

_registerSortedPool(
hook,
USDC,
USDT,
CL_USDC_USD,
CL_USDT_USD,
6,
6,
"USDC/USDT"
);
_registerSortedPool(
hook,
USDC,
DAI,
CL_USDC_USD,
CL_DAI_USD,
6,
18,
"USDC/DAI"
);
_registerSortedPool(
hook,
USDT,
DAI,
CL_USDT_USD,
CL_DAI_USD,
6,
18,
"USDT/DAI"
);
_registerSortedPool(
hook,
USDC,
CRVUSD,
CL_USDC_USD,
CL_CRVUSD_USD,
6,
18,
"USDC/crvUSD"
);

vm.stopBroadcast();

console2.log("=== OSCILLON DEPLOYMENT COMPLETE ===");
console2.log("Hook address :", address(hook));
console2.log("Owner :", deployer);
console2.log("Pools active : 4");
console2.log("Next step : seed each pool with liquidity");
console2.log("Then : verify on Arbiscan and submit to 1inch");
}

function _registerSortedPool(
OscillonHook hook,
address tokenA,
address tokenB,
address oracleA,
address oracleB,
uint8 decimalsA,
uint8 decimalsB,
string memory label
) internal {
PoolKey memory key;
if (tokenA < tokenB) {
key = PoolKey({
currency0: Currency.wrap(tokenA),
currency1: Currency.wrap(tokenB),
fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
tickSpacing: 1,
hooks: hook
});
hook.registerPool(key, oracleA, oracleB, decimalsA, decimalsB);
} else {
key = PoolKey({
currency0: Currency.wrap(tokenB),
currency1: Currency.wrap(tokenA),
fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
tickSpacing: 1,
hooks: hook
});
hook.registerPool(key, oracleB, oracleA, decimalsB, decimalsA);
}

console2.log("Registered:", label);
}
}
Loading
Loading