Skip to content

Commit b40ee50

Browse files
committed
(fix) (openai/gpt-5.5, reviewed T, tested T) derive pullLp repo from registered pool
1 parent 1bd9679 commit b40ee50

3 files changed

Lines changed: 109 additions & 9 deletions

File tree

contracts/src/RIKLauncher.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ contract RIKLauncher {
6161
// send to doppler
6262
(asset,,,,) = airlock.create(p);
6363
marketOf[repoId] = asset;
64-
splitter.registerMarket(asset, repoId);
6564
repoOf[asset] = repoId;
65+
splitter.registerMarket(asset, repoId);
6666

6767
emit MarketLaunched(repoId, asset, msg.sender);
6868
}

contracts/src/RIKRoyaltySplitter.sol

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ interface IMulticurvePool {
1717
function token1() external view returns (address);
1818
}
1919

20+
interface IMulticurveFeeCollector {
21+
function collectFees() external;
22+
}
23+
2024
contract RIKRoyaltySplitter is ReentrancyGuard {
2125
using SafeERC20 for IERC20;
2226

@@ -56,16 +60,33 @@ contract RIKRoyaltySplitter is ReentrancyGuard {
5660
}
5761

5862
function _repoOfPool(IMulticurvePool pool) internal view returns (uint256) {
59-
// Doppler's DERC20 is the side of the pool which is NOT the numeraire
60-
// both repoOf(t0) and repoOf(t1) are checked; one and only one is non-zero.
63+
// Doppler's DERC20 is the registered side; the numeraire side is zero.
6164
uint256 r0 = repoOf[pool.token0()];
6265
uint256 r1 = repoOf[pool.token1()];
6366
if (r0 == 0 && r1 == 0) revert UnknownPool();
6467
return r0 == 0 ? r1 : r0;
6568
}
6669

6770
function pullLp(IMulticurvePool pool) external nonReentrant {
68-
_repoOfPool(pool);
71+
uint256 repoId = _repoOfPool(pool);
72+
address token0 = pool.token0();
73+
address token1 = pool.token1();
74+
uint256 before0 = IERC20(token0).balanceOf(address(this));
75+
uint256 before1 = IERC20(token1).balanceOf(address(this));
76+
77+
IMulticurveFeeCollector(address(pool)).collectFees();
78+
79+
uint256 delta0 = IERC20(token0).balanceOf(address(this)) - before0;
80+
if (delta0 != 0) {
81+
claimable[repoId][token0] += delta0;
82+
emit Accrued(repoId, token0, delta0);
83+
}
84+
85+
uint256 delta1 = IERC20(token1).balanceOf(address(this)) - before1;
86+
if (delta1 != 0) {
87+
claimable[repoId][token1] += delta1;
88+
emit Accrued(repoId, token1, delta1);
89+
}
6990
}
7091

7192
/// @notice Pull all integrator fees the Airlock owes us in 'token', route them to repoId's bucket.

contracts/test/RIKRoyaltySplitter.t.sol

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,28 @@ contract SplitterMockRegistry {
7575
contract SplitterMockPool is IMulticurvePool {
7676
address public token0;
7777
address public token1;
78+
uint256 public fees0;
79+
uint256 public fees1;
7880

7981
constructor(address _token0, address _token1) {
8082
token0 = _token0;
8183
token1 = _token1;
8284
}
85+
86+
function setFees(uint256 _fees0, uint256 _fees1) external {
87+
fees0 = _fees0;
88+
fees1 = _fees1;
89+
}
90+
91+
function collectFees() external {
92+
uint256 amount0 = fees0;
93+
uint256 amount1 = fees1;
94+
fees0 = 0;
95+
fees1 = 0;
96+
97+
if (amount0 != 0) require(IERC20(token0).transfer(msg.sender, amount0), "transfer0");
98+
if (amount1 != 0) require(IERC20(token1).transfer(msg.sender, amount1), "transfer1");
99+
}
83100
}
84101

85102
contract RIKRoyaltySplitter_T is Test {
@@ -101,6 +118,13 @@ contract RIKRoyaltySplitter_T is Test {
101118
registry.setOwner(repoId, owner);
102119
}
103120

121+
function test_strangerCannotRegister() public {
122+
address asset = address(0xA55E7);
123+
124+
vm.expectRevert(RIKRoyaltySplitter.OnlyLauncher.selector);
125+
splitter.registerMarket(asset, 42);
126+
}
127+
104128
function test_RegisterMarketOnlyLauncher() public {
105129
address asset = address(0xA55E7);
106130

@@ -165,14 +189,22 @@ contract RIKRoyaltySplitter_T is Test {
165189
}
166190

167191
function test_PullLpAcceptsRegisteredAssetOnEitherSide() public {
168-
address asset = address(0xA55E7);
169-
address numeraire = address(0xBEEF);
192+
SplitterMockERC20 asset = new SplitterMockERC20();
193+
SplitterMockERC20 numeraire = new SplitterMockERC20();
170194

171195
vm.prank(launcher);
172-
splitter.registerMarket(asset, repoId);
196+
splitter.registerMarket(address(asset), repoId);
173197

174-
splitter.pullLp(new SplitterMockPool(asset, numeraire));
175-
splitter.pullLp(new SplitterMockPool(numeraire, asset));
198+
splitter.pullLp(new SplitterMockPool(address(asset), address(numeraire)));
199+
splitter.pullLp(new SplitterMockPool(address(numeraire), address(asset)));
200+
}
201+
202+
function test_pullLpForUnknownPoolReverts() public {
203+
SplitterMockPool pool = new SplitterMockPool(address(0x1111), address(0x2222));
204+
205+
vm.expectRevert(RIKRoyaltySplitter.UnknownPool.selector);
206+
207+
splitter.pullLp(pool);
176208
}
177209

178210
function test_PullLpRejectsUnknownPool() public {
@@ -183,6 +215,53 @@ contract RIKRoyaltySplitter_T is Test {
183215
splitter.pullLp(pool);
184216
}
185217

218+
function test_pullLpDerivesRepoFromPool() public {
219+
uint256 otherRepoId = 22223;
220+
SplitterMockERC20 numeraire = new SplitterMockERC20();
221+
SplitterMockPool pool = new SplitterMockPool(address(token), address(numeraire));
222+
uint256 amount = 3 ether;
223+
224+
vm.prank(launcher);
225+
splitter.registerMarket(address(token), repoId);
226+
227+
numeraire.mint(address(pool), amount);
228+
pool.setFees(0, amount);
229+
230+
splitter.pullLp(pool);
231+
232+
assertEq(splitter.claimable(repoId, address(numeraire)), amount);
233+
assertEq(splitter.claimable(otherRepoId, address(numeraire)), 0);
234+
}
235+
236+
function test_twoReposIndependent() public {
237+
uint256 repoA = 11112;
238+
uint256 repoB = 22223;
239+
SplitterMockERC20 assetA = new SplitterMockERC20();
240+
SplitterMockERC20 assetB = new SplitterMockERC20();
241+
SplitterMockERC20 weth = new SplitterMockERC20();
242+
SplitterMockPool poolA = new SplitterMockPool(address(assetA), address(weth));
243+
SplitterMockPool poolB = new SplitterMockPool(address(assetB), address(weth));
244+
245+
vm.startPrank(launcher);
246+
splitter.registerMarket(address(assetA), repoA);
247+
splitter.registerMarket(address(assetB), repoB);
248+
vm.stopPrank();
249+
250+
weth.mint(address(poolA), 3 ether);
251+
weth.mint(address(poolB), 7 ether);
252+
poolA.setFees(0, 3 ether);
253+
poolB.setFees(0, 7 ether);
254+
255+
splitter.pullLp(poolA);
256+
splitter.pullLp(poolB);
257+
258+
uint256 claimableA = splitter.claimable(repoA, address(weth));
259+
uint256 claimableB = splitter.claimable(repoB, address(weth));
260+
assertGt(claimableA, 0);
261+
assertGt(claimableB, 0);
262+
assertNotEq(claimableA, claimableB);
263+
}
264+
186265
function _fundFees(uint256 amount) internal {
187266
token.mint(address(airlock), amount);
188267
airlock.setFees(address(splitter), address(token), amount);

0 commit comments

Comments
 (0)