@rev-net/core-v6 0.0.37 → 0.0.40
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/CHANGELOG.md +2 -2
- package/README.md +6 -7
- package/foundry.toml +1 -1
- package/package.json +23 -16
- package/references/operations.md +1 -1
- package/references/runtime.md +1 -1
- package/script/Deploy.s.sol +12 -9
- package/src/REVDeployer.sol +69 -67
- package/src/REVHiddenTokens.sol +2 -2
- package/src/REVLoans.sol +26 -22
- package/src/REVOwner.sol +147 -29
- package/src/interfaces/IREVDeployer.sol +2 -1
- package/src/interfaces/IREVHiddenTokens.sol +4 -1
- package/src/interfaces/IREVOwner.sol +5 -0
- package/src/structs/REVAutoIssuance.sol +4 -2
- package/src/structs/REVConfig.sol +8 -5
- package/src/structs/REVDescription.sol +6 -5
- package/src/structs/REVLoan.sol +8 -5
- package/src/structs/REVStageConfig.sol +14 -16
- package/ADMINISTRATION.md +0 -73
- package/ARCHITECTURE.md +0 -116
- package/AUDIT_INSTRUCTIONS.md +0 -90
- package/RISKS.md +0 -107
- package/SKILLS.md +0 -46
- package/STYLE_GUIDE.md +0 -610
- package/USER_JOURNEYS.md +0 -195
- package/foundry.lock +0 -11
- package/slither-ci.config.json +0 -10
- package/sphinx.lock +0 -507
- package/test/REV.integrations.t.sol +0 -573
- package/test/REVAutoIssuanceFuzz.t.sol +0 -328
- package/test/REVDeployerRegressions.t.sol +0 -396
- package/test/REVInvincibility.t.sol +0 -1371
- package/test/REVInvincibilityHandler.sol +0 -387
- package/test/REVLifecycle.t.sol +0 -420
- package/test/REVLoans.invariants.t.sol +0 -724
- package/test/REVLoansAttacks.t.sol +0 -816
- package/test/REVLoansFeeRecovery.t.sol +0 -783
- package/test/REVLoansFindings.t.sol +0 -711
- package/test/REVLoansRegressions.t.sol +0 -364
- package/test/REVLoansSourceFeeRecovery.t.sol +0 -517
- package/test/REVLoansSourced.t.sol +0 -1839
- package/test/REVLoansUnSourced.t.sol +0 -409
- package/test/TestAuditFixVerification.t.sol +0 -675
- package/test/TestBurnHeldTokens.t.sol +0 -394
- package/test/TestCEIPattern.t.sol +0 -508
- package/test/TestCashOutCallerValidation.t.sol +0 -452
- package/test/TestConversionDocumentation.t.sol +0 -365
- package/test/TestCrossCurrencyReclaim.t.sol +0 -610
- package/test/TestCrossSourceReallocation.t.sol +0 -361
- package/test/TestERC2771MetaTx.t.sol +0 -585
- package/test/TestEmptyBuybackSpecs.t.sol +0 -300
- package/test/TestFlashLoanSurplus.t.sol +0 -365
- package/test/TestHiddenTokens.t.sol +0 -474
- package/test/TestHookArrayOOB.t.sol +0 -278
- package/test/TestLiquidationBehavior.t.sol +0 -398
- package/test/TestLoanSourceRotation.t.sol +0 -553
- package/test/TestLoansCashOutDelay.t.sol +0 -493
- package/test/TestLongTailEconomics.t.sol +0 -677
- package/test/TestLowFindings.t.sol +0 -677
- package/test/TestMixedFixes.t.sol +0 -593
- package/test/TestPermit2Signatures.t.sol +0 -683
- package/test/TestReallocationSandwich.t.sol +0 -412
- package/test/TestRevnetRegressions.t.sol +0 -350
- package/test/TestSplitWeightAdjustment.t.sol +0 -527
- package/test/TestSplitWeightE2E.t.sol +0 -605
- package/test/TestSplitWeightFork.t.sol +0 -855
- package/test/TestStageTransitionBorrowable.t.sol +0 -301
- package/test/TestSwapTerminalPermission.t.sol +0 -262
- package/test/TestTerminalEncodingInHash.t.sol +0 -326
- package/test/TestUint112Overflow.t.sol +0 -311
- package/test/TestZeroAmountLoanGuard.t.sol +0 -378
- package/test/TestZeroRepayment.t.sol +0 -354
- package/test/audit/CrossChainBuybackRouteMismatch.t.sol +0 -184
- package/test/audit/HiddenSupplyCashout.t.sol +0 -61
- package/test/audit/LoanIdOverflowGuard.t.sol +0 -523
- package/test/audit/NemesisVerification.t.sol +0 -97
- package/test/audit/OperatorDelegation.t.sol +0 -356
- package/test/audit/PhantomSurplusTerminal.t.sol +0 -367
- package/test/audit/REVOwnerCurrencyMismatch.t.sol +0 -188
- package/test/audit/REVOwnerRemoteSurplusCurrencyMismatch.t.sol +0 -140
- package/test/audit/ReallocatePermission.t.sol +0 -363
- package/test/audit/RemoteLoanAccountingGap.t.sol +0 -74
- package/test/audit/SupportsInterfaceTest.t.sol +0 -51
- package/test/audit/TestFeeAllowanceLeak.t.sol +0 -197
- package/test/audit/TestLoansAndDeployerFixes.t.sol +0 -576
- package/test/fork/ForkTestBase.sol +0 -727
- package/test/fork/TestAutoIssuanceFork.t.sol +0 -148
- package/test/fork/TestCashOutFork.t.sol +0 -253
- package/test/fork/TestIssuanceDecayFork.t.sol +0 -158
- package/test/fork/TestLoanAdversarialFork.t.sol +0 -744
- package/test/fork/TestLoanBorrowFork.t.sol +0 -163
- package/test/fork/TestLoanCrossRulesetFork.t.sol +0 -308
- package/test/fork/TestLoanERC20Fork.t.sol +0 -459
- package/test/fork/TestLoanLiquidationFork.t.sol +0 -135
- package/test/fork/TestLoanReallocateFork.t.sol +0 -113
- package/test/fork/TestLoanRepayFork.t.sol +0 -188
- package/test/fork/TestLoanTransferFork.t.sol +0 -143
- package/test/fork/TestPermit2PaymentFork.t.sol +0 -300
- package/test/fork/TestSplitWeightFork.t.sol +0 -189
- package/test/helpers/MaliciousContracts.sol +0 -247
- package/test/helpers/REVEmpty721Config.sol +0 -45
- package/test/mock/MockBuybackCashOutRecorder.sol +0 -84
- package/test/mock/MockBuybackDataHook.sol +0 -112
- package/test/mock/MockBuybackDataHookMintPath.sol +0 -68
- package/test/mock/MockSuckerRegistry.sol +0 -17
- package/test/regression/TestBurnPermissionRequired.t.sol +0 -294
- package/test/regression/TestCashOutBuybackFeeLeak.t.sol +0 -232
- package/test/regression/TestCrossRevnetLiquidation.t.sol +0 -255
- package/test/regression/TestCumulativeLoanCounter.t.sol +0 -361
- package/test/regression/TestLiquidateGapHandling.t.sol +0 -394
- package/test/regression/TestZeroPriceFeed.t.sol +0 -422
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
|
-
import "./ForkTestBase.sol";
|
|
6
|
-
import {REVEmpty721Config} from "../helpers/REVEmpty721Config.sol";
|
|
7
|
-
import {MockERC20} from "@bananapus/core-v6/test/mock/MockERC20.sol";
|
|
8
|
-
import {MockPriceFeed} from "@bananapus/core-v6/test/mock/MockPriceFeed.sol";
|
|
9
|
-
import {JBSingleAllowance} from "@bananapus/core-v6/src/structs/JBSingleAllowance.sol";
|
|
10
|
-
import {IAllowanceTransfer} from "@uniswap/permit2/src/interfaces/IAllowanceTransfer.sol";
|
|
11
|
-
import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
|
|
12
|
-
|
|
13
|
-
/// @notice Fork tests for Permit2-based ERC-20 payments to a revnet terminal.
|
|
14
|
-
///
|
|
15
|
-
/// Verifies that JBMultiTerminal._acceptFundsFor() correctly processes Permit2 metadata,
|
|
16
|
-
/// including valid signatures, expired signatures, and replayed nonces.
|
|
17
|
-
///
|
|
18
|
-
/// Run with: FOUNDRY_PROFILE=fork forge test --match-contract TestPermit2PaymentFork -vvv
|
|
19
|
-
contract TestPermit2PaymentFork is ForkTestBase {
|
|
20
|
-
using JBMetadataResolver for bytes;
|
|
21
|
-
|
|
22
|
-
// Permit2 signing key.
|
|
23
|
-
uint256 constant PRIV_KEY = 0xBEEF1234;
|
|
24
|
-
address signer;
|
|
25
|
-
|
|
26
|
-
MockERC20 testToken;
|
|
27
|
-
uint256 revnetId;
|
|
28
|
-
|
|
29
|
-
// Permit2 EIP-712 typehashes (copied from nana-core-v6 test patterns).
|
|
30
|
-
bytes32 public constant _PERMIT_DETAILS_TYPEHASH =
|
|
31
|
-
keccak256("PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)");
|
|
32
|
-
|
|
33
|
-
bytes32 public constant _PERMIT_SINGLE_TYPEHASH = keccak256(
|
|
34
|
-
"PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
// Permit2 domain separator (fetched at runtime from the deployed Permit2 contract).
|
|
38
|
-
// forge-lint: disable-next-line(mixed-case-variable)
|
|
39
|
-
bytes32 DOMAIN_SEPARATOR;
|
|
40
|
-
|
|
41
|
-
function setUp() public override {
|
|
42
|
-
super.setUp();
|
|
43
|
-
|
|
44
|
-
signer = vm.addr(PRIV_KEY);
|
|
45
|
-
vm.deal(signer, 10 ether);
|
|
46
|
-
|
|
47
|
-
// Deploy fee project.
|
|
48
|
-
_deployFeeProject(5000);
|
|
49
|
-
|
|
50
|
-
// Deploy a mock ERC-20 for testing.
|
|
51
|
-
testToken = new MockERC20("Test Token", "TEST");
|
|
52
|
-
|
|
53
|
-
// Add a price feed: testToken → NATIVE_TOKEN so the buyback hook can quote.
|
|
54
|
-
// 1 testToken (6 dec) = 0.0005 ETH (i.e. 1 ETH = 2000 testTokens).
|
|
55
|
-
MockPriceFeed priceFeed = new MockPriceFeed(5e14, 18);
|
|
56
|
-
vm.prank(multisig());
|
|
57
|
-
jbPrices()
|
|
58
|
-
.addPriceFeedFor(
|
|
59
|
-
0, uint32(uint160(address(testToken))), uint32(uint160(JBConstants.NATIVE_TOKEN)), priceFeed
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
// Deploy a revnet that accepts both native token and the test ERC-20.
|
|
63
|
-
revnetId = _deployRevnetWithERC20(5000);
|
|
64
|
-
|
|
65
|
-
// Fetch the Permit2 domain separator from the on-chain Permit2 contract.
|
|
66
|
-
DOMAIN_SEPARATOR = permit2().DOMAIN_SEPARATOR();
|
|
67
|
-
|
|
68
|
-
// Mint tokens to the signer and approve Permit2 (NOT the terminal directly).
|
|
69
|
-
testToken.mint(signer, 1000e6);
|
|
70
|
-
vm.prank(signer);
|
|
71
|
-
IERC20(address(testToken)).approve(address(permit2()), type(uint256).max);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/// @notice Deploy a revnet that accepts both native token and the test ERC-20.
|
|
75
|
-
function _deployRevnetWithERC20(uint16 cashOutTaxRate) internal returns (uint256 id) {
|
|
76
|
-
JBAccountingContext[] memory acc = new JBAccountingContext[](2);
|
|
77
|
-
acc[0] = JBAccountingContext({
|
|
78
|
-
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
79
|
-
});
|
|
80
|
-
acc[1] = JBAccountingContext({
|
|
81
|
-
token: address(testToken), decimals: 6, currency: uint32(uint160(address(testToken)))
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
JBTerminalConfig[] memory tc = new JBTerminalConfig[](1);
|
|
85
|
-
tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: acc});
|
|
86
|
-
|
|
87
|
-
REVStageConfig[] memory stages = new REVStageConfig[](1);
|
|
88
|
-
JBSplit[] memory splits = new JBSplit[](1);
|
|
89
|
-
splits[0].beneficiary = payable(multisig());
|
|
90
|
-
splits[0].percent = 10_000;
|
|
91
|
-
stages[0] = REVStageConfig({
|
|
92
|
-
startsAtOrAfter: uint40(block.timestamp),
|
|
93
|
-
autoIssuances: new REVAutoIssuance[](0),
|
|
94
|
-
splitPercent: 0,
|
|
95
|
-
splits: splits,
|
|
96
|
-
initialIssuance: INITIAL_ISSUANCE,
|
|
97
|
-
issuanceCutFrequency: 0,
|
|
98
|
-
issuanceCutPercent: 0,
|
|
99
|
-
cashOutTaxRate: cashOutTaxRate,
|
|
100
|
-
extraMetadata: 0
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
REVConfig memory cfg = REVConfig({
|
|
104
|
-
description: REVDescription("Permit2 Test", "P2T", "ipfs://p2t", "PERMIT2_SALT"),
|
|
105
|
-
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
106
|
-
splitOperator: multisig(),
|
|
107
|
-
stageConfigurations: stages
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
REVSuckerDeploymentConfig memory sdc = REVSuckerDeploymentConfig({
|
|
111
|
-
deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256(abi.encodePacked("PERMIT2_TEST"))
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
(id,) = REV_DEPLOYER.deployFor({
|
|
115
|
-
revnetId: 0,
|
|
116
|
-
configuration: cfg,
|
|
117
|
-
terminalConfigurations: tc,
|
|
118
|
-
suckerDeploymentConfiguration: sdc,
|
|
119
|
-
tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
|
|
120
|
-
allowedPosts: REVEmpty721Config.emptyAllowedPosts()
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/// @notice Build Permit2 metadata for a payment.
|
|
125
|
-
function _buildPermit2Metadata(
|
|
126
|
-
uint160 amount,
|
|
127
|
-
uint48 expiration,
|
|
128
|
-
uint48 nonce,
|
|
129
|
-
uint256 sigDeadline
|
|
130
|
-
)
|
|
131
|
-
internal
|
|
132
|
-
view
|
|
133
|
-
returns (bytes memory metadata)
|
|
134
|
-
{
|
|
135
|
-
// Build the permit struct for signing.
|
|
136
|
-
IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer.PermitSingle({
|
|
137
|
-
details: IAllowanceTransfer.PermitDetails({
|
|
138
|
-
token: address(testToken), amount: amount, expiration: expiration, nonce: nonce
|
|
139
|
-
}),
|
|
140
|
-
spender: address(jbMultiTerminal()),
|
|
141
|
-
sigDeadline: sigDeadline
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// Sign it.
|
|
145
|
-
bytes memory sig = _getPermitSignature(permitSingle, PRIV_KEY, DOMAIN_SEPARATOR);
|
|
146
|
-
|
|
147
|
-
// Pack into JBSingleAllowance.
|
|
148
|
-
JBSingleAllowance memory allowance = JBSingleAllowance({
|
|
149
|
-
sigDeadline: sigDeadline, amount: amount, expiration: expiration, nonce: nonce, signature: sig
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// Encode as metadata with the "permit2" key targeting the terminal, plus a "quote" entry
|
|
153
|
-
// targeting the buyback hook with minimumSwapAmountOut=1 so the buyback hook skips the
|
|
154
|
-
// TWAP oracle and falls through to normal minting (no V4 pool has liquidity for testToken).
|
|
155
|
-
bytes4[] memory ids = new bytes4[](2);
|
|
156
|
-
bytes[] memory datas = new bytes[](2);
|
|
157
|
-
ids[0] = JBMetadataResolver.getId("permit2", address(jbMultiTerminal()));
|
|
158
|
-
datas[0] = abi.encode(allowance);
|
|
159
|
-
ids[1] = JBMetadataResolver.getId("quote", address(BUYBACK_HOOK));
|
|
160
|
-
datas[1] = abi.encode(uint256(0), uint256(1));
|
|
161
|
-
metadata = JBMetadataResolver.createMetadata(ids, datas);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/// @notice Pay a revnet with ERC-20 via Permit2 with zero direct terminal approval -- succeeds.
|
|
165
|
-
function testFork_Permit2PaymentSucceeds() public {
|
|
166
|
-
uint160 payAmount = 100e6; // 100 tokens (6 decimals)
|
|
167
|
-
uint48 expiration = uint48(block.timestamp + 1 days);
|
|
168
|
-
uint256 sigDeadline = block.timestamp + 1 days;
|
|
169
|
-
uint48 nonce = 0;
|
|
170
|
-
|
|
171
|
-
bytes memory metadata = _buildPermit2Metadata(payAmount, expiration, nonce, sigDeadline);
|
|
172
|
-
|
|
173
|
-
// Verify the signer has NOT approved the terminal directly.
|
|
174
|
-
assertEq(
|
|
175
|
-
IERC20(address(testToken)).allowance(signer, address(jbMultiTerminal())),
|
|
176
|
-
0,
|
|
177
|
-
"signer should have zero direct terminal approval"
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
// Pay using Permit2.
|
|
181
|
-
vm.prank(signer);
|
|
182
|
-
uint256 tokensReceived = jbMultiTerminal()
|
|
183
|
-
.pay({
|
|
184
|
-
projectId: revnetId,
|
|
185
|
-
token: address(testToken),
|
|
186
|
-
amount: payAmount,
|
|
187
|
-
beneficiary: signer,
|
|
188
|
-
minReturnedTokens: 0,
|
|
189
|
-
memo: "permit2 payment",
|
|
190
|
-
metadata: metadata
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// Verify payment succeeded.
|
|
194
|
-
assertGt(tokensReceived, 0, "should receive project tokens from Permit2 payment");
|
|
195
|
-
|
|
196
|
-
// Verify tokens were transferred from signer to terminal.
|
|
197
|
-
assertEq(
|
|
198
|
-
testToken.balanceOf(address(jbMultiTerminal())), payAmount, "terminal should hold the paid ERC-20 tokens"
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
// Verify signer's token balance decreased.
|
|
202
|
-
assertEq(testToken.balanceOf(signer), 1000e6 - payAmount, "signer's token balance should decrease");
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/// @notice Payment with an expired Permit2 signature deadline should revert.
|
|
206
|
-
function testFork_Permit2ExpiredSignatureReverts() public {
|
|
207
|
-
uint160 payAmount = 100e6;
|
|
208
|
-
uint48 expiration = uint48(block.timestamp + 1 days);
|
|
209
|
-
uint48 nonce = 0;
|
|
210
|
-
|
|
211
|
-
// Set sigDeadline in the past.
|
|
212
|
-
uint256 expiredDeadline = block.timestamp - 1;
|
|
213
|
-
|
|
214
|
-
bytes memory metadata = _buildPermit2Metadata(payAmount, expiration, nonce, expiredDeadline);
|
|
215
|
-
|
|
216
|
-
// The Permit2 contract should reject the expired signature.
|
|
217
|
-
// The terminal catches permit failures in try-catch and falls back to transferFrom,
|
|
218
|
-
// which will also fail due to zero approval. So we expect a generic revert.
|
|
219
|
-
vm.prank(signer);
|
|
220
|
-
vm.expectRevert();
|
|
221
|
-
jbMultiTerminal()
|
|
222
|
-
.pay({
|
|
223
|
-
projectId: revnetId,
|
|
224
|
-
token: address(testToken),
|
|
225
|
-
amount: payAmount,
|
|
226
|
-
beneficiary: signer,
|
|
227
|
-
minReturnedTokens: 0,
|
|
228
|
-
memo: "expired permit2",
|
|
229
|
-
metadata: metadata
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/// @notice Replaying the same Permit2 nonce should revert on the second payment.
|
|
234
|
-
function testFork_Permit2ReplayReverts() public {
|
|
235
|
-
uint160 payAmount = 50e6;
|
|
236
|
-
uint48 expiration = uint48(block.timestamp + 1 days);
|
|
237
|
-
uint256 sigDeadline = block.timestamp + 1 days;
|
|
238
|
-
uint48 nonce = 0;
|
|
239
|
-
|
|
240
|
-
bytes memory metadata = _buildPermit2Metadata(payAmount, expiration, nonce, sigDeadline);
|
|
241
|
-
|
|
242
|
-
// First payment succeeds.
|
|
243
|
-
vm.prank(signer);
|
|
244
|
-
jbMultiTerminal()
|
|
245
|
-
.pay({
|
|
246
|
-
projectId: revnetId,
|
|
247
|
-
token: address(testToken),
|
|
248
|
-
amount: payAmount,
|
|
249
|
-
beneficiary: signer,
|
|
250
|
-
minReturnedTokens: 0,
|
|
251
|
-
memo: "first permit2 payment",
|
|
252
|
-
metadata: metadata
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// Second payment with the same nonce.
|
|
256
|
-
// The permit call will fail (nonce already used), terminal catches it via try-catch,
|
|
257
|
-
// then falls back to transferFrom which fails because there is no direct approval.
|
|
258
|
-
vm.prank(signer);
|
|
259
|
-
vm.expectRevert();
|
|
260
|
-
jbMultiTerminal()
|
|
261
|
-
.pay({
|
|
262
|
-
projectId: revnetId,
|
|
263
|
-
token: address(testToken),
|
|
264
|
-
amount: payAmount,
|
|
265
|
-
beneficiary: signer,
|
|
266
|
-
minReturnedTokens: 0,
|
|
267
|
-
memo: "replayed permit2 payment",
|
|
268
|
-
metadata: metadata
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// ───────────────────────── Permit2 Signature Helpers
|
|
273
|
-
// ─────────────────────────
|
|
274
|
-
|
|
275
|
-
/// @notice Sign a PermitSingle struct and return the concatenated signature.
|
|
276
|
-
function _getPermitSignature(
|
|
277
|
-
IAllowanceTransfer.PermitSingle memory permitSingle,
|
|
278
|
-
uint256 privateKey,
|
|
279
|
-
bytes32 domainSeparator
|
|
280
|
-
)
|
|
281
|
-
internal
|
|
282
|
-
pure
|
|
283
|
-
returns (bytes memory sig)
|
|
284
|
-
{
|
|
285
|
-
bytes32 permitHash = keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permitSingle.details));
|
|
286
|
-
|
|
287
|
-
bytes32 msgHash = keccak256(
|
|
288
|
-
abi.encodePacked(
|
|
289
|
-
"\x19\x01",
|
|
290
|
-
domainSeparator,
|
|
291
|
-
keccak256(
|
|
292
|
-
abi.encode(_PERMIT_SINGLE_TYPEHASH, permitHash, permitSingle.spender, permitSingle.sigDeadline)
|
|
293
|
-
)
|
|
294
|
-
)
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash);
|
|
298
|
-
return bytes.concat(r, s, bytes1(v));
|
|
299
|
-
}
|
|
300
|
-
}
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
|
-
import "./ForkTestBase.sol";
|
|
6
|
-
|
|
7
|
-
/// @notice Fork tests verifying that revnet 721 tier splits + real Uniswap V4 buyback hook produce correct token
|
|
8
|
-
/// issuance in both the swap path (AMM buyback) and the mint path (direct minting).
|
|
9
|
-
///
|
|
10
|
-
/// Requires: RPC_ETHEREUM_MAINNET env var for mainnet fork (real PoolManager).
|
|
11
|
-
///
|
|
12
|
-
/// Run with: FOUNDRY_PROFILE=fork forge test --match-contract TestSplitWeightFork -vvv --skip "script/*"
|
|
13
|
-
contract TestSplitWeightFork is ForkTestBase {
|
|
14
|
-
using StateLibrary for IPoolManager;
|
|
15
|
-
using PoolIdLibrary for PoolKey;
|
|
16
|
-
|
|
17
|
-
// ───────────────────────── Tests
|
|
18
|
-
// ─────────────────────────
|
|
19
|
-
|
|
20
|
-
/// @notice SWAP PATH: Pool offers good rate -> buyback hook swaps on AMM instead of minting.
|
|
21
|
-
/// With 30% tier split, the buyback should swap with 0.7 ETH worth.
|
|
22
|
-
/// Terminal mints 0 tokens (weight=0), buyback hook mints via controller after swap.
|
|
23
|
-
function test_fork_swapPath_splitWithBuyback() public {
|
|
24
|
-
_deployFeeProject(5000);
|
|
25
|
-
(uint256 revnetId, IJB721TiersHook hook) = _deployRevnetWith721(5000);
|
|
26
|
-
|
|
27
|
-
// Pool is already initialized at 1:1 price by REVDeployer during deployment.
|
|
28
|
-
// Move the price so project tokens are cheap (swap path wins).
|
|
29
|
-
|
|
30
|
-
address projectToken = address(jbTokens().tokenOf(revnetId));
|
|
31
|
-
require(projectToken != address(0), "project token not deployed");
|
|
32
|
-
|
|
33
|
-
// Native ETH is address(0), always less than any deployed token.
|
|
34
|
-
PoolKey memory key = PoolKey({
|
|
35
|
-
currency0: Currency.wrap(address(0)),
|
|
36
|
-
currency1: Currency.wrap(projectToken),
|
|
37
|
-
fee: REV_DEPLOYER.DEFAULT_BUYBACK_POOL_FEE(),
|
|
38
|
-
tickSpacing: REV_DEPLOYER.DEFAULT_BUYBACK_TICK_SPACING(),
|
|
39
|
-
hooks: IHooks(address(0))
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
uint256 projectLiq = 10_000_000e18;
|
|
43
|
-
uint256 ethLiq = 5000e18;
|
|
44
|
-
|
|
45
|
-
vm.prank(address(jbController()));
|
|
46
|
-
jbTokens().mintFor(address(liqHelper), revnetId, projectLiq);
|
|
47
|
-
vm.deal(address(liqHelper), ethLiq);
|
|
48
|
-
|
|
49
|
-
vm.startPrank(address(liqHelper));
|
|
50
|
-
IERC20(projectToken).approve(address(poolManager), type(uint256).max);
|
|
51
|
-
vm.stopPrank();
|
|
52
|
-
|
|
53
|
-
// Add full-range liquidity at tick 0 (1:1 price).
|
|
54
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
55
|
-
int256 liquidityDelta = int256(ethLiq / 4);
|
|
56
|
-
vm.prank(address(liqHelper));
|
|
57
|
-
liqHelper.addLiquidity{value: ethLiq}(key, TICK_LOWER, TICK_UPPER, liquidityDelta);
|
|
58
|
-
|
|
59
|
-
// Swap a large amount of project tokens for ETH to move the price.
|
|
60
|
-
uint256 swapAmount = 5_000_000e18;
|
|
61
|
-
vm.prank(address(jbController()));
|
|
62
|
-
jbTokens().mintFor(address(liqHelper), revnetId, swapAmount);
|
|
63
|
-
|
|
64
|
-
// currency0 is native ETH (address(0)), currency1 is projectToken.
|
|
65
|
-
// To sell projectToken for ETH (making project tokens cheaper), swap 1->0 (zeroForOne = false).
|
|
66
|
-
// zeroForOne=false pushes sqrtPrice up (more projectTokens per ETH).
|
|
67
|
-
bool zeroForOne = false;
|
|
68
|
-
uint160 sqrtPriceLimit = TickMath.getSqrtPriceAtTick(76_000);
|
|
69
|
-
|
|
70
|
-
vm.prank(address(liqHelper));
|
|
71
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
72
|
-
liqHelper.swap(key, zeroForOne, -int256(swapAmount), sqrtPriceLimit);
|
|
73
|
-
|
|
74
|
-
// Read the post-swap tick for the oracle mock.
|
|
75
|
-
(, int24 postSwapTick,,) = poolManager.getSlot0(key.toId());
|
|
76
|
-
_mockOracle(liquidityDelta, postSwapTick, uint32(REV_DEPLOYER.DEFAULT_BUYBACK_TWAP_WINDOW()));
|
|
77
|
-
|
|
78
|
-
address metadataTarget = hook.METADATA_ID_TARGET();
|
|
79
|
-
bytes memory metadata = _buildPayMetadataWithQuote({
|
|
80
|
-
hookMetadataTarget: metadataTarget, amountToSwapWith: 0.7 ether, minimumSwapAmountOut: 1
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
vm.prank(PAYER);
|
|
84
|
-
uint256 terminalTokensReturned = jbMultiTerminal().pay{value: 1 ether}({
|
|
85
|
-
projectId: revnetId,
|
|
86
|
-
token: JBConstants.NATIVE_TOKEN,
|
|
87
|
-
amount: 1 ether,
|
|
88
|
-
beneficiary: PAYER,
|
|
89
|
-
minReturnedTokens: 0,
|
|
90
|
-
memo: "Fork: swap path with splits",
|
|
91
|
-
metadata: metadata
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
assertGt(terminalTokensReturned, 700e18, "swap path: should get more tokens than minting (pool rate better)");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/// @notice MINT PATH: Pool offers bad rate -> buyback decides minting is better.
|
|
98
|
-
/// With 30% tier split, REVDeployer scales weight from 1000e18 to 700e18.
|
|
99
|
-
/// Terminal mints 700 tokens.
|
|
100
|
-
function test_fork_mintPath_splitWithBuyback() public {
|
|
101
|
-
_deployFeeProject(5000);
|
|
102
|
-
(uint256 revnetId, IJB721TiersHook hook) = _deployRevnetWith721(5000);
|
|
103
|
-
_setupPool(revnetId, 10_000 ether);
|
|
104
|
-
|
|
105
|
-
address metadataTarget = hook.METADATA_ID_TARGET();
|
|
106
|
-
bytes memory metadata = _buildPayMetadataNoQuote(metadataTarget);
|
|
107
|
-
|
|
108
|
-
vm.prank(PAYER);
|
|
109
|
-
uint256 tokensReceived = jbMultiTerminal().pay{value: 1 ether}({
|
|
110
|
-
projectId: revnetId,
|
|
111
|
-
token: JBConstants.NATIVE_TOKEN,
|
|
112
|
-
amount: 1 ether,
|
|
113
|
-
beneficiary: PAYER,
|
|
114
|
-
minReturnedTokens: 0,
|
|
115
|
-
memo: "Fork: mint path with splits",
|
|
116
|
-
metadata: metadata
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
uint256 expectedTokens = 700e18;
|
|
120
|
-
assertEq(tokensReceived, expectedTokens, "mint path: should receive 700 tokens (weight scaled for 30% split)");
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/// @notice MINT PATH without splits: baseline confirming 1000 tokens for 1 ETH.
|
|
124
|
-
function test_fork_mintPath_noSplits_fullTokens() public {
|
|
125
|
-
_deployFeeProject(5000);
|
|
126
|
-
(uint256 revnetId,) = _deployRevnetWith721(5000);
|
|
127
|
-
_setupPool(revnetId, 10_000 ether);
|
|
128
|
-
|
|
129
|
-
vm.prank(PAYER);
|
|
130
|
-
uint256 tokensReceived = jbMultiTerminal().pay{value: 1 ether}({
|
|
131
|
-
projectId: revnetId,
|
|
132
|
-
token: JBConstants.NATIVE_TOKEN,
|
|
133
|
-
amount: 1 ether,
|
|
134
|
-
beneficiary: PAYER,
|
|
135
|
-
minReturnedTokens: 0,
|
|
136
|
-
memo: "Fork: no split baseline",
|
|
137
|
-
metadata: ""
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
uint256 expectedTokens = 1000e18;
|
|
141
|
-
assertEq(tokensReceived, expectedTokens, "no splits: should receive 1000 tokens");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/// @notice Invariant: tokens / projectAmount rate is identical with and without splits.
|
|
145
|
-
function test_fork_invariant_tokenPerEthConsistent() public {
|
|
146
|
-
_deployFeeProject(5000);
|
|
147
|
-
|
|
148
|
-
// --- Revnet 1: with 721 splits (30%) ---
|
|
149
|
-
(uint256 revnetId1, IJB721TiersHook hook1) = _deployRevnetWith721(5000);
|
|
150
|
-
_setupPool(revnetId1, 10_000 ether);
|
|
151
|
-
|
|
152
|
-
address metadataTarget1 = hook1.METADATA_ID_TARGET();
|
|
153
|
-
bytes memory metadata1 = _buildPayMetadataNoQuote(metadataTarget1);
|
|
154
|
-
|
|
155
|
-
vm.prank(PAYER);
|
|
156
|
-
uint256 tokens1 = jbMultiTerminal().pay{value: 1 ether}({
|
|
157
|
-
projectId: revnetId1,
|
|
158
|
-
token: JBConstants.NATIVE_TOKEN,
|
|
159
|
-
amount: 1 ether,
|
|
160
|
-
beneficiary: PAYER,
|
|
161
|
-
minReturnedTokens: 0,
|
|
162
|
-
memo: "invariant: with splits",
|
|
163
|
-
metadata: metadata1
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
// --- Revnet 2: no splits (plain payment, no tier metadata) ---
|
|
167
|
-
uint256 revnetId2 = _deployRevnet(5000);
|
|
168
|
-
_setupPool(revnetId2, 10_000 ether);
|
|
169
|
-
|
|
170
|
-
vm.prank(PAYER);
|
|
171
|
-
uint256 tokens2 = jbMultiTerminal().pay{value: 1 ether}({
|
|
172
|
-
projectId: revnetId2,
|
|
173
|
-
token: JBConstants.NATIVE_TOKEN,
|
|
174
|
-
amount: 1 ether,
|
|
175
|
-
beneficiary: PAYER,
|
|
176
|
-
minReturnedTokens: 0,
|
|
177
|
-
memo: "invariant: no splits",
|
|
178
|
-
metadata: ""
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
uint256 projectAmount1 = 0.7 ether;
|
|
182
|
-
uint256 projectAmount2 = 1 ether;
|
|
183
|
-
|
|
184
|
-
uint256 rate1 = (tokens1 * 1e18) / projectAmount1;
|
|
185
|
-
uint256 rate2 = (tokens2 * 1e18) / projectAmount2;
|
|
186
|
-
|
|
187
|
-
assertEq(rate1, rate2, "token-per-ETH rate should be identical with and without splits");
|
|
188
|
-
}
|
|
189
|
-
}
|