Skip to content

Commit f104561

Browse files
committed
libs and CLAMM
1 parent 7d8ceef commit f104561

File tree

9 files changed

+625
-57
lines changed

9 files changed

+625
-57
lines changed

script/Counter.s.sol

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/CLAMM.sol

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
// Layout of Contract:
4+
// version
5+
// imports
6+
// interfaces, libraries, contracts
7+
// errors
8+
// Type declarations
9+
// State variables
10+
// Events
11+
// Modifiers
12+
// Functions
13+
14+
// Layout of Functions:
15+
// constructor
16+
// receive function (if exists)
17+
// fallback function (if exists)
18+
// external
19+
// public
20+
// internal
21+
// private
22+
// view & pure functions
23+
24+
/**
25+
* @title CLAMM - Concentrated Liquidity Automated Market Maker
26+
* @author Yug Agarwal
27+
* @notice This contract is a simplified version of Uniswap V3's CLAMM
28+
* @dev It omits Factory, Price Oracle, Flash Swap, NFT, Solidity advanced math libraries, Callbacks
29+
*/
30+
pragma solidity 0.8.19;
31+
32+
import {Tick} from "./lib/Tick.sol";
33+
import {TickMath} from "./lib/TickMath.sol";
34+
import {Position} from "./lib/Position.sol";
35+
import {SafeCast} from "./lib/SafeCast.sol";
36+
import {IERC20} from "./interfaces/IERC20.sol";
37+
38+
contract CLAMM {
39+
using SafeCast for int256;
40+
using Position for mapping(bytes32 => Position.Info);
41+
using Position for Position.Info;
42+
using Tick for mapping(int24 => Tick.Info);
43+
44+
error CLAMM__AlreadyInitialized();
45+
error CLAMM__Locked();
46+
error CLAMM__AmountInvalid();
47+
error CLAMM__tickLowerGreaterThanOrEqualToUpper();
48+
error CLAMM__tickLowerLessThanMin();
49+
error CLAMM__tickUpperGreaterThanMax();
50+
51+
address public immutable i_token0;
52+
address public immutable i_token1;
53+
uint24 public immutable i_fee;
54+
int24 public immutable i_tickSpacing;
55+
uint128 public immutable i_maxLiquidityPerTick;
56+
57+
struct Slot0 {
58+
uint160 sqrtPriceX96;
59+
int24 tick;
60+
bool unlocked;
61+
}
62+
63+
struct ModifyPositionParams {
64+
address owner;
65+
int24 tickLower;
66+
int24 tickUpper;
67+
int128 liquidityDelta;
68+
}
69+
70+
Slot0 public slot0;
71+
mapping(int24 => Tick.Info) public ticks;
72+
mapping(bytes32 => Position.Info) public positions;
73+
74+
event Initialize(uint160 indexed sqrtPriceX96, int24 indexed tick);
75+
76+
modifier lock() {
77+
if (slot0.unlocked == false) {
78+
revert CLAMM__AlreadyInitialized();
79+
}
80+
slot0.unlocked = false;
81+
_;
82+
slot0.unlocked = true;
83+
}
84+
85+
/**
86+
*
87+
* @param _token0 first token in the pair
88+
* @param _token1 second token in the pair
89+
* @param _fee swap fees
90+
* @param _tickSpacing tick spacing
91+
* @dev tick spacing is the minimum distance between ticks
92+
*/
93+
constructor(address _token0, address _token1, uint24 _fee, int24 _tickSpacing) {
94+
i_token0 = _token0;
95+
i_token1 = _token1;
96+
i_fee = _fee;
97+
i_tickSpacing = _tickSpacing;
98+
i_maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(_tickSpacing);
99+
}
100+
101+
function initialize(uint160 sqrtPriceX96) external {
102+
if (slot0.sqrtPriceX96 == 0) {
103+
revert CLAMM__Locked();
104+
}
105+
106+
int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96);
107+
108+
slot0 = Slot0({sqrtPriceX96: sqrtPriceX96, tick: tick, unlocked: true});
109+
110+
emit Initialize(sqrtPriceX96, tick);
111+
}
112+
113+
function mint(address recipient, int24 tickLower, int24 tickUpper, uint128 amount)
114+
external
115+
lock
116+
returns (uint256 amount0, uint256 amount1)
117+
{
118+
// mint logic
119+
if (amount <= 0) revert CLAMM__AmountInvalid();
120+
(, int256 amount0Int, int256 amount1Int) = _modifyPosition(
121+
ModifyPositionParams({
122+
owner: recipient,
123+
tickLower: tickLower,
124+
tickUpper: tickUpper,
125+
liquidityDelta: int256(uint256(amount)).toInt128()
126+
})
127+
);
128+
129+
amount0 = uint256(amount0Int);
130+
amount1 = uint256(amount1Int);
131+
132+
if (amount0 > 0) {
133+
IERC20(i_token0).transferFrom(msg.sender, address(this), amount0);
134+
}
135+
if (amount1 > 0) {
136+
IERC20(i_token1).transferFrom(msg.sender, address(this), amount1);
137+
}
138+
}
139+
140+
function _updatePostion(address owner, int24 tickUpper, int24 tickLower, int128 liquidityDelta, int24 tick)
141+
private
142+
returns (Position.Info storage position)
143+
{
144+
position = positions.get(owner, tickLower, tickUpper);
145+
146+
// Fees Update
147+
uint256 _feeGrowthGlobal0X128 = 0;
148+
uint256 _feeGrowthGlobal1X128 = 0;
149+
150+
position.update(liquidityDelta, 0, 0);
151+
}
152+
153+
function _modifyPosition(ModifyPositionParams memory params)
154+
private
155+
returns (Position.Info storage position, int256 amount0, int256 amount1)
156+
{
157+
checkTicks(params.tickLower, params.tickUpper);
158+
Slot0 memory _slot0 = slot0;
159+
160+
_updatePostion(
161+
params.owner,
162+
params.tickUpper,
163+
params.tickLower,
164+
params.liquidityDelta, // Amount of liquidity to add or remove
165+
_slot0.tick
166+
);
167+
168+
return (positions[bytes32(0)], 0, 0);
169+
}
170+
171+
function checkTicks(int24 tickLower, int24 tickUpper) private pure {
172+
if (tickLower >= tickUpper) {
173+
revert CLAMM__tickLowerGreaterThanOrEqualToUpper();
174+
}
175+
if (tickLower < TickMath.MIN_TICK) {
176+
revert CLAMM__tickLowerLessThanMin();
177+
}
178+
if (tickUpper > TickMath.MAX_TICK) {
179+
revert CLAMM__tickUpperGreaterThanMax();
180+
}
181+
}
182+
}

src/Counter.sol

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/interfaces/IERC20.sol

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
pragma solidity 0.8.19;
3+
4+
interface IERC20 {
5+
function totalSupply() external view returns (uint256);
6+
function balanceOf(address account) external view returns (uint256);
7+
function transfer(address recipient, uint256 amount)
8+
external
9+
returns (bool);
10+
function allowance(address owner, address spender)
11+
external
12+
view
13+
returns (uint256);
14+
function approve(address spender, uint256 amount) external returns (bool);
15+
function transferFrom(address sender, address recipient, uint256 amount)
16+
external
17+
returns (bool);
18+
19+
event Transfer(address indexed from, address indexed to, uint256 value);
20+
event Approval(
21+
address indexed owner, address indexed spender, uint256 value
22+
);
23+
}

src/lib/Position.sol

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity 0.8.19;
4+
5+
library Position {
6+
error Position__NoLiquidity();
7+
8+
struct Info {
9+
// Amount of liquidity in the position
10+
uint128 liquidity;
11+
12+
// fee growth per uint of liquidity as of the last update to liquidity or fees owed
13+
uint256 feeGrowthInside0LastX128;
14+
uint256 feeGrowthInside1LastX128;
15+
16+
//the fees owed to the position owner in token0/token1
17+
uint256 tokensOwed0;
18+
uint256 tokensOwed1;
19+
}
20+
21+
function get(
22+
mapping(bytes32 => Info) storage self,
23+
address owner,
24+
int24 tickLower,
25+
int24 tickUpper
26+
) internal view returns (Info storage position) {
27+
position =
28+
self[keccak256(abi.encodePacked(owner, tickLower, tickUpper))];
29+
}
30+
31+
function update(
32+
Info storage self,
33+
int128 liquidityDelta,
34+
uint256 feeGrowthInside0LastX128,
35+
uint256 feeGrowthInside1LastX128
36+
) internal {
37+
Info memory _self = self;
38+
39+
if(liquidityDelta == 0) {
40+
if(_self.liquidity == 0) revert Position__NoLiquidity();
41+
}
42+
if(liquidityDelta != 0) {
43+
self.liquidity = liquidityDelta < 0
44+
? _self.liquidity - uint128(-liquidityDelta)
45+
: _self.liquidity + uint128(liquidityDelta);
46+
}
47+
}
48+
}

src/lib/SafeCast.sol

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.19;
3+
4+
// Copied from https://github.com/Uniswap/v4-core
5+
6+
/// @title Safe casting methods
7+
/// @notice Contains methods for safely casting between types
8+
library SafeCast {
9+
/// @notice Cast a uint256 to a uint160, revert on overflow
10+
/// @param y The uint256 to be downcasted
11+
/// @return z The downcasted integer, now type uint160
12+
function toUint160(uint256 y) internal pure returns (uint160 z) {
13+
require((z = uint160(y)) == y);
14+
}
15+
16+
/// @notice Cast a int256 to a int128, revert on overflow or underflow
17+
/// @param y The int256 to be downcasted
18+
/// @return z The downcasted integer, now type int128
19+
function toInt128(int256 y) internal pure returns (int128 z) {
20+
require((z = int128(y)) == y);
21+
}
22+
23+
/// @notice Cast a uint256 to a int256, revert on overflow
24+
/// @param y The uint256 to be casted
25+
/// @return z The casted integer, now type int256
26+
function toInt256(uint256 y) internal pure returns (int256 z) {
27+
require(y <= uint256(type(int256).max));
28+
z = int256(y);
29+
}
30+
31+
/// @notice Cast a uint256 to a int128, revert on overflow
32+
/// @param y The uint256 to be downcasted
33+
/// @return z The downcasted integer, now type int128
34+
function toInt128(uint256 y) internal pure returns (int128 z) {
35+
require(y <= uint128(type(int128).max));
36+
z = int128(int256(y));
37+
}
38+
}

0 commit comments

Comments
 (0)