@rev-net/core-v6 0.0.7 → 0.0.9
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/ADMINISTRATION.md +186 -0
- package/ARCHITECTURE.md +87 -0
- package/README.md +4 -2
- package/RISKS.md +49 -0
- package/SKILLS.md +22 -2
- package/STYLE_GUIDE.md +482 -0
- package/foundry.toml +6 -6
- package/package.json +13 -10
- package/script/Deploy.s.sol +3 -2
- package/src/REVDeployer.sol +129 -72
- package/src/REVLoans.sol +174 -165
- package/src/interfaces/IREVDeployer.sol +111 -72
- package/src/interfaces/IREVLoans.sol +116 -76
- package/src/structs/REV721TiersHookFlags.sol +14 -0
- package/src/structs/REVBaseline721HookConfig.sol +27 -0
- package/src/structs/REVDeploy721TiersHookConfig.sol +2 -2
- package/test/REV.integrations.t.sol +4 -3
- package/test/REVAutoIssuanceFuzz.t.sol +12 -8
- package/test/REVDeployerAuditRegressions.t.sol +4 -3
- package/test/REVInvincibility.t.sol +8 -6
- package/test/REVInvincibilityHandler.sol +1 -0
- package/test/REVLifecycle.t.sol +4 -3
- package/test/REVLoans.invariants.t.sol +5 -3
- package/test/REVLoansAttacks.t.sol +4 -3
- package/test/REVLoansAuditRegressions.t.sol +13 -24
- package/test/REVLoansFeeRecovery.t.sol +4 -3
- package/test/REVLoansSourced.t.sol +4 -3
- package/test/REVLoansUnSourced.t.sol +4 -3
- package/test/REVLoans_AuditFindings.t.sol +644 -0
- package/test/TestEmptyBuybackSpecs.t.sol +4 -3
- package/test/TestPR09_ConversionDocumentation.t.sol +4 -3
- package/test/TestPR10_LiquidationBehavior.t.sol +4 -3
- package/test/TestPR11_LowFindings.t.sol +4 -3
- package/test/TestPR12_FlashLoanSurplus.t.sol +4 -3
- package/test/TestPR13_CrossSourceReallocation.t.sol +4 -3
- package/test/TestPR15_CashOutCallerValidation.t.sol +4 -3
- package/test/TestPR16_ZeroRepayment.t.sol +4 -3
- package/test/TestPR21_Uint112Overflow.t.sol +4 -3
- package/test/TestPR22_HookArrayOOB.t.sol +4 -3
- package/test/TestPR26_BurnHeldTokens.t.sol +4 -3
- package/test/TestPR27_CEIPattern.t.sol +4 -3
- package/test/TestPR29_SwapTerminalPermission.t.sol +4 -3
- package/test/TestPR32_MixedFixes.t.sol +4 -3
- package/test/TestSplitWeightAdjustment.t.sol +445 -0
- package/test/TestSplitWeightE2E.t.sol +528 -0
- package/test/TestSplitWeightFork.t.sol +821 -0
- package/test/TestStageTransitionBorrowable.t.sol +4 -3
- package/test/fork/ForkTestBase.sol +617 -0
- package/test/fork/TestCashOutFork.t.sol +245 -0
- package/test/fork/TestLoanBorrowFork.t.sol +163 -0
- package/test/fork/TestLoanLiquidationFork.t.sol +129 -0
- package/test/fork/TestLoanReallocateFork.t.sol +103 -0
- package/test/fork/TestLoanRepayFork.t.sol +184 -0
- package/test/fork/TestSplitWeightFork.t.sol +186 -0
- package/test/mock/MockBuybackDataHook.sol +11 -4
- package/test/mock/MockBuybackDataHookMintPath.sol +11 -3
- package/test/regression/TestI20_CumulativeLoanCounter.t.sol +6 -5
- package/test/regression/TestL27_LiquidateGapHandling.t.sol +7 -6
- package/SECURITY.md +0 -68
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
import "forge-std/Test.sol";
|
|
5
|
+
import /* {*} from */ "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
6
|
+
import /* {*} from */ "./../src/REVDeployer.sol";
|
|
7
|
+
import "@croptop/core-v6/src/CTPublisher.sol";
|
|
8
|
+
import {MockBuybackDataHookMintPath} from "./mock/MockBuybackDataHookMintPath.sol";
|
|
9
|
+
import {MockBuybackDataHook} from "./mock/MockBuybackDataHook.sol";
|
|
10
|
+
import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
|
|
11
|
+
import "@bananapus/721-hook-v6/script/helpers/Hook721DeploymentLib.sol";
|
|
12
|
+
import "@bananapus/suckers-v6/script/helpers/SuckerDeploymentLib.sol";
|
|
13
|
+
import "@croptop/core-v6/script/helpers/CroptopDeploymentLib.sol";
|
|
14
|
+
import "@bananapus/router-terminal-v6/script/helpers/RouterTerminalDeploymentLib.sol";
|
|
15
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
16
|
+
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
17
|
+
import {REVLoans} from "../src/REVLoans.sol";
|
|
18
|
+
import {REVStageConfig, REVAutoIssuance} from "../src/structs/REVStageConfig.sol";
|
|
19
|
+
import {REVLoanSource} from "../src/structs/REVLoanSource.sol";
|
|
20
|
+
import {REVDescription} from "../src/structs/REVDescription.sol";
|
|
21
|
+
import {IREVLoans} from "./../src/interfaces/IREVLoans.sol";
|
|
22
|
+
import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
|
|
23
|
+
import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
|
|
24
|
+
import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
|
|
25
|
+
import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
|
|
26
|
+
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
27
|
+
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
28
|
+
import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
|
|
29
|
+
import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
|
|
30
|
+
import {IJBBuybackHookRegistry} from "@bananapus/buyback-hook-v6/src/interfaces/IJBBuybackHookRegistry.sol";
|
|
31
|
+
import {IJBPayHook} from "@bananapus/core-v6/src/interfaces/IJBPayHook.sol";
|
|
32
|
+
import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
|
|
33
|
+
import {JBPayHookSpecification} from "@bananapus/core-v6/src/structs/JBPayHookSpecification.sol";
|
|
34
|
+
import {JBTokenAmount} from "@bananapus/core-v6/src/structs/JBTokenAmount.sol";
|
|
35
|
+
|
|
36
|
+
/// @notice Tests for the split weight adjustment in REVDeployer.beforePayRecordedWith.
|
|
37
|
+
contract TestSplitWeightAdjustment is TestBaseWorkflow {
|
|
38
|
+
bytes32 REV_DEPLOYER_SALT = "REVDeployer_SWA";
|
|
39
|
+
|
|
40
|
+
REVDeployer REV_DEPLOYER;
|
|
41
|
+
JB721TiersHook EXAMPLE_HOOK;
|
|
42
|
+
IJB721TiersHookDeployer HOOK_DEPLOYER;
|
|
43
|
+
IJB721TiersHookStore HOOK_STORE;
|
|
44
|
+
IJBAddressRegistry ADDRESS_REGISTRY;
|
|
45
|
+
IREVLoans LOANS_CONTRACT;
|
|
46
|
+
IJBSuckerRegistry SUCKER_REGISTRY;
|
|
47
|
+
CTPublisher PUBLISHER;
|
|
48
|
+
MockBuybackDataHookMintPath MOCK_BUYBACK;
|
|
49
|
+
|
|
50
|
+
uint256 FEE_PROJECT_ID;
|
|
51
|
+
|
|
52
|
+
address private constant TRUSTED_FORWARDER = 0xB2b5841DBeF766d4b521221732F9B618fCf34A87;
|
|
53
|
+
address USER = makeAddr("user");
|
|
54
|
+
|
|
55
|
+
function setUp() public override {
|
|
56
|
+
super.setUp();
|
|
57
|
+
FEE_PROJECT_ID = jbProjects().createFor(multisig());
|
|
58
|
+
SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
|
|
59
|
+
HOOK_STORE = new JB721TiersHookStore();
|
|
60
|
+
EXAMPLE_HOOK =
|
|
61
|
+
new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, jbSplits(), multisig());
|
|
62
|
+
ADDRESS_REGISTRY = new JBAddressRegistry();
|
|
63
|
+
HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
|
|
64
|
+
PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
|
|
65
|
+
MOCK_BUYBACK = new MockBuybackDataHookMintPath();
|
|
66
|
+
LOANS_CONTRACT = new REVLoans({
|
|
67
|
+
controller: jbController(),
|
|
68
|
+
projects: jbProjects(),
|
|
69
|
+
revId: FEE_PROJECT_ID,
|
|
70
|
+
owner: address(this),
|
|
71
|
+
permit2: permit2(),
|
|
72
|
+
trustedForwarder: TRUSTED_FORWARDER
|
|
73
|
+
});
|
|
74
|
+
REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
|
|
75
|
+
jbController(),
|
|
76
|
+
SUCKER_REGISTRY,
|
|
77
|
+
FEE_PROJECT_ID,
|
|
78
|
+
HOOK_DEPLOYER,
|
|
79
|
+
PUBLISHER,
|
|
80
|
+
IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
|
|
81
|
+
address(LOANS_CONTRACT),
|
|
82
|
+
TRUSTED_FORWARDER
|
|
83
|
+
);
|
|
84
|
+
vm.prank(multisig());
|
|
85
|
+
jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function _buildMinimalConfig()
|
|
89
|
+
internal
|
|
90
|
+
view
|
|
91
|
+
returns (REVConfig memory cfg, JBTerminalConfig[] memory tc, REVSuckerDeploymentConfig memory sdc)
|
|
92
|
+
{
|
|
93
|
+
JBAccountingContext[] memory acc = new JBAccountingContext[](1);
|
|
94
|
+
acc[0] = JBAccountingContext({
|
|
95
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
96
|
+
});
|
|
97
|
+
tc = new JBTerminalConfig[](1);
|
|
98
|
+
tc[0] = JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: acc});
|
|
99
|
+
|
|
100
|
+
REVStageConfig[] memory stages = new REVStageConfig[](1);
|
|
101
|
+
JBSplit[] memory splits = new JBSplit[](1);
|
|
102
|
+
splits[0].beneficiary = payable(multisig());
|
|
103
|
+
splits[0].percent = 10_000;
|
|
104
|
+
stages[0] = REVStageConfig({
|
|
105
|
+
startsAtOrAfter: uint40(block.timestamp),
|
|
106
|
+
autoIssuances: new REVAutoIssuance[](0),
|
|
107
|
+
splitPercent: 0,
|
|
108
|
+
splits: splits,
|
|
109
|
+
initialIssuance: uint112(1000e18),
|
|
110
|
+
issuanceCutFrequency: 0,
|
|
111
|
+
issuanceCutPercent: 0,
|
|
112
|
+
cashOutTaxRate: 5000,
|
|
113
|
+
extraMetadata: 0
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
cfg = REVConfig({
|
|
117
|
+
description: REVDescription("Test", "TST", "ipfs://test", "TEST_SALT"),
|
|
118
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
119
|
+
splitOperator: multisig(),
|
|
120
|
+
stageConfigurations: stages
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
sdc = REVSuckerDeploymentConfig({
|
|
124
|
+
deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256(abi.encodePacked("TEST"))
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function _deployRevnet() internal returns (uint256 revnetId) {
|
|
129
|
+
// Deploy fee project first.
|
|
130
|
+
(REVConfig memory feeCfg, JBTerminalConfig[] memory feeTc, REVSuckerDeploymentConfig memory feeSdc) =
|
|
131
|
+
_buildMinimalConfig();
|
|
132
|
+
vm.prank(multisig());
|
|
133
|
+
REV_DEPLOYER.deployFor({
|
|
134
|
+
revnetId: FEE_PROJECT_ID,
|
|
135
|
+
configuration: feeCfg,
|
|
136
|
+
terminalConfigurations: feeTc,
|
|
137
|
+
suckerDeploymentConfiguration: feeSdc
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Deploy the revnet.
|
|
141
|
+
(REVConfig memory cfg, JBTerminalConfig[] memory tc, REVSuckerDeploymentConfig memory sdc) =
|
|
142
|
+
_buildMinimalConfig();
|
|
143
|
+
cfg.description = REVDescription("Test2", "TS2", "ipfs://test2", "TEST_SALT_2");
|
|
144
|
+
revnetId = REV_DEPLOYER.deployFor({
|
|
145
|
+
revnetId: 0, configuration: cfg, terminalConfigurations: tc, suckerDeploymentConfiguration: sdc
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// @notice No 721 hook = no split adjustment, weight from buyback passes through.
|
|
150
|
+
function test_beforePay_no721_noAdjustment() public {
|
|
151
|
+
uint256 revnetId = _deployRevnet();
|
|
152
|
+
|
|
153
|
+
JBBeforePayRecordedContext memory context = JBBeforePayRecordedContext({
|
|
154
|
+
terminal: address(jbMultiTerminal()),
|
|
155
|
+
payer: USER,
|
|
156
|
+
amount: JBTokenAmount({
|
|
157
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
158
|
+
value: 1 ether,
|
|
159
|
+
decimals: 18,
|
|
160
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
161
|
+
}),
|
|
162
|
+
projectId: revnetId,
|
|
163
|
+
rulesetId: 0,
|
|
164
|
+
beneficiary: USER,
|
|
165
|
+
weight: 1000e18,
|
|
166
|
+
reservedPercent: 0,
|
|
167
|
+
metadata: ""
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// No 721 hook deployed, buyback returns context.weight with empty specs.
|
|
171
|
+
(uint256 weight, JBPayHookSpecification[] memory specs) = REV_DEPLOYER.beforePayRecordedWith(context);
|
|
172
|
+
|
|
173
|
+
assertEq(weight, context.weight, "weight should pass through unchanged");
|
|
174
|
+
assertEq(specs.length, 0, "no specs when no 721 hook and empty buyback");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// @notice When 721 hook returns splits, weight is adjusted proportionally.
|
|
178
|
+
function test_beforePay_splitAdjustsWeight() public {
|
|
179
|
+
uint256 revnetId = _deployRevnet();
|
|
180
|
+
|
|
181
|
+
// Mock a 721 hook for this project.
|
|
182
|
+
address mock721 = makeAddr("mock721");
|
|
183
|
+
vm.etch(mock721, bytes("0x01"));
|
|
184
|
+
|
|
185
|
+
// Store the mock 721 hook.
|
|
186
|
+
// tiered721HookOf is internal, so we use vm.store.
|
|
187
|
+
// Slot for tiered721HookOf[revnetId]: keccak256(abi.encode(revnetId, slot))
|
|
188
|
+
// Need to find the storage slot for tiered721HookOf mapping.
|
|
189
|
+
bytes32 slot = keccak256(abi.encode(revnetId, uint256(3))); // slot 9 for tiered721HookOf in REVDeployer
|
|
190
|
+
vm.store(address(REV_DEPLOYER), slot, bytes32(uint256(uint160(mock721))));
|
|
191
|
+
|
|
192
|
+
// Verify the store worked.
|
|
193
|
+
assertEq(address(REV_DEPLOYER.tiered721HookOf(revnetId)), mock721, "721 hook stored");
|
|
194
|
+
|
|
195
|
+
// Mock 721 hook returning 0.3 ETH split on 1 ETH payment.
|
|
196
|
+
JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
|
|
197
|
+
hookSpecs[0] = JBPayHookSpecification({hook: IJBPayHook(mock721), amount: 0.3 ether, metadata: bytes("")});
|
|
198
|
+
vm.mockCall(
|
|
199
|
+
mock721,
|
|
200
|
+
abi.encodeWithSelector(IJBRulesetDataHook.beforePayRecordedWith.selector),
|
|
201
|
+
abi.encode(uint256(700e18), hookSpecs)
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
JBBeforePayRecordedContext memory context = JBBeforePayRecordedContext({
|
|
205
|
+
terminal: address(jbMultiTerminal()),
|
|
206
|
+
payer: USER,
|
|
207
|
+
amount: JBTokenAmount({
|
|
208
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
209
|
+
value: 1 ether,
|
|
210
|
+
decimals: 18,
|
|
211
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
212
|
+
}),
|
|
213
|
+
projectId: revnetId,
|
|
214
|
+
rulesetId: 0,
|
|
215
|
+
beneficiary: USER,
|
|
216
|
+
weight: 1000e18,
|
|
217
|
+
reservedPercent: 0,
|
|
218
|
+
metadata: ""
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
(uint256 weight,) = REV_DEPLOYER.beforePayRecordedWith(context);
|
|
222
|
+
|
|
223
|
+
// Buyback returns context.weight (1000e18) since mock buyback passes through.
|
|
224
|
+
// Weight adjusted for 0.3 ETH split on 1 ETH: 1000e18 * 0.7 = 700e18.
|
|
225
|
+
assertEq(weight, 700e18, "weight = buybackWeight * (amount - split) / amount");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/// @notice When 721 splits take the full amount, weight is zero.
|
|
229
|
+
function test_beforePay_fullSplit_weightZero() public {
|
|
230
|
+
uint256 revnetId = _deployRevnet();
|
|
231
|
+
|
|
232
|
+
address mock721 = makeAddr("mock721_full");
|
|
233
|
+
vm.etch(mock721, bytes("0x01"));
|
|
234
|
+
bytes32 slot = keccak256(abi.encode(revnetId, uint256(3)));
|
|
235
|
+
vm.store(address(REV_DEPLOYER), slot, bytes32(uint256(uint160(mock721))));
|
|
236
|
+
|
|
237
|
+
// Mock 721 hook returning full 1 ETH split.
|
|
238
|
+
JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
|
|
239
|
+
hookSpecs[0] = JBPayHookSpecification({hook: IJBPayHook(mock721), amount: 1 ether, metadata: bytes("")});
|
|
240
|
+
vm.mockCall(
|
|
241
|
+
mock721,
|
|
242
|
+
abi.encodeWithSelector(IJBRulesetDataHook.beforePayRecordedWith.selector),
|
|
243
|
+
abi.encode(uint256(0), hookSpecs)
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
JBBeforePayRecordedContext memory context = JBBeforePayRecordedContext({
|
|
247
|
+
terminal: address(jbMultiTerminal()),
|
|
248
|
+
payer: USER,
|
|
249
|
+
amount: JBTokenAmount({
|
|
250
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
251
|
+
value: 1 ether,
|
|
252
|
+
decimals: 18,
|
|
253
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
254
|
+
}),
|
|
255
|
+
projectId: revnetId,
|
|
256
|
+
rulesetId: 0,
|
|
257
|
+
beneficiary: USER,
|
|
258
|
+
weight: 1000e18,
|
|
259
|
+
reservedPercent: 0,
|
|
260
|
+
metadata: ""
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
(uint256 weight,) = REV_DEPLOYER.beforePayRecordedWith(context);
|
|
264
|
+
|
|
265
|
+
assertEq(weight, 0, "full split = zero weight");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/// @notice 721 with splits + buyback (AMM swap path) — weight adjusted, both specs present.
|
|
269
|
+
function test_beforePay_splitPlusBuybackAMM_correctWeight() public {
|
|
270
|
+
// Deploy with the AMM-path buyback hook.
|
|
271
|
+
(REVConfig memory feeCfg, JBTerminalConfig[] memory feeTc, REVSuckerDeploymentConfig memory feeSdc) =
|
|
272
|
+
_buildMinimalConfig();
|
|
273
|
+
vm.prank(multisig());
|
|
274
|
+
jbProjects().approve(address(0), FEE_PROJECT_ID); // clear old approval
|
|
275
|
+
|
|
276
|
+
// Deploy a new REVDeployer with the AMM buyback mock.
|
|
277
|
+
MockBuybackDataHook ammBuyback = new MockBuybackDataHook();
|
|
278
|
+
REVDeployer ammDeployer = new REVDeployer{salt: "REVDeployer_AMM"}(
|
|
279
|
+
jbController(),
|
|
280
|
+
SUCKER_REGISTRY,
|
|
281
|
+
FEE_PROJECT_ID,
|
|
282
|
+
HOOK_DEPLOYER,
|
|
283
|
+
PUBLISHER,
|
|
284
|
+
IJBBuybackHookRegistry(address(ammBuyback)),
|
|
285
|
+
address(LOANS_CONTRACT),
|
|
286
|
+
TRUSTED_FORWARDER
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
vm.prank(multisig());
|
|
290
|
+
jbProjects().approve(address(ammDeployer), FEE_PROJECT_ID);
|
|
291
|
+
|
|
292
|
+
vm.prank(multisig());
|
|
293
|
+
ammDeployer.deployFor({
|
|
294
|
+
revnetId: FEE_PROJECT_ID,
|
|
295
|
+
configuration: feeCfg,
|
|
296
|
+
terminalConfigurations: feeTc,
|
|
297
|
+
suckerDeploymentConfiguration: feeSdc
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
(REVConfig memory cfg, JBTerminalConfig[] memory tc, REVSuckerDeploymentConfig memory sdc) =
|
|
301
|
+
_buildMinimalConfig();
|
|
302
|
+
cfg.description = REVDescription("AMM", "AMM", "ipfs://amm", "AMM_SALT");
|
|
303
|
+
uint256 revnetId = ammDeployer.deployFor({
|
|
304
|
+
revnetId: 0, configuration: cfg, terminalConfigurations: tc, suckerDeploymentConfiguration: sdc
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Mock a 721 hook for this project.
|
|
308
|
+
address mock721 = makeAddr("mock721_amm");
|
|
309
|
+
vm.etch(mock721, bytes("0x01"));
|
|
310
|
+
bytes32 slot = keccak256(abi.encode(revnetId, uint256(3)));
|
|
311
|
+
vm.store(address(ammDeployer), slot, bytes32(uint256(uint160(mock721))));
|
|
312
|
+
|
|
313
|
+
// Mock 721 hook returning 0.4 ETH split on 1 ETH payment.
|
|
314
|
+
JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
|
|
315
|
+
hookSpecs[0] = JBPayHookSpecification({hook: IJBPayHook(mock721), amount: 0.4 ether, metadata: bytes("")});
|
|
316
|
+
vm.mockCall(
|
|
317
|
+
mock721,
|
|
318
|
+
abi.encodeWithSelector(IJBRulesetDataHook.beforePayRecordedWith.selector),
|
|
319
|
+
abi.encode(uint256(600e18), hookSpecs)
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
JBBeforePayRecordedContext memory context = JBBeforePayRecordedContext({
|
|
323
|
+
terminal: address(jbMultiTerminal()),
|
|
324
|
+
payer: USER,
|
|
325
|
+
amount: JBTokenAmount({
|
|
326
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
327
|
+
value: 1 ether,
|
|
328
|
+
decimals: 18,
|
|
329
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
330
|
+
}),
|
|
331
|
+
projectId: revnetId,
|
|
332
|
+
rulesetId: 0,
|
|
333
|
+
beneficiary: USER,
|
|
334
|
+
weight: 1000e18,
|
|
335
|
+
reservedPercent: 0,
|
|
336
|
+
metadata: ""
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
(uint256 weight, JBPayHookSpecification[] memory specs) = ammDeployer.beforePayRecordedWith(context);
|
|
340
|
+
|
|
341
|
+
// AMM buyback returns context.weight (passes through the reduced weight from context).
|
|
342
|
+
// The buyback mock receives a reduced context with 0.6 ETH and returns that weight.
|
|
343
|
+
// Then weight is adjusted: weight * 0.6 = 600e18.
|
|
344
|
+
assertEq(weight, 600e18, "weight = buybackWeight * (amount - split) / amount");
|
|
345
|
+
|
|
346
|
+
// Specs: [721 hook with split, buyback spec].
|
|
347
|
+
assertEq(specs.length, 2, "721 spec + buyback spec");
|
|
348
|
+
assertEq(address(specs[0].hook), mock721, "first = 721 hook");
|
|
349
|
+
assertEq(specs[0].amount, 0.4 ether, "721 split amount preserved");
|
|
350
|
+
assertEq(address(specs[1].hook), address(ammBuyback), "second = buyback hook");
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/// @notice 721 with splits + buyback (mint path, no AMM trigger) — weight adjusted, only 721 spec.
|
|
354
|
+
function test_beforePay_splitPlusBuybackMintPath_correctWeight() public {
|
|
355
|
+
uint256 revnetId = _deployRevnet();
|
|
356
|
+
|
|
357
|
+
address mock721 = makeAddr("mock721_mint");
|
|
358
|
+
vm.etch(mock721, bytes("0x01"));
|
|
359
|
+
bytes32 slot = keccak256(abi.encode(revnetId, uint256(3)));
|
|
360
|
+
vm.store(address(REV_DEPLOYER), slot, bytes32(uint256(uint160(mock721))));
|
|
361
|
+
|
|
362
|
+
// Mock 721 hook returning 0.2 ETH split.
|
|
363
|
+
JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
|
|
364
|
+
hookSpecs[0] = JBPayHookSpecification({hook: IJBPayHook(mock721), amount: 0.2 ether, metadata: bytes("")});
|
|
365
|
+
vm.mockCall(
|
|
366
|
+
mock721,
|
|
367
|
+
abi.encodeWithSelector(IJBRulesetDataHook.beforePayRecordedWith.selector),
|
|
368
|
+
abi.encode(uint256(800e18), hookSpecs)
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
JBBeforePayRecordedContext memory context = JBBeforePayRecordedContext({
|
|
372
|
+
terminal: address(jbMultiTerminal()),
|
|
373
|
+
payer: USER,
|
|
374
|
+
amount: JBTokenAmount({
|
|
375
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
376
|
+
value: 1 ether,
|
|
377
|
+
decimals: 18,
|
|
378
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
379
|
+
}),
|
|
380
|
+
projectId: revnetId,
|
|
381
|
+
rulesetId: 0,
|
|
382
|
+
beneficiary: USER,
|
|
383
|
+
weight: 1000e18,
|
|
384
|
+
reservedPercent: 0,
|
|
385
|
+
metadata: ""
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
(uint256 weight, JBPayHookSpecification[] memory specs) = REV_DEPLOYER.beforePayRecordedWith(context);
|
|
389
|
+
|
|
390
|
+
// Buyback mint path: returns context.weight (1000e18 from reduced context = 0.8 ETH context).
|
|
391
|
+
// Actually MockBuybackDataHookMintPath returns context.weight with empty specs.
|
|
392
|
+
// Weight adjusted: 1000e18 * 0.8 = 800e18.
|
|
393
|
+
assertEq(weight, 800e18, "weight adjusted for split with mint-path buyback");
|
|
394
|
+
|
|
395
|
+
// Only 721 spec (buyback mint path returns empty).
|
|
396
|
+
assertEq(specs.length, 1, "only 721 spec (buyback empty)");
|
|
397
|
+
assertEq(address(specs[0].hook), mock721, "spec = 721 hook");
|
|
398
|
+
assertEq(specs[0].amount, 0.2 ether, "split amount");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/// @notice Splits forward actual 721 hook specs (not hardcoded amount: 0).
|
|
402
|
+
function test_beforePay_splitForwardsActualSpecs() public {
|
|
403
|
+
uint256 revnetId = _deployRevnet();
|
|
404
|
+
|
|
405
|
+
address mock721 = makeAddr("mock721_specs");
|
|
406
|
+
vm.etch(mock721, bytes("0x01"));
|
|
407
|
+
bytes32 slot = keccak256(abi.encode(revnetId, uint256(3)));
|
|
408
|
+
vm.store(address(REV_DEPLOYER), slot, bytes32(uint256(uint160(mock721))));
|
|
409
|
+
|
|
410
|
+
// Mock 721 hook returning split amount with metadata.
|
|
411
|
+
bytes memory splitMeta = abi.encode(uint256(42));
|
|
412
|
+
JBPayHookSpecification[] memory hookSpecs = new JBPayHookSpecification[](1);
|
|
413
|
+
hookSpecs[0] = JBPayHookSpecification({hook: IJBPayHook(mock721), amount: 0.5 ether, metadata: splitMeta});
|
|
414
|
+
vm.mockCall(
|
|
415
|
+
mock721,
|
|
416
|
+
abi.encodeWithSelector(IJBRulesetDataHook.beforePayRecordedWith.selector),
|
|
417
|
+
abi.encode(uint256(500e18), hookSpecs)
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
JBBeforePayRecordedContext memory context = JBBeforePayRecordedContext({
|
|
421
|
+
terminal: address(jbMultiTerminal()),
|
|
422
|
+
payer: USER,
|
|
423
|
+
amount: JBTokenAmount({
|
|
424
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
425
|
+
value: 1 ether,
|
|
426
|
+
decimals: 18,
|
|
427
|
+
currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
428
|
+
}),
|
|
429
|
+
projectId: revnetId,
|
|
430
|
+
rulesetId: 0,
|
|
431
|
+
beneficiary: USER,
|
|
432
|
+
weight: 1000e18,
|
|
433
|
+
reservedPercent: 0,
|
|
434
|
+
metadata: ""
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
(, JBPayHookSpecification[] memory specs) = REV_DEPLOYER.beforePayRecordedWith(context);
|
|
438
|
+
|
|
439
|
+
// Should have 721 hook spec (buyback empty).
|
|
440
|
+
assertEq(specs.length, 1, "should have 1 spec (721 hook, buyback empty)");
|
|
441
|
+
assertEq(address(specs[0].hook), mock721, "spec points to 721 hook");
|
|
442
|
+
assertEq(specs[0].amount, 0.5 ether, "split amount forwarded");
|
|
443
|
+
assertEq(specs[0].metadata, splitMeta, "split metadata forwarded");
|
|
444
|
+
}
|
|
445
|
+
}
|