@zoralabs/coins 2.2.1 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$js.log +125 -106
- package/CHANGELOG.md +50 -5
- package/README.md +5 -0
- package/abis/AddressConstants.json +7 -0
- package/abis/BaseCoin.json +0 -5
- package/abis/BaseTest.json +62 -0
- package/abis/BuySupplyWithV4SwapHook.json +429 -0
- package/abis/ContentCoin.json +0 -5
- package/abis/CreatorCoin.json +0 -5
- package/abis/FeeEstimatorHook.json +94 -1
- package/abis/IUniswapV4Router04.json +484 -0
- package/abis/IUpgradeableDestinationV4HookWithUpdateableFee.json +95 -0
- package/abis/IZoraFactory.json +69 -0
- package/abis/MockAirlock.json +39 -0
- package/abis/SimpleERC20.json +326 -0
- package/abis/ZoraFactoryImpl.json +69 -0
- package/abis/ZoraV4CoinHook.json +94 -1
- package/addresses/8453.json +8 -10
- package/audits/report-cantinacode-zora-0827.pdf +3498 -4
- package/audits/report-cantinacode-zora-1021.pdf +0 -0
- package/dist/index.cjs +161 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +160 -21
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +259 -40
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/foundry.toml +3 -3
- package/package/wagmiGenerated.ts +160 -21
- package/package.json +1 -1
- package/script/DeployPostDeploymentHooks.s.sol +1 -3
- package/script/TestBackingCoinSwap.s.sol +0 -2
- package/script/TestV4Swap.s.sol +0 -2
- package/src/BaseCoin.sol +4 -12
- package/src/ContentCoin.sol +3 -4
- package/src/CreatorCoin.sol +8 -10
- package/src/ZoraFactoryImpl.sol +115 -83
- package/src/deployment/CoinsDeployerBase.sol +9 -8
- package/src/hook-registry/ZoraHookRegistry.sol +4 -0
- package/src/hooks/ZoraV4CoinHook.sol +66 -9
- package/src/hooks/deployment/BuySupplyWithV4SwapHook.sol +310 -0
- package/src/interfaces/IUpgradeableV4Hook.sol +18 -0
- package/src/interfaces/IZoraFactory.sol +21 -2
- package/src/libs/CoinConstants.sol +51 -8
- package/src/libs/CoinDopplerMultiCurve.sol +11 -11
- package/src/libs/CoinRewardsV4.sol +26 -33
- package/src/libs/CoinSetup.sol +2 -9
- package/src/libs/DopplerMath.sol +2 -2
- package/src/libs/V4Liquidity.sol +79 -15
- package/src/utils/AutoSwapper.sol +1 -1
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/BuySupplyWithV4SwapHook.t.sol +509 -0
- package/test/Coin.t.sol +26 -14
- package/test/CoinRewardsV4.t.sol +33 -0
- package/test/CoinUniV4.t.sol +3 -5
- package/test/ContentCoinRewards.t.sol +44 -3
- package/test/CreatorCoin.t.sol +54 -33
- package/test/CreatorCoinRewards.t.sol +1 -3
- package/test/DeploymentHooks.t.sol +54 -2
- package/test/Factory.t.sol +3 -3
- package/test/LiquidityMigration.t.sol +145 -7
- package/test/MultiOwnable.t.sol +4 -4
- package/test/Upgrades.t.sol +26 -17
- package/test/V4Liquidity.t.sol +178 -0
- package/test/ZoraHookRegistry.t.sol +19 -9
- package/test/mocks/MockAirlock.sol +22 -0
- package/test/mocks/SimpleERC20.sol +8 -0
- package/test/utils/BaseTest.sol +155 -3
- package/test/utils/RewardTestHelpers.sol +4 -4
- package/test/utils/hookmate/README.md +50 -0
- package/test/utils/hookmate/artifacts/DeployHelper.sol +20 -0
- package/test/utils/hookmate/artifacts/Permit2.sol +16 -0
- package/test/utils/hookmate/artifacts/UniversalRouter.sol +29 -0
- package/test/utils/hookmate/artifacts/V4PoolManager.sol +17 -0
- package/test/utils/hookmate/artifacts/V4PositionManager.sol +23 -0
- package/test/utils/hookmate/artifacts/V4Quoter.sol +17 -0
- package/test/utils/hookmate/artifacts/V4Router.sol +18 -0
- package/test/utils/hookmate/constants/AddressConstants.sol +193 -0
- package/test/utils/hookmate/interfaces/router/IUniswapV4Router04.sol +173 -0
- package/test/utils/hookmate/interfaces/router/PathKey.sol +34 -0
- package/test/utils/hookmate/test/utils/SwapFeeEventAsserter.sol +24 -0
- package/wagmi.config.ts +1 -1
- package/abis/CoinConstants.json +0 -54
- package/abis/CoinRewardsV4.json +0 -67
- package/src/libs/CreatorCoinConstants.sol +0 -15
- package/src/libs/MarketConstants.sol +0 -23
- package/src/utils/uniswap/BytesLib.sol +0 -35
- package/src/utils/uniswap/Path.sol +0 -31
- /package/abis/{VmContractHelper227.json → VmContractHelper239.json} +0 -0
package/test/Coin.t.sol
CHANGED
|
@@ -16,7 +16,7 @@ contract CoinTest is BaseTest {
|
|
|
16
16
|
using stdJson for string;
|
|
17
17
|
|
|
18
18
|
function setUp() public override {
|
|
19
|
-
super.
|
|
19
|
+
super.setUpNonForked();
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function test_contract_ierc165_support() public {
|
|
@@ -37,16 +37,16 @@ contract CoinTest is BaseTest {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function test_supply_constants() public {
|
|
40
|
-
assertEq(CoinConstants.MAX_TOTAL_SUPPLY, CoinConstants.
|
|
40
|
+
assertEq(CoinConstants.MAX_TOTAL_SUPPLY, CoinConstants.CONTENT_COIN_MARKET_SUPPLY + CoinConstants.CONTENT_COIN_INITIAL_CREATOR_SUPPLY);
|
|
41
41
|
|
|
42
42
|
assertEq(CoinConstants.MAX_TOTAL_SUPPLY, 1_000_000_000e18);
|
|
43
|
-
assertEq(CoinConstants.
|
|
44
|
-
assertEq(CoinConstants.
|
|
43
|
+
assertEq(CoinConstants.CONTENT_COIN_MARKET_SUPPLY, 990_000_000e18);
|
|
44
|
+
assertEq(CoinConstants.CONTENT_COIN_INITIAL_CREATOR_SUPPLY, 10_000_000e18);
|
|
45
45
|
|
|
46
46
|
_deployV4Coin();
|
|
47
47
|
assertEq(coinV4.totalSupply(), CoinConstants.MAX_TOTAL_SUPPLY);
|
|
48
|
-
assertEq(coinV4.balanceOf(coinV4.payoutRecipient()), CoinConstants.
|
|
49
|
-
assertApproxEqAbs(coinV4.balanceOf(address(coinV4.poolManager())), CoinConstants.
|
|
48
|
+
assertEq(coinV4.balanceOf(coinV4.payoutRecipient()), CoinConstants.CONTENT_COIN_INITIAL_CREATOR_SUPPLY);
|
|
49
|
+
assertApproxEqAbs(coinV4.balanceOf(address(coinV4.poolManager())), CoinConstants.CONTENT_COIN_MARKET_SUPPLY, 1e18);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
function test_initialize_validation() public {
|
|
@@ -107,23 +107,35 @@ contract CoinTest is BaseTest {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
function test_burn() public {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
// Deploy a mock ERC20 currency
|
|
111
|
+
MockERC20 mockCurrency = new MockERC20("Mock Currency", "MOCK");
|
|
112
|
+
mockCurrency.mint(users.buyer, 1000 ether);
|
|
113
|
+
// Pool manager needs currency for liquidity operations
|
|
114
|
+
mockCurrency.mint(address(poolManager), 1000000 ether);
|
|
115
|
+
|
|
116
|
+
// Deploy coin with mock currency
|
|
117
|
+
coinV4 = ContentCoin(payable(address(_deployV4Coin(address(mockCurrency), address(0), bytes32(0)))));
|
|
114
118
|
|
|
115
|
-
|
|
119
|
+
// Approve with permit2 and swap
|
|
120
|
+
uint128 swapAmount = 1 ether;
|
|
121
|
+
vm.startPrank(users.buyer);
|
|
122
|
+
UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(mockCurrency), swapAmount, uint48(block.timestamp + 1 days));
|
|
123
|
+
vm.stopPrank();
|
|
124
|
+
|
|
125
|
+
_swapSomeCurrencyForCoin(coinV4, address(mockCurrency), swapAmount, users.buyer);
|
|
126
|
+
|
|
127
|
+
uint256 beforeBalance = coinV4.balanceOf(users.buyer);
|
|
116
128
|
uint256 beforeTotalSupply = coinV4.totalSupply();
|
|
117
129
|
|
|
118
130
|
uint256 burnAmount = beforeBalance / 2;
|
|
119
131
|
|
|
120
|
-
vm.prank(users.
|
|
132
|
+
vm.prank(users.buyer);
|
|
121
133
|
coinV4.burn(burnAmount);
|
|
122
134
|
|
|
123
|
-
uint256 afterBalance = coinV4.balanceOf(users.
|
|
135
|
+
uint256 afterBalance = coinV4.balanceOf(users.buyer);
|
|
124
136
|
uint256 afterTotalSupply = coinV4.totalSupply();
|
|
125
137
|
|
|
126
|
-
assertEq(beforeBalance - afterBalance, burnAmount, "
|
|
138
|
+
assertEq(beforeBalance - afterBalance, burnAmount, "buyer coin balance");
|
|
127
139
|
assertEq(beforeTotalSupply - afterTotalSupply, burnAmount, "coin total supply");
|
|
128
140
|
}
|
|
129
141
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import "forge-std/Test.sol";
|
|
5
|
+
import {CoinRewardsV4} from "../src/libs/CoinRewardsV4.sol";
|
|
6
|
+
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
|
|
7
|
+
|
|
8
|
+
contract CoinRewardsV4Test is Test {
|
|
9
|
+
function test_convertDeltaToPositiveUint128_success_with_valid_positive_values() public pure {
|
|
10
|
+
// Test with small positive value
|
|
11
|
+
int256 smallDelta = 1000;
|
|
12
|
+
uint128 result = CoinRewardsV4.convertDeltaToPositiveUint128(smallDelta);
|
|
13
|
+
assertEq(result, uint128(uint256(smallDelta)));
|
|
14
|
+
|
|
15
|
+
// Test with large but valid positive value (within uint128 range)
|
|
16
|
+
int256 largeDelta = int256(uint256(type(uint128).max));
|
|
17
|
+
uint128 result2 = CoinRewardsV4.convertDeltaToPositiveUint128(largeDelta);
|
|
18
|
+
assertEq(result2, type(uint128).max);
|
|
19
|
+
|
|
20
|
+
// Test with zero
|
|
21
|
+
int256 zeroDelta = 0;
|
|
22
|
+
uint128 result3 = CoinRewardsV4.convertDeltaToPositiveUint128(zeroDelta);
|
|
23
|
+
assertEq(result3, 0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// forge-config: default.allow_internal_expect_revert = true
|
|
27
|
+
function test_convertDeltaToPositiveUint128_edge_cases_and_reverts(int8 difference) public {
|
|
28
|
+
if (difference < 0) {
|
|
29
|
+
vm.expectRevert(SafeCast.SafeCastOverflow.selector);
|
|
30
|
+
}
|
|
31
|
+
CoinRewardsV4.convertDeltaToPositiveUint128(difference);
|
|
32
|
+
}
|
|
33
|
+
}
|
package/test/CoinUniV4.t.sol
CHANGED
|
@@ -17,7 +17,6 @@ import {LpPosition} from "../src/types/LpPosition.sol";
|
|
|
17
17
|
import {CoinCommon} from "../src/libs/CoinCommon.sol";
|
|
18
18
|
import {IZoraV4CoinHook} from "../src/interfaces/IZoraV4CoinHook.sol";
|
|
19
19
|
import {CoinConstants} from "../src/libs/CoinConstants.sol";
|
|
20
|
-
import {MarketConstants} from "../src/libs/MarketConstants.sol";
|
|
21
20
|
import {IMsgSender} from "../src/interfaces/IMsgSender.sol";
|
|
22
21
|
import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
|
|
23
22
|
import {toBalanceDelta, BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
|
|
@@ -39,9 +38,8 @@ contract CoinUniV4Test is BaseTest {
|
|
|
39
38
|
MockERC20 internal mockERC20B;
|
|
40
39
|
|
|
41
40
|
function setUp() public override {
|
|
42
|
-
super.
|
|
41
|
+
super.setUpNonForked();
|
|
43
42
|
|
|
44
|
-
quoter = IV4Quoter(V4_QUOTER);
|
|
45
43
|
mockERC20A = new MockERC20("MockERC20A", "MCKA");
|
|
46
44
|
mockERC20B = new MockERC20("MockERC20B", "MCKB");
|
|
47
45
|
|
|
@@ -106,8 +104,8 @@ contract CoinUniV4Test is BaseTest {
|
|
|
106
104
|
|
|
107
105
|
// Verify total supply equals maximum allowed
|
|
108
106
|
assertEq(coinV4.totalSupply(), CoinConstants.MAX_TOTAL_SUPPLY, "total supply");
|
|
109
|
-
assertApproxEqAbs(coinV4.balanceOf(address(coinV4.poolManager())),
|
|
110
|
-
assertEq(coinV4.balanceOf(coinV4.payoutRecipient()), CoinConstants.
|
|
107
|
+
assertApproxEqAbs(coinV4.balanceOf(address(coinV4.poolManager())), CoinConstants.CONTENT_COIN_MARKET_SUPPLY, 1000, "pool launch supply");
|
|
108
|
+
assertEq(coinV4.balanceOf(coinV4.payoutRecipient()), CoinConstants.CONTENT_COIN_INITIAL_CREATOR_SUPPLY, "creator launch reward");
|
|
111
109
|
}
|
|
112
110
|
|
|
113
111
|
function test_estimateLpFees() public {
|
|
@@ -21,9 +21,7 @@ contract ContentCoinRewardsTest is BaseTest {
|
|
|
21
21
|
address internal tradeReferrer;
|
|
22
22
|
|
|
23
23
|
function setUp() public override {
|
|
24
|
-
super.
|
|
25
|
-
|
|
26
|
-
deal(address(zoraToken), address(poolManager), 1_000_000_000e18);
|
|
24
|
+
super.setUpNonForked();
|
|
27
25
|
|
|
28
26
|
backingCreatorCoin = CreatorCoin(_deployCreatorCoin());
|
|
29
27
|
|
|
@@ -317,4 +315,47 @@ contract ContentCoinRewardsTest is BaseTest {
|
|
|
317
315
|
|
|
318
316
|
assertFalse(isLegacy, "Content coin should NOT be categorized as legacy creator coin");
|
|
319
317
|
}
|
|
318
|
+
|
|
319
|
+
/// @notice Test reward distribution when platform referrer rejects ETH - should fallback to protocol recipient
|
|
320
|
+
function test_rewards_platform_referrer_eth_rejection_fallback() public {
|
|
321
|
+
// Deploy ETH-rejecting contract to use as platform referrer
|
|
322
|
+
EthRejectingContract ethRejecter = new EthRejectingContract();
|
|
323
|
+
|
|
324
|
+
// Deploy ETH-backed coin (like test_distributesMarketRewardsInEth)
|
|
325
|
+
address currency = address(0); // ETH backing
|
|
326
|
+
bytes32 salt = keccak256(abi.encodePacked("eth-reject-test"));
|
|
327
|
+
_deployV4Coin(currency, address(ethRejecter), salt); // ethRejecter becomes platform referrer
|
|
328
|
+
|
|
329
|
+
// Fund trader with ETH
|
|
330
|
+
uint128 ethAmount = 0.1 ether;
|
|
331
|
+
address trader = makeAddr("trader");
|
|
332
|
+
deal(trader, ethAmount);
|
|
333
|
+
|
|
334
|
+
// Record initial ETH balances
|
|
335
|
+
uint256 initialProtocolEth = coinV4.protocolRewardRecipient().balance;
|
|
336
|
+
uint256 initialRejecterEth = address(ethRejecter).balance;
|
|
337
|
+
|
|
338
|
+
// Execute ETH -> Coin trade using the working BaseTest function
|
|
339
|
+
_swapSomeCurrencyForCoin(coinV4, currency, ethAmount, trader);
|
|
340
|
+
|
|
341
|
+
// Calculate ETH balance deltas
|
|
342
|
+
uint256 protocolEthDelta = coinV4.protocolRewardRecipient().balance - initialProtocolEth;
|
|
343
|
+
uint256 rejecterEthDelta = address(ethRejecter).balance - initialRejecterEth;
|
|
344
|
+
|
|
345
|
+
// Verify ETH-rejecting contract got no ETH
|
|
346
|
+
assertEq(rejecterEthDelta, 0, "Platform referrer should receive no ETH");
|
|
347
|
+
|
|
348
|
+
// Verify protocol got ETH (backup mechanism worked)
|
|
349
|
+
assertGt(protocolEthDelta, 0, "Protocol should receive backup ETH from failed platform referrer");
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Contract that rejects ETH transfers (no payable functions)
|
|
354
|
+
contract EthRejectingContract {
|
|
355
|
+
// This contract has no receive() or fallback() payable functions
|
|
356
|
+
// so ETH transfers will fail
|
|
357
|
+
receive() external payable {
|
|
358
|
+
console.log("EthRejectingContract received ETH");
|
|
359
|
+
revert("EthRejectingContract received ETH");
|
|
360
|
+
}
|
|
320
361
|
}
|
package/test/CreatorCoin.t.sol
CHANGED
|
@@ -5,7 +5,7 @@ import "./utils/BaseTest.sol";
|
|
|
5
5
|
|
|
6
6
|
import {ICreatorCoin} from "../src/interfaces/ICreatorCoin.sol";
|
|
7
7
|
import {ICreatorCoinHook} from "../src/interfaces/ICreatorCoinHook.sol";
|
|
8
|
-
import {
|
|
8
|
+
import {CoinConstants} from "../src/libs/CoinConstants.sol";
|
|
9
9
|
import {CoinRewardsV4} from "../src/libs/CoinRewardsV4.sol";
|
|
10
10
|
import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
|
|
11
11
|
|
|
@@ -13,10 +13,7 @@ contract CreatorCoinTest is BaseTest {
|
|
|
13
13
|
CreatorCoin internal creatorCoin;
|
|
14
14
|
|
|
15
15
|
function setUp() public override {
|
|
16
|
-
super.
|
|
17
|
-
|
|
18
|
-
deal(address(zoraToken), address(poolManager), 1_000_000_000e18);
|
|
19
|
-
|
|
16
|
+
super.setUpNonForked();
|
|
20
17
|
_deployCreatorCoin();
|
|
21
18
|
}
|
|
22
19
|
|
|
@@ -65,11 +62,11 @@ contract CreatorCoinTest is BaseTest {
|
|
|
65
62
|
assertEq(creatorCoin.name(), "Testcoin");
|
|
66
63
|
assertEq(creatorCoin.symbol(), "TEST");
|
|
67
64
|
assertEq(creatorCoin.payoutRecipient(), users.creator);
|
|
68
|
-
assertEq(creatorCoin.currency(),
|
|
69
|
-
assertEq(creatorCoin.totalSupply(),
|
|
65
|
+
assertEq(creatorCoin.currency(), CoinConstants.CREATOR_COIN_CURRENCY);
|
|
66
|
+
assertEq(creatorCoin.totalSupply(), CoinConstants.TOTAL_SUPPLY);
|
|
70
67
|
|
|
71
|
-
assertEq(creatorCoin.balanceOf(address(creatorCoin)),
|
|
72
|
-
assertEq(creatorCoin.balanceOf(address(creatorCoin.poolManager())),
|
|
68
|
+
assertEq(creatorCoin.balanceOf(address(creatorCoin)), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
|
|
69
|
+
assertEq(creatorCoin.balanceOf(address(creatorCoin.poolManager())), CoinConstants.CREATOR_COIN_MARKET_SUPPLY);
|
|
73
70
|
}
|
|
74
71
|
|
|
75
72
|
function test_deploy_creator_coin_with_invalid_currency_reverts() public {
|
|
@@ -105,7 +102,7 @@ contract CreatorCoinTest is BaseTest {
|
|
|
105
102
|
uint256 deploymentTime = block.timestamp;
|
|
106
103
|
|
|
107
104
|
assertEq(creatorCoin.vestingStartTime(), deploymentTime);
|
|
108
|
-
assertEq(creatorCoin.vestingEndTime(), deploymentTime +
|
|
105
|
+
assertEq(creatorCoin.vestingEndTime(), deploymentTime + CoinConstants.CREATOR_VESTING_DURATION);
|
|
109
106
|
assertEq(creatorCoin.totalClaimed(), 0);
|
|
110
107
|
}
|
|
111
108
|
|
|
@@ -124,16 +121,16 @@ contract CreatorCoinTest is BaseTest {
|
|
|
124
121
|
vm.warp(creatorCoin.vestingStartTime() + oneYear);
|
|
125
122
|
|
|
126
123
|
// After 1 year out of 5, should be able to claim 20% of vesting supply
|
|
127
|
-
uint256 expectedClaimable = (
|
|
124
|
+
uint256 expectedClaimable = (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY * oneYear) / CoinConstants.CREATOR_VESTING_DURATION;
|
|
128
125
|
assertEq(creatorCoin.getClaimableAmount(), expectedClaimable);
|
|
129
126
|
}
|
|
130
127
|
|
|
131
128
|
function test_getClaimableAmount_after_half_vesting_period() public {
|
|
132
|
-
uint256 halfVesting =
|
|
129
|
+
uint256 halfVesting = CoinConstants.CREATOR_VESTING_DURATION / 2;
|
|
133
130
|
vm.warp(creatorCoin.vestingStartTime() + halfVesting);
|
|
134
131
|
|
|
135
132
|
// After 2.5 years, should be able to claim 50% of vesting supply
|
|
136
|
-
uint256 expectedClaimable =
|
|
133
|
+
uint256 expectedClaimable = CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY / 2;
|
|
137
134
|
assertEq(creatorCoin.getClaimableAmount(), expectedClaimable);
|
|
138
135
|
}
|
|
139
136
|
|
|
@@ -141,14 +138,14 @@ contract CreatorCoinTest is BaseTest {
|
|
|
141
138
|
vm.warp(creatorCoin.vestingEndTime());
|
|
142
139
|
|
|
143
140
|
// After full vesting period, should be able to claim entire vesting supply
|
|
144
|
-
assertEq(creatorCoin.getClaimableAmount(),
|
|
141
|
+
assertEq(creatorCoin.getClaimableAmount(), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
|
|
145
142
|
}
|
|
146
143
|
|
|
147
144
|
function test_getClaimableAmount_after_vesting_period_ends() public {
|
|
148
145
|
vm.warp(creatorCoin.vestingEndTime() + 365 days);
|
|
149
146
|
|
|
150
147
|
// Even after vesting ends, should still be able to claim entire vesting supply
|
|
151
|
-
assertEq(creatorCoin.getClaimableAmount(),
|
|
148
|
+
assertEq(creatorCoin.getClaimableAmount(), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
|
|
152
149
|
}
|
|
153
150
|
|
|
154
151
|
function test_getClaimableAmount_after_one_day() public {
|
|
@@ -156,12 +153,12 @@ contract CreatorCoinTest is BaseTest {
|
|
|
156
153
|
uint256 oneDay = 1 days;
|
|
157
154
|
vm.warp(creatorCoin.vestingStartTime() + oneDay);
|
|
158
155
|
|
|
159
|
-
uint256 expectedClaimable = (
|
|
156
|
+
uint256 expectedClaimable = (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY * oneDay) / CoinConstants.CREATOR_VESTING_DURATION;
|
|
160
157
|
assertEq(creatorCoin.getClaimableAmount(), expectedClaimable);
|
|
161
158
|
|
|
162
159
|
// Verify it's a small but non-zero amount
|
|
163
160
|
assertGt(expectedClaimable, 0);
|
|
164
|
-
assertLt(expectedClaimable,
|
|
161
|
+
assertLt(expectedClaimable, CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY / 1000); // Less than 0.1%
|
|
165
162
|
}
|
|
166
163
|
|
|
167
164
|
function test_claimVesting_at_launch() public {
|
|
@@ -175,7 +172,7 @@ contract CreatorCoinTest is BaseTest {
|
|
|
175
172
|
uint256 oneYear = 365 days;
|
|
176
173
|
vm.warp(creatorCoin.vestingStartTime() + oneYear);
|
|
177
174
|
|
|
178
|
-
uint256 expectedClaimable = (
|
|
175
|
+
uint256 expectedClaimable = (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY * oneYear) / CoinConstants.CREATOR_VESTING_DURATION;
|
|
179
176
|
uint256 initialCreatorBalance = creatorCoin.balanceOf(users.creator);
|
|
180
177
|
uint256 initialContractBalance = creatorCoin.balanceOf(address(creatorCoin));
|
|
181
178
|
|
|
@@ -201,7 +198,7 @@ contract CreatorCoinTest is BaseTest {
|
|
|
201
198
|
|
|
202
199
|
// First claim after 1 year
|
|
203
200
|
vm.warp(creatorCoin.vestingStartTime() + oneYear);
|
|
204
|
-
uint256 expectedClaim1 = (
|
|
201
|
+
uint256 expectedClaim1 = (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY * oneYear) / CoinConstants.CREATOR_VESTING_DURATION;
|
|
205
202
|
uint256 claimed1 = creatorCoin.claimVesting();
|
|
206
203
|
|
|
207
204
|
assertEq(claimed1, expectedClaim1);
|
|
@@ -210,7 +207,7 @@ contract CreatorCoinTest is BaseTest {
|
|
|
210
207
|
|
|
211
208
|
// Second claim after another year (2 years total)
|
|
212
209
|
vm.warp(creatorCoin.vestingStartTime() + 2 * oneYear);
|
|
213
|
-
uint256 totalVestedAfter2Years = (
|
|
210
|
+
uint256 totalVestedAfter2Years = (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY * 2 * oneYear) / CoinConstants.CREATOR_VESTING_DURATION;
|
|
214
211
|
uint256 expectedClaim2 = totalVestedAfter2Years - expectedClaim1;
|
|
215
212
|
|
|
216
213
|
uint256 claimed2 = creatorCoin.claimVesting();
|
|
@@ -241,9 +238,9 @@ contract CreatorCoinTest is BaseTest {
|
|
|
241
238
|
|
|
242
239
|
uint256 claimedAmount = creatorCoin.claimVesting();
|
|
243
240
|
|
|
244
|
-
assertEq(claimedAmount,
|
|
245
|
-
assertEq(creatorCoin.totalClaimed(),
|
|
246
|
-
assertEq(creatorCoin.balanceOf(users.creator),
|
|
241
|
+
assertEq(claimedAmount, CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
|
|
242
|
+
assertEq(creatorCoin.totalClaimed(), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
|
|
243
|
+
assertEq(creatorCoin.balanceOf(users.creator), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
|
|
247
244
|
|
|
248
245
|
// Subsequent claims should return 0
|
|
249
246
|
uint256 secondClaim = creatorCoin.claimVesting();
|
|
@@ -251,21 +248,21 @@ contract CreatorCoinTest is BaseTest {
|
|
|
251
248
|
}
|
|
252
249
|
|
|
253
250
|
function test_claimVesting_partial_then_full() public {
|
|
254
|
-
uint256 halfVesting =
|
|
251
|
+
uint256 halfVesting = CoinConstants.CREATOR_VESTING_DURATION / 2;
|
|
255
252
|
|
|
256
253
|
// Claim half way through vesting
|
|
257
254
|
vm.warp(creatorCoin.vestingStartTime() + halfVesting);
|
|
258
255
|
uint256 partialClaim = creatorCoin.claimVesting();
|
|
259
|
-
assertEq(partialClaim,
|
|
256
|
+
assertEq(partialClaim, CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY / 2);
|
|
260
257
|
|
|
261
258
|
// Claim the rest after full vesting
|
|
262
259
|
vm.warp(creatorCoin.vestingEndTime());
|
|
263
260
|
uint256 remainingClaim = creatorCoin.claimVesting();
|
|
264
|
-
assertEq(remainingClaim,
|
|
261
|
+
assertEq(remainingClaim, CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY / 2);
|
|
265
262
|
|
|
266
263
|
// Total should equal full vesting supply
|
|
267
|
-
assertEq(creatorCoin.totalClaimed(),
|
|
268
|
-
assertEq(creatorCoin.balanceOf(users.creator),
|
|
264
|
+
assertEq(creatorCoin.totalClaimed(), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
|
|
265
|
+
assertEq(creatorCoin.balanceOf(users.creator), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
|
|
269
266
|
}
|
|
270
267
|
|
|
271
268
|
function test_vesting_calculation_edge_cases() public {
|
|
@@ -277,17 +274,17 @@ contract CreatorCoinTest is BaseTest {
|
|
|
277
274
|
vm.warp(creatorCoin.vestingStartTime() + 1);
|
|
278
275
|
uint256 claimableAfterOneSecond = creatorCoin.getClaimableAmount();
|
|
279
276
|
assertGt(claimableAfterOneSecond, 0);
|
|
280
|
-
assertLt(claimableAfterOneSecond,
|
|
277
|
+
assertLt(claimableAfterOneSecond, CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY / 1000000); // Very small amount
|
|
281
278
|
|
|
282
279
|
// Test one second before vesting ends
|
|
283
280
|
vm.warp(creatorCoin.vestingEndTime() - 1);
|
|
284
281
|
uint256 claimableBeforeEnd = creatorCoin.getClaimableAmount();
|
|
285
|
-
assertLt(claimableBeforeEnd,
|
|
286
|
-
assertGt(claimableBeforeEnd,
|
|
282
|
+
assertLt(claimableBeforeEnd, CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
|
|
283
|
+
assertGt(claimableBeforeEnd, CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY - (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY / 1000000));
|
|
287
284
|
|
|
288
285
|
// Test at exact vesting end time
|
|
289
286
|
vm.warp(creatorCoin.vestingEndTime());
|
|
290
|
-
assertEq(creatorCoin.getClaimableAmount(),
|
|
287
|
+
assertEq(creatorCoin.getClaimableAmount(), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
|
|
291
288
|
}
|
|
292
289
|
|
|
293
290
|
function test_vesting_frequent_small_claims() public {
|
|
@@ -302,11 +299,35 @@ contract CreatorCoinTest is BaseTest {
|
|
|
302
299
|
}
|
|
303
300
|
|
|
304
301
|
// Verify total claimed matches expected amount for 7 days
|
|
305
|
-
uint256 expectedTotal = (
|
|
302
|
+
uint256 expectedTotal = (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY * 7 days) / CoinConstants.CREATOR_VESTING_DURATION;
|
|
306
303
|
assertEq(totalClaimed, expectedTotal);
|
|
307
304
|
assertEq(creatorCoin.totalClaimed(), expectedTotal);
|
|
308
305
|
}
|
|
309
306
|
|
|
307
|
+
function test_vesting_duration_accounts_for_leap_years() public pure {
|
|
308
|
+
// Verify the vesting duration is exactly 5 years accounting for leap years
|
|
309
|
+
// 365.25 days per year * 5 years = 1826.25 days = 157,788,000 seconds
|
|
310
|
+
uint256 expectedDuration = 5 * 365.25 days;
|
|
311
|
+
uint256 expectedSeconds = 157_788_000; // 5 * 365.25 * 24 * 60 * 60
|
|
312
|
+
|
|
313
|
+
assertEq(CoinConstants.CREATOR_VESTING_DURATION, expectedDuration);
|
|
314
|
+
assertEq(CoinConstants.CREATOR_VESTING_DURATION, expectedSeconds);
|
|
315
|
+
|
|
316
|
+
// Verify it's longer than 5 * 365 days (which would be the old incorrect duration)
|
|
317
|
+
uint256 oldIncorrectDuration = 5 * 365 days;
|
|
318
|
+
uint256 differenceInSeconds = expectedDuration - oldIncorrectDuration;
|
|
319
|
+
uint256 expectedDifferenceInDays = 1.25 days; // 1.25 days = 108,000 seconds
|
|
320
|
+
|
|
321
|
+
assertEq(differenceInSeconds, expectedDifferenceInDays);
|
|
322
|
+
assertEq(differenceInSeconds, 108_000); // 1.25 * 24 * 60 * 60
|
|
323
|
+
|
|
324
|
+
// Verify this matches exactly 5 years with leap year correction
|
|
325
|
+
// Over 5 years, there's typically 1 leap day (Feb 29), plus 0.25 day per year
|
|
326
|
+
// for the quarter-day that accumulates: 1 + (5 * 0.25) = 2.25 days total
|
|
327
|
+
// But we use 365.25 average, so: 5 * 0.25 = 1.25 additional days
|
|
328
|
+
assertTrue(CoinConstants.CREATOR_VESTING_DURATION > oldIncorrectDuration);
|
|
329
|
+
}
|
|
330
|
+
|
|
310
331
|
function test_buy(uint128 amountIn) public {
|
|
311
332
|
vm.assume(amountIn > 0.00001e18);
|
|
312
333
|
vm.assume(amountIn < 500_000e18);
|
|
@@ -22,9 +22,7 @@ contract CreatorCoinRewardsTest is BaseTest {
|
|
|
22
22
|
address internal tradeReferrer;
|
|
23
23
|
|
|
24
24
|
function setUp() public override {
|
|
25
|
-
super.
|
|
26
|
-
|
|
27
|
-
deal(address(zoraToken), address(poolManager), 1_000_000_000e18);
|
|
25
|
+
super.setUpNonForked();
|
|
28
26
|
|
|
29
27
|
// Set up referrer addresses for all tests
|
|
30
28
|
platformReferrer = makeAddr("platformReferrer");
|
|
@@ -4,6 +4,7 @@ pragma solidity ^0.8.13;
|
|
|
4
4
|
import {BaseTest} from "./utils/BaseTest.sol";
|
|
5
5
|
import {BuySupplyWithSwapRouterHook} from "../src/hooks/deployment/BuySupplyWithSwapRouterHook.sol";
|
|
6
6
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
7
|
+
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
7
8
|
import {IUniswapV3Pool} from "../src/interfaces/IUniswapV3Pool.sol";
|
|
8
9
|
import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
|
|
9
10
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
@@ -64,7 +65,7 @@ contract DeploymentsHooksTest is BaseTest {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
function test_buySupplyWithEthUsingV4Hook_withExactInputMultiHop(uint256 initialOrderSize) public {
|
|
67
|
-
vm.assume(initialOrderSize >
|
|
68
|
+
vm.assume(initialOrderSize > 0.0001 ether);
|
|
68
69
|
vm.assume(initialOrderSize < 1 ether);
|
|
69
70
|
|
|
70
71
|
vm.deal(users.creator, initialOrderSize);
|
|
@@ -105,7 +106,7 @@ contract DeploymentsHooksTest is BaseTest {
|
|
|
105
106
|
assertEq(coinV4.currency(), zora, "currency");
|
|
106
107
|
assertGt(amountCurrency, 0, "amountCurrency > 0");
|
|
107
108
|
assertGt(coinsPurchased, 0, "coinsPurchased > 0");
|
|
108
|
-
assertEq(coinV4.balanceOf(users.creator), CoinConstants.
|
|
109
|
+
assertEq(coinV4.balanceOf(users.creator), CoinConstants.CONTENT_COIN_INITIAL_CREATOR_SUPPLY + coinsPurchased, "balanceOf creator");
|
|
109
110
|
// assertGt(IERC20(zora).balanceOf(address(pool)), 0, "Pool ZORA balance");
|
|
110
111
|
}
|
|
111
112
|
|
|
@@ -211,4 +212,55 @@ contract DeploymentsHooksTest is BaseTest {
|
|
|
211
212
|
vm.prank(users.creator);
|
|
212
213
|
_deployWithHook(address(0), bytes(""), zora);
|
|
213
214
|
}
|
|
215
|
+
|
|
216
|
+
function test_creatorCoin_deployWithHook_buySupplyWithEth() public {
|
|
217
|
+
uint256 initialOrderSize = 0.0001 ether;
|
|
218
|
+
vm.deal(users.creator, initialOrderSize);
|
|
219
|
+
|
|
220
|
+
uint24 poolFee = 3000;
|
|
221
|
+
|
|
222
|
+
bytes memory hookData = _encodeExactInputSingle(
|
|
223
|
+
users.creator,
|
|
224
|
+
ISwapRouter.ExactInputSingleParams({
|
|
225
|
+
tokenIn: address(weth),
|
|
226
|
+
tokenOut: zora,
|
|
227
|
+
fee: poolFee,
|
|
228
|
+
recipient: address(buySupplyWithSwapRouterHook),
|
|
229
|
+
amountIn: initialOrderSize,
|
|
230
|
+
amountOutMinimum: 0,
|
|
231
|
+
sqrtPriceLimitX96: 0
|
|
232
|
+
})
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
bytes memory poolConfig = CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(zora);
|
|
236
|
+
|
|
237
|
+
vm.prank(users.creator);
|
|
238
|
+
(address creatorCoinAddress, bytes memory hookDataOut) = factory.deployCreatorCoin{value: initialOrderSize}(
|
|
239
|
+
users.creator,
|
|
240
|
+
_getDefaultOwners(),
|
|
241
|
+
"https://test.com",
|
|
242
|
+
"Creator Coin Test",
|
|
243
|
+
"CCT",
|
|
244
|
+
poolConfig,
|
|
245
|
+
users.platformReferrer,
|
|
246
|
+
address(buySupplyWithSwapRouterHook),
|
|
247
|
+
hookData,
|
|
248
|
+
bytes32(uint256(123)) // coinSalt
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
(uint256 amountCurrency, uint256 coinsPurchased) = abi.decode(hookDataOut, (uint256, uint256));
|
|
252
|
+
|
|
253
|
+
// Verify the creator coin was deployed successfully
|
|
254
|
+
ICoin creatorCoin = ICoin(creatorCoinAddress);
|
|
255
|
+
assertEq(creatorCoin.currency(), zora, "currency should be ZORA");
|
|
256
|
+
assertEq(IERC20Metadata(creatorCoinAddress).name(), "Creator Coin Test", "name should match");
|
|
257
|
+
assertEq(IERC20Metadata(creatorCoinAddress).symbol(), "CCT", "symbol should match");
|
|
258
|
+
|
|
259
|
+
// Verify the hook executed and purchased coins
|
|
260
|
+
assertGt(amountCurrency, 0, "amountCurrency should be > 0");
|
|
261
|
+
assertGt(coinsPurchased, 0, "coinsPurchased should be > 0");
|
|
262
|
+
|
|
263
|
+
// Verify creator received the purchased coins (launch reward vests over time for creator coins)
|
|
264
|
+
assertEq(IERC20(creatorCoinAddress).balanceOf(users.creator), coinsPurchased, "creator should have purchased coins");
|
|
265
|
+
}
|
|
214
266
|
}
|
package/test/Factory.t.sol
CHANGED
|
@@ -8,7 +8,7 @@ import {IZoraFactory} from "../src/interfaces/IZoraFactory.sol";
|
|
|
8
8
|
|
|
9
9
|
contract FactoryTest is BaseTest {
|
|
10
10
|
function setUp() public override {
|
|
11
|
-
super.
|
|
11
|
+
super.setUpNonForked();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function test_factory_constructor_and_proxy_setup() public {
|
|
@@ -115,8 +115,8 @@ contract FactoryTest is BaseTest {
|
|
|
115
115
|
|
|
116
116
|
address platformReferrer = users.platformReferrer;
|
|
117
117
|
|
|
118
|
-
bytes memory poolConfig = CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(address(
|
|
119
|
-
bytes memory poolConfigForGettingAddress = poolConfigChanged ? CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(
|
|
118
|
+
bytes memory poolConfig = CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(address(0));
|
|
119
|
+
bytes memory poolConfigForGettingAddress = poolConfigChanged ? CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(ZORA_TOKEN_ADDRESS) : poolConfig;
|
|
120
120
|
|
|
121
121
|
address expectedCoinAddress = factory.coinAddress(msgSender, name, symbol, poolConfigForGettingAddress, platformReferrer, salt);
|
|
122
122
|
|