Skip to content

Commit 165ca25

Browse files
authored
allow the base hook to receive eth, so that it can receive the taken eth before distributing rewards (#1314)
* allow the base hook to receive eth, so that it can receive the taken eth before distributing rewards * added more tests for eth backed coins * bumped hooks * version bump * version bump
1 parent f88477e commit 165ca25

File tree

7 files changed

+91
-23
lines changed

7 files changed

+91
-23
lines changed

docs/pages/changelogs/coins.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
### Patch Changes
77

88
- [522a7c33](https://github.com/ourzora/zora-protocol/commit/522a7c33): Update LICENSE for coins
9+
- [5c561b01](https://github.com/ourzora/zora-protocol/commit/5c561b01): Fixed bug where hooks could not receive taken eth for paying out rewards
910

1011
## 1.1.1
1112

packages/coins/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Patch Changes
66

77
- 522a7c33: Update LICENSE for coins
8+
- 5c561b01: Fixed bug where hooks could not receive taken eth for paying out rewards
89

910
## 1.1.1
1011

packages/coins/addresses/8453.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
"COIN_V3_IMPL": "0x45Bf86430af7CD071Ea23aE52325A78C8d12aD5a",
44
"COIN_V4_IMPL": "0xca72309AaF706d290E08608b1Af47943902f69b2",
55
"COIN_VERSION": "1.1.0",
6-
"CREATOR_COIN_HOOK": "0x5e5D19d22c85A4aef7C1FdF25fB22A5a38f71040",
7-
"CREATOR_COIN_HOOK_SALT": "0x0000000000000000000000000000000000000000000000000000000000002989",
6+
"CREATOR_COIN_HOOK": "0xd61A675F8a0c67A73DC3B54FB7318B4D91409040",
7+
"CREATOR_COIN_HOOK_SALT": "0x0000000000000000000000000000000000000000000000000000000000000ae8",
88
"CREATOR_COIN_IMPL": "0x88CC4E08C7608723f3E44e17aC669Fb43b6A8313",
99
"HOOK_UPGRADE_GATE": "0xD88f6BdD765313CaFA5888C177c325E2C3AbF2D2",
1010
"ZORA_FACTORY": "0x777777751622c0d3258f214F9DF38E35BF45baF3",
11-
"ZORA_FACTORY_IMPL": "0x2dF69e41e848caA24aC3Dd1112C9DDC4F9E728F8",
12-
"ZORA_V4_COIN_HOOK": "0x5bF219b3Cc11E3f6Dd8dc8fC89D7d1deB0431040",
13-
"ZORA_V4_COIN_HOOK_SALT": "0x00000000000000000000000000000000000000000000000000000000000003c3"
11+
"ZORA_FACTORY_IMPL": "0x0e2ea62E5377D46FeF114A60AfBE3d5eA7490577",
12+
"ZORA_V4_COIN_HOOK": "0x9ea932730A7787000042e34390B8E435dD839040",
13+
"ZORA_V4_COIN_HOOK_SALT": "0x0000000000000000000000000000000000000000000000000000000000002fa2"
1414
}

packages/coins/src/hooks/BaseZoraV4CoinHook.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,4 +372,6 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
372372

373373
newPoolKey = V4Liquidity.lockAndMigrate(poolManager, poolKey, poolCoin.positions, poolCoin.coin, newHook, additionalData);
374374
}
375+
376+
receive() external payable onlyPoolManager {}
375377
}

packages/coins/test/CoinUniV4.t.sol

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ contract CoinUniV4Test is BaseTest {
6363
uint256 deadline = block.timestamp + 20;
6464
router.execute(commands, inputs, deadline);
6565

66-
feeState = FeeEstimatorHook(address(contentCoinHook)).getFeeState();
66+
feeState = FeeEstimatorHook(payable(address(contentCoinHook))).getFeeState();
6767

6868
vm.revertToState(snapshot);
6969
}
@@ -80,8 +80,8 @@ contract CoinUniV4Test is BaseTest {
8080
uint256 deadline = block.timestamp + 20;
8181
router.execute(commands, inputs, deadline);
8282

83-
delta = FeeEstimatorHook(address(contentCoinHook)).getFeeState().lastDelta;
84-
swapParams = FeeEstimatorHook(address(contentCoinHook)).getFeeState().lastSwapParams;
83+
delta = FeeEstimatorHook(payable(address(contentCoinHook))).getFeeState().lastDelta;
84+
swapParams = FeeEstimatorHook(payable(address(contentCoinHook))).getFeeState().lastSwapParams;
8585

8686
sqrtPriceX96 = PoolStateReader.getSqrtPriceX96(coinV4.getPoolKey(), poolManager);
8787

@@ -293,6 +293,62 @@ contract CoinUniV4Test is BaseTest {
293293
assertApproxEqAbs(mockERC20A.balanceOf(coinV4.protocolRewardRecipient()), totalRewards.protocol, 10, "protocol reward currency");
294294
}
295295

296+
function test_distributesMarketRewardsInEth() public {
297+
uint64 amountIn = 0.1 ether;
298+
299+
// Use address(0) as currency to price the coin in ETH
300+
address currency = address(0);
301+
bytes32 salt = keccak256(abi.encodePacked("eth-rewards-test"));
302+
_deployV4Coin(currency, address(0), salt);
303+
304+
address trader = makeAddr("trader");
305+
306+
// Give trader ETH
307+
vm.deal(trader, amountIn);
308+
309+
// Record initial ETH balance of payout recipient
310+
uint256 initialPayoutBalance = coinV4.payoutRecipient().balance;
311+
312+
// Swap ETH for coin
313+
_swapSomeCurrencyForCoin(coinV4, currency, amountIn, trader);
314+
315+
// Verify that rewards were paid out in ETH
316+
assertGt(coinV4.payoutRecipient().balance, initialPayoutBalance, "backing reward should be paid in ETH");
317+
}
318+
319+
function test_canSwapEthForCoin(uint128 amountIn) public {
320+
vm.assume(amountIn > 0.00001 ether);
321+
vm.assume(amountIn < 1 ether);
322+
323+
// Use address(0) as currency to price the coin in ETH
324+
address currency = address(0);
325+
bytes32 salt = keccak256(abi.encodePacked("eth-coin-test"));
326+
_deployV4Coin(currency, address(0), salt);
327+
328+
address trader = makeAddr("trader");
329+
330+
// Give trader ETH
331+
vm.deal(trader, amountIn);
332+
333+
uint256 initialEthBalance = trader.balance;
334+
335+
// Swap ETH for coin
336+
_swapSomeCurrencyForCoin(coinV4, currency, amountIn, trader);
337+
338+
// Verify the swap worked
339+
assertEq(trader.balance, initialEthBalance - amountIn, "trader should have spent ETH");
340+
assertGt(coinV4.balanceOf(trader), 0, "trader should have received coin");
341+
342+
// Now swap some coin back for ETH
343+
uint128 coinBalance = uint128(coinV4.balanceOf(trader));
344+
345+
_swapSomeCoinForCurrency(coinV4, currency, coinBalance, trader);
346+
347+
// Verify the reverse swap worked
348+
assertEq(coinV4.balanceOf(trader), 0, "trader should have no coins left");
349+
assertGt(trader.balance, 0, "trader should have received ETH back");
350+
}
351+
296352
function test_swap_emitsCoinMarketRewardsV4(uint64 amountIn) public {
297353
vm.assume(amountIn > 0.00001 ether);
298354
address currency = address(mockERC20A);

packages/coins/test/Upgrades.t.sol

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,22 +225,22 @@ contract UpgradesTest is BaseTest, CoinsDeployerBase {
225225
}
226226

227227
function test_canUpgradeBrokenContentCoinAndSwap() public {
228-
vm.createSelectFork("base", 31835069);
228+
vm.createSelectFork("base", 32613149);
229229

230230
address trader = 0xf69fEc6d858c77e969509843852178bd24CAd2B6;
231231

232-
address contentCoin = 0x4E93A01c90f812284F71291a8d1415a904957156;
232+
address contentCoin = 0xB9799C839818bF50240CE683363D00c43a2E23b8;
233233

234234
address creatorCoin = ICoinV4(contentCoin).currency();
235235

236-
address existingHook = 0xd3D133469ADC85e01A4887404D8AC12d630e9040;
237-
238-
uint256 amountIn = IERC20(creatorCoin).balanceOf(trader);
236+
uint256 amountIn = 0.000111 ether;
239237

240238
bytes memory creationCode = HooksDeployment.contentCoinCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
241239

242240
(IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
243241

242+
address existingHook = address(ICoinV4(contentCoin).hooks());
243+
244244
address[] memory baseImpls = new address[](1);
245245
baseImpls[0] = existingHook;
246246

packages/coins/test/utils/BaseTest.sol

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,15 @@ contract BaseTest is Test, ContractAddresses {
206206
);
207207

208208
vm.startPrank(trader);
209-
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), currency, amountIn, uint48(block.timestamp + 1 days));
209+
if (currency != address(0)) {
210+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), currency, amountIn, uint48(block.timestamp + 1 days));
211+
}
212+
213+
uint256 value = currency == address(0) ? amountIn : 0;
210214

211215
// Execute the swap
212216
uint256 deadline = block.timestamp + 20;
213-
router.execute(commands, inputs, deadline);
217+
router.execute{value: value}(commands, inputs, deadline);
214218

215219
vm.stopPrank();
216220
}
@@ -269,18 +273,22 @@ contract BaseTest is Test, ContractAddresses {
269273
(bytes32 contentCoinSalt, bytes32 creatorCoinSalt) = getSalts(trustedMessageSenders);
270274

271275
contentCoinHook = ContentCoinHook(
272-
address(
273-
HooksDeployment.deployHookWithSalt(
274-
HooksDeployment.contentCoinCreationCode(V4_POOL_MANAGER, address(factory), trustedMessageSenders, address(hookUpgradeGate)),
275-
contentCoinSalt
276+
payable(
277+
address(
278+
HooksDeployment.deployHookWithSalt(
279+
HooksDeployment.contentCoinCreationCode(V4_POOL_MANAGER, address(factory), trustedMessageSenders, address(hookUpgradeGate)),
280+
contentCoinSalt
281+
)
276282
)
277283
)
278284
);
279285
creatorCoinHook = CreatorCoinHook(
280-
address(
281-
HooksDeployment.deployHookWithSalt(
282-
HooksDeployment.creatorCoinHookCreationCode(V4_POOL_MANAGER, address(factory), trustedMessageSenders, address(hookUpgradeGate)),
283-
creatorCoinSalt
286+
payable(
287+
address(
288+
HooksDeployment.deployHookWithSalt(
289+
HooksDeployment.creatorCoinHookCreationCode(V4_POOL_MANAGER, address(factory), trustedMessageSenders, address(hookUpgradeGate)),
290+
creatorCoinSalt
291+
)
284292
)
285293
)
286294
);

0 commit comments

Comments
 (0)