@rev-net/core-v6 0.0.17 → 0.0.19
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 +14 -4
- package/ARCHITECTURE.md +14 -10
- package/AUDIT_INSTRUCTIONS.md +40 -17
- package/CHANGE_LOG.md +87 -0
- package/README.md +10 -5
- package/RISKS.md +15 -10
- package/SKILLS.md +31 -15
- package/USER_JOURNEYS.md +16 -12
- package/foundry.toml +1 -1
- package/package.json +8 -8
- package/script/Deploy.s.sol +60 -19
- package/src/REVDeployer.sol +21 -303
- package/src/REVLoans.sol +31 -0
- package/src/REVOwner.sol +430 -0
- package/src/interfaces/IREVDeployer.sol +4 -10
- package/src/interfaces/IREVOwner.sol +10 -0
- package/src/structs/REVBaseline721HookConfig.sol +0 -2
- package/test/REV.integrations.t.sol +14 -1
- package/test/REVAutoIssuanceFuzz.t.sol +14 -1
- package/test/REVDeployerRegressions.t.sol +17 -2
- package/test/REVInvincibility.t.sol +31 -3
- package/test/REVLifecycle.t.sol +16 -1
- package/test/REVLoans.invariants.t.sol +16 -1
- package/test/REVLoansAttacks.t.sol +16 -1
- package/test/REVLoansFeeRecovery.t.sol +16 -1
- package/test/REVLoansFindings.t.sol +16 -1
- package/test/REVLoansRegressions.t.sol +16 -1
- package/test/REVLoansSourceFeeRecovery.t.sol +16 -1
- package/test/REVLoansSourced.t.sol +16 -1
- package/test/REVLoansUnSourced.t.sol +16 -1
- package/test/TestBurnHeldTokens.t.sol +16 -1
- package/test/TestCEIPattern.t.sol +16 -1
- package/test/TestCashOutCallerValidation.t.sol +19 -4
- package/test/TestConversionDocumentation.t.sol +16 -1
- package/test/TestCrossCurrencyReclaim.t.sol +16 -1
- package/test/TestCrossSourceReallocation.t.sol +16 -1
- package/test/TestERC2771MetaTx.t.sol +16 -1
- package/test/TestEmptyBuybackSpecs.t.sol +18 -3
- package/test/TestFlashLoanSurplus.t.sol +16 -1
- package/test/TestHookArrayOOB.t.sol +17 -2
- package/test/TestLiquidationBehavior.t.sol +16 -1
- package/test/TestLoanSourceRotation.t.sol +16 -1
- package/test/TestLoansCashOutDelay.t.sol +482 -0
- package/test/TestLongTailEconomics.t.sol +16 -1
- package/test/TestLowFindings.t.sol +16 -1
- package/test/TestMixedFixes.t.sol +16 -1
- package/test/TestPermit2Signatures.t.sol +16 -1
- package/test/TestReallocationSandwich.t.sol +16 -1
- package/test/TestRevnetRegressions.t.sol +16 -1
- package/test/TestSplitWeightAdjustment.t.sol +43 -19
- package/test/TestSplitWeightE2E.t.sol +26 -3
- package/test/TestSplitWeightFork.t.sol +16 -2
- package/test/TestStageTransitionBorrowable.t.sol +16 -1
- package/test/TestSwapTerminalPermission.t.sol +16 -1
- package/test/TestUint112Overflow.t.sol +16 -1
- package/test/TestZeroRepayment.t.sol +16 -1
- package/test/audit/LoanIdOverflowGuard.t.sol +16 -1
- package/test/fork/ForkTestBase.sol +16 -2
- package/test/fork/TestPermit2PaymentFork.t.sol +4 -3
- package/test/helpers/REVEmpty721Config.sol +0 -1
- package/test/regression/TestBurnPermissionRequired.t.sol +16 -1
- package/test/regression/TestCashOutBuybackFeeLeak.t.sol +15 -1
- package/test/regression/TestCrossRevnetLiquidation.t.sol +16 -1
- package/test/regression/TestCumulativeLoanCounter.t.sol +16 -1
- package/test/regression/TestLiquidateGapHandling.t.sol +16 -1
- package/test/regression/TestZeroPriceFeed.t.sol +16 -1
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
5
|
+
import "forge-std/Test.sol";
|
|
6
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
7
|
+
import /* {*} from */ "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
8
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
9
|
+
import /* {*} from */ "./../src/REVDeployer.sol";
|
|
10
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
11
|
+
import "@croptop/core-v6/src/CTPublisher.sol";
|
|
12
|
+
import {MockBuybackDataHook} from "./mock/MockBuybackDataHook.sol";
|
|
13
|
+
|
|
14
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
15
|
+
import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
|
|
16
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
17
|
+
import "@bananapus/721-hook-v6/script/helpers/Hook721DeploymentLib.sol";
|
|
18
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
19
|
+
import "@bananapus/suckers-v6/script/helpers/SuckerDeploymentLib.sol";
|
|
20
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
21
|
+
import "@croptop/core-v6/script/helpers/CroptopDeploymentLib.sol";
|
|
22
|
+
// forge-lint: disable-next-line(unaliased-plain-import)
|
|
23
|
+
import "@bananapus/router-terminal-v6/script/helpers/RouterTerminalDeploymentLib.sol";
|
|
24
|
+
|
|
25
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
26
|
+
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
27
|
+
import {REVLoans} from "../src/REVLoans.sol";
|
|
28
|
+
import {REVStageConfig, REVAutoIssuance} from "../src/structs/REVStageConfig.sol";
|
|
29
|
+
import {REVLoanSource} from "../src/structs/REVLoanSource.sol";
|
|
30
|
+
import {REVDescription} from "../src/structs/REVDescription.sol";
|
|
31
|
+
import {IREVLoans} from "./../src/interfaces/IREVLoans.sol";
|
|
32
|
+
import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
|
|
33
|
+
import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
|
|
34
|
+
import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
|
|
35
|
+
import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
|
|
36
|
+
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
37
|
+
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
38
|
+
import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
|
|
39
|
+
import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
|
|
40
|
+
import {REVOwner} from "../src/REVOwner.sol";
|
|
41
|
+
import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
|
|
42
|
+
|
|
43
|
+
struct FeeProjectConfig {
|
|
44
|
+
REVConfig configuration;
|
|
45
|
+
JBTerminalConfig[] terminalConfigurations;
|
|
46
|
+
REVSuckerDeploymentConfig suckerDeploymentConfiguration;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// @notice Tests that REVLoans enforces the cash out delay set by REVDeployer for cross-chain deployments.
|
|
50
|
+
contract TestLoansCashOutDelay is TestBaseWorkflow {
|
|
51
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
52
|
+
bytes32 REV_DEPLOYER_SALT = "REVDeployer";
|
|
53
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
54
|
+
bytes32 ERC20_SALT = "REV_TOKEN";
|
|
55
|
+
|
|
56
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
57
|
+
REVDeployer REV_DEPLOYER;
|
|
58
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
59
|
+
REVOwner REV_OWNER;
|
|
60
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
61
|
+
JB721TiersHook EXAMPLE_HOOK;
|
|
62
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
63
|
+
IJB721TiersHookDeployer HOOK_DEPLOYER;
|
|
64
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
65
|
+
IJB721TiersHookStore HOOK_STORE;
|
|
66
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
67
|
+
IJBAddressRegistry ADDRESS_REGISTRY;
|
|
68
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
69
|
+
IREVLoans LOANS_CONTRACT;
|
|
70
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
71
|
+
IJBSuckerRegistry SUCKER_REGISTRY;
|
|
72
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
73
|
+
CTPublisher PUBLISHER;
|
|
74
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
75
|
+
MockBuybackDataHook MOCK_BUYBACK;
|
|
76
|
+
|
|
77
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
78
|
+
uint256 FEE_PROJECT_ID;
|
|
79
|
+
|
|
80
|
+
/// @notice Revnet deployed with startsAtOrAfter in the past (triggers cash out delay).
|
|
81
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
82
|
+
uint256 DELAYED_REVNET_ID;
|
|
83
|
+
|
|
84
|
+
/// @notice Revnet deployed with startsAtOrAfter == block.timestamp (no delay).
|
|
85
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
86
|
+
uint256 NORMAL_REVNET_ID;
|
|
87
|
+
|
|
88
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
89
|
+
address USER = makeAddr("user");
|
|
90
|
+
|
|
91
|
+
address private constant TRUSTED_FORWARDER = 0xB2b5841DBeF766d4b521221732F9B618fCf34A87;
|
|
92
|
+
|
|
93
|
+
function getFeeProjectConfig() internal view returns (FeeProjectConfig memory) {
|
|
94
|
+
JBAccountingContext[] memory accountingContextsToAccept = new JBAccountingContext[](1);
|
|
95
|
+
accountingContextsToAccept[0] = JBAccountingContext({
|
|
96
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
JBTerminalConfig[] memory terminalConfigurations = new JBTerminalConfig[](1);
|
|
100
|
+
terminalConfigurations[0] =
|
|
101
|
+
JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: accountingContextsToAccept});
|
|
102
|
+
|
|
103
|
+
REVStageConfig[] memory stageConfigurations = new REVStageConfig[](1);
|
|
104
|
+
JBSplit[] memory splits = new JBSplit[](1);
|
|
105
|
+
splits[0].beneficiary = payable(multisig());
|
|
106
|
+
splits[0].percent = 10_000;
|
|
107
|
+
|
|
108
|
+
stageConfigurations[0] = REVStageConfig({
|
|
109
|
+
startsAtOrAfter: uint40(block.timestamp),
|
|
110
|
+
autoIssuances: new REVAutoIssuance[](0),
|
|
111
|
+
splitPercent: 2000,
|
|
112
|
+
splits: splits,
|
|
113
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
114
|
+
initialIssuance: uint112(1000e18),
|
|
115
|
+
issuanceCutFrequency: 90 days,
|
|
116
|
+
issuanceCutPercent: JBConstants.MAX_WEIGHT_CUT_PERCENT / 2,
|
|
117
|
+
cashOutTaxRate: 6000,
|
|
118
|
+
extraMetadata: 0
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return FeeProjectConfig({
|
|
122
|
+
configuration: REVConfig({
|
|
123
|
+
// forge-lint: disable-next-line(named-struct-fields)
|
|
124
|
+
description: REVDescription("Revnet", "$REV", "ipfs://fee", ERC20_SALT),
|
|
125
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
126
|
+
splitOperator: multisig(),
|
|
127
|
+
stageConfigurations: stageConfigurations
|
|
128
|
+
}),
|
|
129
|
+
terminalConfigurations: terminalConfigurations,
|
|
130
|
+
suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
|
|
131
|
+
deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256(abi.encodePacked("REV"))
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// @notice Returns a revnet config. When `pastStart` is true, `startsAtOrAfter` is set to 1 second ago,
|
|
137
|
+
/// triggering the 30-day cash out delay in REVDeployer._setCashOutDelayIfNeeded.
|
|
138
|
+
function _getRevnetConfig(
|
|
139
|
+
bool pastStart,
|
|
140
|
+
string memory name,
|
|
141
|
+
string memory symbol,
|
|
142
|
+
bytes32 salt
|
|
143
|
+
)
|
|
144
|
+
internal
|
|
145
|
+
view
|
|
146
|
+
returns (FeeProjectConfig memory)
|
|
147
|
+
{
|
|
148
|
+
JBAccountingContext[] memory accountingContextsToAccept = new JBAccountingContext[](1);
|
|
149
|
+
accountingContextsToAccept[0] = JBAccountingContext({
|
|
150
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
JBTerminalConfig[] memory terminalConfigurations = new JBTerminalConfig[](1);
|
|
154
|
+
terminalConfigurations[0] =
|
|
155
|
+
JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: accountingContextsToAccept});
|
|
156
|
+
|
|
157
|
+
REVStageConfig[] memory stageConfigurations = new REVStageConfig[](1);
|
|
158
|
+
JBSplit[] memory splits = new JBSplit[](1);
|
|
159
|
+
splits[0].beneficiary = payable(multisig());
|
|
160
|
+
splits[0].percent = 10_000;
|
|
161
|
+
|
|
162
|
+
// If pastStart, set startsAtOrAfter to 1 second ago — simulates cross-chain deployment
|
|
163
|
+
// where the stage is already active on another chain.
|
|
164
|
+
uint40 startsAt = pastStart ? uint40(block.timestamp - 1) : uint40(block.timestamp);
|
|
165
|
+
|
|
166
|
+
stageConfigurations[0] = REVStageConfig({
|
|
167
|
+
startsAtOrAfter: startsAt,
|
|
168
|
+
autoIssuances: new REVAutoIssuance[](0),
|
|
169
|
+
splitPercent: 2000,
|
|
170
|
+
splits: splits,
|
|
171
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
172
|
+
initialIssuance: uint112(1000e18),
|
|
173
|
+
issuanceCutFrequency: 90 days,
|
|
174
|
+
issuanceCutPercent: JBConstants.MAX_WEIGHT_CUT_PERCENT / 2,
|
|
175
|
+
cashOutTaxRate: 6000,
|
|
176
|
+
extraMetadata: 0
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return FeeProjectConfig({
|
|
180
|
+
configuration: REVConfig({
|
|
181
|
+
// forge-lint: disable-next-line(named-struct-fields)
|
|
182
|
+
description: REVDescription(name, symbol, "ipfs://test", salt),
|
|
183
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
184
|
+
splitOperator: multisig(),
|
|
185
|
+
stageConfigurations: stageConfigurations
|
|
186
|
+
}),
|
|
187
|
+
terminalConfigurations: terminalConfigurations,
|
|
188
|
+
suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
|
|
189
|
+
deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: salt
|
|
190
|
+
})
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function setUp() public override {
|
|
195
|
+
super.setUp();
|
|
196
|
+
|
|
197
|
+
// Warp to a realistic timestamp so startsAtOrAfter - 1 doesn't underflow.
|
|
198
|
+
vm.warp(1_700_000_000);
|
|
199
|
+
|
|
200
|
+
FEE_PROJECT_ID = jbProjects().createFor(multisig());
|
|
201
|
+
|
|
202
|
+
SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
|
|
203
|
+
HOOK_STORE = new JB721TiersHookStore();
|
|
204
|
+
EXAMPLE_HOOK = new JB721TiersHook(
|
|
205
|
+
jbDirectory(), jbPermissions(), jbPrices(), jbRulesets(), HOOK_STORE, jbSplits(), multisig()
|
|
206
|
+
);
|
|
207
|
+
ADDRESS_REGISTRY = new JBAddressRegistry();
|
|
208
|
+
HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
|
|
209
|
+
PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
|
|
210
|
+
MOCK_BUYBACK = new MockBuybackDataHook();
|
|
211
|
+
|
|
212
|
+
LOANS_CONTRACT = new REVLoans({
|
|
213
|
+
controller: jbController(),
|
|
214
|
+
projects: jbProjects(),
|
|
215
|
+
revId: FEE_PROJECT_ID,
|
|
216
|
+
owner: address(this),
|
|
217
|
+
permit2: permit2(),
|
|
218
|
+
trustedForwarder: TRUSTED_FORWARDER
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
REV_OWNER = new REVOwner(
|
|
222
|
+
IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
|
|
223
|
+
jbDirectory(),
|
|
224
|
+
FEE_PROJECT_ID,
|
|
225
|
+
SUCKER_REGISTRY,
|
|
226
|
+
address(LOANS_CONTRACT)
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
|
|
230
|
+
jbController(),
|
|
231
|
+
SUCKER_REGISTRY,
|
|
232
|
+
FEE_PROJECT_ID,
|
|
233
|
+
HOOK_DEPLOYER,
|
|
234
|
+
PUBLISHER,
|
|
235
|
+
IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
|
|
236
|
+
address(LOANS_CONTRACT),
|
|
237
|
+
TRUSTED_FORWARDER,
|
|
238
|
+
address(REV_OWNER)
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
|
|
242
|
+
|
|
243
|
+
// Approve the deployer to configure the fee project.
|
|
244
|
+
vm.prank(multisig());
|
|
245
|
+
jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
|
|
246
|
+
|
|
247
|
+
// Deploy the fee project.
|
|
248
|
+
FeeProjectConfig memory feeProjectConfig = getFeeProjectConfig();
|
|
249
|
+
vm.prank(multisig());
|
|
250
|
+
REV_DEPLOYER.deployFor({
|
|
251
|
+
revnetId: FEE_PROJECT_ID,
|
|
252
|
+
configuration: feeProjectConfig.configuration,
|
|
253
|
+
terminalConfigurations: feeProjectConfig.terminalConfigurations,
|
|
254
|
+
suckerDeploymentConfiguration: feeProjectConfig.suckerDeploymentConfiguration,
|
|
255
|
+
tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
|
|
256
|
+
allowedPosts: REVEmpty721Config.emptyAllowedPosts()
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Deploy a revnet with startsAtOrAfter in the past (triggers 30-day cash out delay).
|
|
260
|
+
FeeProjectConfig memory delayedConfig =
|
|
261
|
+
_getRevnetConfig(true, "Delayed", "$DLY", keccak256(abi.encodePacked("DELAYED")));
|
|
262
|
+
(DELAYED_REVNET_ID,) = REV_DEPLOYER.deployFor({
|
|
263
|
+
revnetId: 0,
|
|
264
|
+
configuration: delayedConfig.configuration,
|
|
265
|
+
terminalConfigurations: delayedConfig.terminalConfigurations,
|
|
266
|
+
suckerDeploymentConfiguration: delayedConfig.suckerDeploymentConfiguration,
|
|
267
|
+
tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
|
|
268
|
+
allowedPosts: REVEmpty721Config.emptyAllowedPosts()
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Deploy a normal revnet with no delay.
|
|
272
|
+
FeeProjectConfig memory normalConfig =
|
|
273
|
+
_getRevnetConfig(false, "Normal", "$NRM", keccak256(abi.encodePacked("NORMAL")));
|
|
274
|
+
(NORMAL_REVNET_ID,) = REV_DEPLOYER.deployFor({
|
|
275
|
+
revnetId: 0,
|
|
276
|
+
configuration: normalConfig.configuration,
|
|
277
|
+
terminalConfigurations: normalConfig.terminalConfigurations,
|
|
278
|
+
suckerDeploymentConfiguration: normalConfig.suckerDeploymentConfiguration,
|
|
279
|
+
tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
|
|
280
|
+
allowedPosts: REVEmpty721Config.emptyAllowedPosts()
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
vm.deal(USER, 100 ether);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ------------------------------------------------------------------
|
|
287
|
+
// Helpers
|
|
288
|
+
// ------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
/// @notice Pay ETH into a revnet and return the number of project tokens received.
|
|
291
|
+
function _payAndGetTokens(uint256 revnetId, uint256 amount) internal returns (uint256 tokenCount) {
|
|
292
|
+
vm.prank(USER);
|
|
293
|
+
tokenCount = jbMultiTerminal().pay{value: amount}(revnetId, JBConstants.NATIVE_TOKEN, amount, USER, 0, "", "");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/// @notice Mock the permissions check so LOANS_CONTRACT can burn tokens on behalf of USER.
|
|
297
|
+
function _mockBorrowPermission(uint256 projectId) internal {
|
|
298
|
+
mockExpect(
|
|
299
|
+
address(jbPermissions()),
|
|
300
|
+
abi.encodeCall(IJBPermissions.hasPermission, (address(LOANS_CONTRACT), USER, projectId, 11, true, true)),
|
|
301
|
+
abi.encode(true)
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ------------------------------------------------------------------
|
|
306
|
+
// Tests: delayed revnet (startsAtOrAfter in the past → 30-day delay)
|
|
307
|
+
// ------------------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
/// @notice Verify the deployer actually set a cash out delay for the delayed revnet.
|
|
310
|
+
function test_delayedRevnet_hasCashOutDelay() public view {
|
|
311
|
+
uint256 cashOutDelay = REV_OWNER.cashOutDelayOf(DELAYED_REVNET_ID);
|
|
312
|
+
assertGt(cashOutDelay, block.timestamp, "Cash out delay should be in the future");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/// @notice Verify the normal revnet has no cash out delay.
|
|
316
|
+
function test_normalRevnet_noCashOutDelay() public view {
|
|
317
|
+
uint256 cashOutDelay = REV_OWNER.cashOutDelayOf(NORMAL_REVNET_ID);
|
|
318
|
+
assertEq(cashOutDelay, 0, "Normal revnet should have no cash out delay");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/// @notice borrowableAmountFrom should return 0 during the delay period.
|
|
322
|
+
function test_borrowableAmountFrom_returnsZeroDuringDelay() public {
|
|
323
|
+
// Pay into the delayed revnet to get tokens.
|
|
324
|
+
uint256 tokenCount = _payAndGetTokens(DELAYED_REVNET_ID, 1 ether);
|
|
325
|
+
assertGt(tokenCount, 0, "Should have tokens");
|
|
326
|
+
|
|
327
|
+
// Query borrowable amount — should be 0 during the delay.
|
|
328
|
+
uint256 borrowable = LOANS_CONTRACT.borrowableAmountFrom(
|
|
329
|
+
DELAYED_REVNET_ID, tokenCount, 18, uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
330
|
+
);
|
|
331
|
+
assertEq(borrowable, 0, "Borrowable amount should be 0 during cash out delay");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/// @notice borrowFrom should revert during the delay period.
|
|
335
|
+
function test_borrowFrom_revertsDuringDelay() public {
|
|
336
|
+
// Pay into the delayed revnet to get tokens.
|
|
337
|
+
uint256 tokenCount = _payAndGetTokens(DELAYED_REVNET_ID, 1 ether);
|
|
338
|
+
assertGt(tokenCount, 0, "Should have tokens");
|
|
339
|
+
|
|
340
|
+
// No permission mock needed — the function reverts before reaching the permission check.
|
|
341
|
+
|
|
342
|
+
REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
|
|
343
|
+
|
|
344
|
+
// Attempt to borrow — should revert with CashOutDelayNotFinished.
|
|
345
|
+
uint256 cashOutDelay = REV_OWNER.cashOutDelayOf(DELAYED_REVNET_ID);
|
|
346
|
+
vm.expectRevert(
|
|
347
|
+
abi.encodeWithSelector(REVLoans.REVLoans_CashOutDelayNotFinished.selector, cashOutDelay, block.timestamp)
|
|
348
|
+
);
|
|
349
|
+
vm.prank(USER);
|
|
350
|
+
LOANS_CONTRACT.borrowFrom(DELAYED_REVNET_ID, source, 1, tokenCount, payable(USER), 25);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/// @notice After warping past the delay, borrowableAmountFrom should return a non-zero value.
|
|
354
|
+
function test_borrowableAmountFrom_nonZeroAfterDelay() public {
|
|
355
|
+
// Pay into the delayed revnet.
|
|
356
|
+
uint256 tokenCount = _payAndGetTokens(DELAYED_REVNET_ID, 1 ether);
|
|
357
|
+
|
|
358
|
+
// Still in delay — should be 0.
|
|
359
|
+
uint256 borrowableBefore = LOANS_CONTRACT.borrowableAmountFrom(
|
|
360
|
+
DELAYED_REVNET_ID, tokenCount, 18, uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
361
|
+
);
|
|
362
|
+
assertEq(borrowableBefore, 0, "Should be 0 during delay");
|
|
363
|
+
|
|
364
|
+
// Warp past the delay.
|
|
365
|
+
vm.warp(block.timestamp + REV_DEPLOYER.CASH_OUT_DELAY() + 1);
|
|
366
|
+
|
|
367
|
+
// Now should be > 0.
|
|
368
|
+
uint256 borrowableAfter = LOANS_CONTRACT.borrowableAmountFrom(
|
|
369
|
+
DELAYED_REVNET_ID, tokenCount, 18, uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
370
|
+
);
|
|
371
|
+
assertGt(borrowableAfter, 0, "Should be > 0 after delay expires");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/// @notice After warping past the delay, borrowFrom should succeed.
|
|
375
|
+
function test_borrowFrom_succeedsAfterDelay() public {
|
|
376
|
+
// Pay into the delayed revnet.
|
|
377
|
+
uint256 tokenCount = _payAndGetTokens(DELAYED_REVNET_ID, 1 ether);
|
|
378
|
+
|
|
379
|
+
// Warp past the delay.
|
|
380
|
+
vm.warp(block.timestamp + REV_DEPLOYER.CASH_OUT_DELAY() + 1);
|
|
381
|
+
|
|
382
|
+
// Get the borrowable amount.
|
|
383
|
+
uint256 borrowable = LOANS_CONTRACT.borrowableAmountFrom(
|
|
384
|
+
DELAYED_REVNET_ID, tokenCount, 18, uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
385
|
+
);
|
|
386
|
+
assertGt(borrowable, 0, "Should be borrowable after delay");
|
|
387
|
+
|
|
388
|
+
// Mock permission.
|
|
389
|
+
_mockBorrowPermission(DELAYED_REVNET_ID);
|
|
390
|
+
|
|
391
|
+
REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
|
|
392
|
+
|
|
393
|
+
// Borrow — should succeed.
|
|
394
|
+
vm.prank(USER);
|
|
395
|
+
(uint256 loanId,) =
|
|
396
|
+
LOANS_CONTRACT.borrowFrom(DELAYED_REVNET_ID, source, borrowable, tokenCount, payable(USER), 25);
|
|
397
|
+
assertGt(loanId, 0, "Should have created a loan");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ------------------------------------------------------------------
|
|
401
|
+
// Tests: normal revnet (no delay)
|
|
402
|
+
// ------------------------------------------------------------------
|
|
403
|
+
|
|
404
|
+
/// @notice A normal revnet (no delay) should allow borrowing immediately.
|
|
405
|
+
function test_normalRevnet_borrowableImmediately() public {
|
|
406
|
+
// Pay into the normal revnet.
|
|
407
|
+
uint256 tokenCount = _payAndGetTokens(NORMAL_REVNET_ID, 1 ether);
|
|
408
|
+
|
|
409
|
+
// Should have a non-zero borrowable amount immediately.
|
|
410
|
+
uint256 borrowable = LOANS_CONTRACT.borrowableAmountFrom(
|
|
411
|
+
NORMAL_REVNET_ID, tokenCount, 18, uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
412
|
+
);
|
|
413
|
+
assertGt(borrowable, 0, "Normal revnet should be borrowable immediately");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/// @notice A normal revnet (no delay) should allow borrowFrom immediately.
|
|
417
|
+
function test_normalRevnet_borrowFromImmediately() public {
|
|
418
|
+
// Pay into the normal revnet.
|
|
419
|
+
uint256 tokenCount = _payAndGetTokens(NORMAL_REVNET_ID, 1 ether);
|
|
420
|
+
|
|
421
|
+
uint256 borrowable = LOANS_CONTRACT.borrowableAmountFrom(
|
|
422
|
+
NORMAL_REVNET_ID, tokenCount, 18, uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
423
|
+
);
|
|
424
|
+
assertGt(borrowable, 0, "Should be borrowable");
|
|
425
|
+
|
|
426
|
+
// Mock permission.
|
|
427
|
+
_mockBorrowPermission(NORMAL_REVNET_ID);
|
|
428
|
+
|
|
429
|
+
REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
|
|
430
|
+
|
|
431
|
+
// Borrow — should succeed without any delay.
|
|
432
|
+
vm.prank(USER);
|
|
433
|
+
(uint256 loanId,) =
|
|
434
|
+
LOANS_CONTRACT.borrowFrom(NORMAL_REVNET_ID, source, borrowable, tokenCount, payable(USER), 25);
|
|
435
|
+
assertGt(loanId, 0, "Should have created a loan");
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ------------------------------------------------------------------
|
|
439
|
+
// Tests: boundary conditions
|
|
440
|
+
// ------------------------------------------------------------------
|
|
441
|
+
|
|
442
|
+
/// @notice borrowFrom should revert at exactly the delay timestamp (not yet expired).
|
|
443
|
+
function test_borrowFrom_revertsAtExactDelayTimestamp() public {
|
|
444
|
+
uint256 tokenCount = _payAndGetTokens(DELAYED_REVNET_ID, 1 ether);
|
|
445
|
+
|
|
446
|
+
// Warp to exactly the delay timestamp (not past it).
|
|
447
|
+
uint256 cashOutDelay = REV_OWNER.cashOutDelayOf(DELAYED_REVNET_ID);
|
|
448
|
+
vm.warp(cashOutDelay);
|
|
449
|
+
|
|
450
|
+
// borrowableAmountFrom should still return 0 (cashOutDelay > block.timestamp is false, but == is not >).
|
|
451
|
+
// Actually cashOutDelay == block.timestamp means cashOutDelay > block.timestamp is false → should pass.
|
|
452
|
+
// Let's verify: at exact boundary, the delay is NOT enforced (delay == timestamp passes).
|
|
453
|
+
uint256 borrowable = LOANS_CONTRACT.borrowableAmountFrom(
|
|
454
|
+
DELAYED_REVNET_ID, tokenCount, 18, uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
455
|
+
);
|
|
456
|
+
assertGt(borrowable, 0, "At exact delay timestamp, borrowing should be allowed");
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/// @notice borrowFrom should revert 1 second before the delay expires.
|
|
460
|
+
function test_borrowFrom_revertsOneSecondBeforeDelay() public {
|
|
461
|
+
uint256 tokenCount = _payAndGetTokens(DELAYED_REVNET_ID, 1 ether);
|
|
462
|
+
|
|
463
|
+
// Warp to 1 second before the delay expires.
|
|
464
|
+
uint256 cashOutDelay = REV_OWNER.cashOutDelayOf(DELAYED_REVNET_ID);
|
|
465
|
+
vm.warp(cashOutDelay - 1);
|
|
466
|
+
|
|
467
|
+
// borrowableAmountFrom should return 0.
|
|
468
|
+
uint256 borrowable = LOANS_CONTRACT.borrowableAmountFrom(
|
|
469
|
+
DELAYED_REVNET_ID, tokenCount, 18, uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
470
|
+
);
|
|
471
|
+
assertEq(borrowable, 0, "Should be 0 one second before delay expires");
|
|
472
|
+
|
|
473
|
+
// borrowFrom should revert before reaching the permission check.
|
|
474
|
+
REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
|
|
475
|
+
|
|
476
|
+
vm.expectRevert(
|
|
477
|
+
abi.encodeWithSelector(REVLoans.REVLoans_CashOutDelayNotFinished.selector, cashOutDelay, block.timestamp)
|
|
478
|
+
);
|
|
479
|
+
vm.prank(USER);
|
|
480
|
+
LOANS_CONTRACT.borrowFrom(DELAYED_REVNET_ID, source, 1, tokenCount, payable(USER), 25);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
@@ -35,6 +35,8 @@ import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStor
|
|
|
35
35
|
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
36
36
|
import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
|
|
37
37
|
import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
|
|
38
|
+
import {REVOwner} from "../src/REVOwner.sol";
|
|
39
|
+
import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
|
|
38
40
|
|
|
39
41
|
/// @notice Long-tail economic simulation: run a revnet through multiple stage transitions with many payments
|
|
40
42
|
/// and cash outs, verifying value conservation and bonding curve consistency.
|
|
@@ -45,6 +47,8 @@ contract TestLongTailEconomics is TestBaseWorkflow {
|
|
|
45
47
|
// forge-lint: disable-next-line(mixed-case-variable)
|
|
46
48
|
REVDeployer REV_DEPLOYER;
|
|
47
49
|
// forge-lint: disable-next-line(mixed-case-variable)
|
|
50
|
+
REVOwner REV_OWNER;
|
|
51
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
48
52
|
JB721TiersHook EXAMPLE_HOOK;
|
|
49
53
|
// forge-lint: disable-next-line(mixed-case-variable)
|
|
50
54
|
IJB721TiersHookDeployer HOOK_DEPLOYER;
|
|
@@ -104,6 +108,14 @@ contract TestLongTailEconomics is TestBaseWorkflow {
|
|
|
104
108
|
trustedForwarder: TRUSTED_FORWARDER
|
|
105
109
|
});
|
|
106
110
|
|
|
111
|
+
REV_OWNER = new REVOwner(
|
|
112
|
+
IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
|
|
113
|
+
jbDirectory(),
|
|
114
|
+
FEE_PROJECT_ID,
|
|
115
|
+
SUCKER_REGISTRY,
|
|
116
|
+
address(LOANS_CONTRACT)
|
|
117
|
+
);
|
|
118
|
+
|
|
107
119
|
REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
|
|
108
120
|
jbController(),
|
|
109
121
|
SUCKER_REGISTRY,
|
|
@@ -112,9 +124,12 @@ contract TestLongTailEconomics is TestBaseWorkflow {
|
|
|
112
124
|
PUBLISHER,
|
|
113
125
|
IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
|
|
114
126
|
address(LOANS_CONTRACT),
|
|
115
|
-
TRUSTED_FORWARDER
|
|
127
|
+
TRUSTED_FORWARDER,
|
|
128
|
+
address(REV_OWNER)
|
|
116
129
|
);
|
|
117
130
|
|
|
131
|
+
REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
|
|
132
|
+
|
|
118
133
|
vm.prank(multisig());
|
|
119
134
|
jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
|
|
120
135
|
|
|
@@ -40,6 +40,8 @@ import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStor
|
|
|
40
40
|
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
41
41
|
import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
|
|
42
42
|
import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
|
|
43
|
+
import {REVOwner} from "../src/REVOwner.sol";
|
|
44
|
+
import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
|
|
43
45
|
|
|
44
46
|
struct FeeProjectConfig {
|
|
45
47
|
REVConfig configuration;
|
|
@@ -56,6 +58,8 @@ contract TestLowFindings is TestBaseWorkflow {
|
|
|
56
58
|
// forge-lint: disable-next-line(mixed-case-variable)
|
|
57
59
|
REVDeployer REV_DEPLOYER;
|
|
58
60
|
// forge-lint: disable-next-line(mixed-case-variable)
|
|
61
|
+
REVOwner REV_OWNER;
|
|
62
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
59
63
|
JB721TiersHook EXAMPLE_HOOK;
|
|
60
64
|
// forge-lint: disable-next-line(mixed-case-variable)
|
|
61
65
|
IJB721TiersHookDeployer HOOK_DEPLOYER;
|
|
@@ -293,6 +297,14 @@ contract TestLowFindings is TestBaseWorkflow {
|
|
|
293
297
|
trustedForwarder: TRUSTED_FORWARDER
|
|
294
298
|
});
|
|
295
299
|
|
|
300
|
+
REV_OWNER = new REVOwner(
|
|
301
|
+
IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
|
|
302
|
+
jbDirectory(),
|
|
303
|
+
FEE_PROJECT_ID,
|
|
304
|
+
SUCKER_REGISTRY,
|
|
305
|
+
address(LOANS_CONTRACT)
|
|
306
|
+
);
|
|
307
|
+
|
|
296
308
|
REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
|
|
297
309
|
jbController(),
|
|
298
310
|
SUCKER_REGISTRY,
|
|
@@ -301,9 +313,12 @@ contract TestLowFindings is TestBaseWorkflow {
|
|
|
301
313
|
PUBLISHER,
|
|
302
314
|
IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
|
|
303
315
|
address(LOANS_CONTRACT),
|
|
304
|
-
TRUSTED_FORWARDER
|
|
316
|
+
TRUSTED_FORWARDER,
|
|
317
|
+
address(REV_OWNER)
|
|
305
318
|
);
|
|
306
319
|
|
|
320
|
+
REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
|
|
321
|
+
|
|
307
322
|
// Deploy fee project.
|
|
308
323
|
vm.prank(multisig());
|
|
309
324
|
jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
|
|
@@ -38,6 +38,8 @@ import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStor
|
|
|
38
38
|
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
39
39
|
import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
|
|
40
40
|
import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
|
|
41
|
+
import {REVOwner} from "../src/REVOwner.sol";
|
|
42
|
+
import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
|
|
41
43
|
|
|
42
44
|
/// @notice Tests for PR #32: liquidation boundary, reallocate msg.value, and decimal normalization fixes.
|
|
43
45
|
contract TestMixedFixes is TestBaseWorkflow {
|
|
@@ -47,6 +49,8 @@ contract TestMixedFixes is TestBaseWorkflow {
|
|
|
47
49
|
// forge-lint: disable-next-line(mixed-case-variable)
|
|
48
50
|
REVDeployer REV_DEPLOYER;
|
|
49
51
|
// forge-lint: disable-next-line(mixed-case-variable)
|
|
52
|
+
REVOwner REV_OWNER;
|
|
53
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
50
54
|
JB721TiersHook EXAMPLE_HOOK;
|
|
51
55
|
// forge-lint: disable-next-line(mixed-case-variable)
|
|
52
56
|
IJB721TiersHookDeployer HOOK_DEPLOYER;
|
|
@@ -100,6 +104,14 @@ contract TestMixedFixes is TestBaseWorkflow {
|
|
|
100
104
|
permit2: permit2(),
|
|
101
105
|
trustedForwarder: TRUSTED_FORWARDER
|
|
102
106
|
});
|
|
107
|
+
REV_OWNER = new REVOwner(
|
|
108
|
+
IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
|
|
109
|
+
jbDirectory(),
|
|
110
|
+
FEE_PROJECT_ID,
|
|
111
|
+
SUCKER_REGISTRY,
|
|
112
|
+
address(LOANS_CONTRACT)
|
|
113
|
+
);
|
|
114
|
+
|
|
103
115
|
REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
|
|
104
116
|
jbController(),
|
|
105
117
|
SUCKER_REGISTRY,
|
|
@@ -108,8 +120,11 @@ contract TestMixedFixes is TestBaseWorkflow {
|
|
|
108
120
|
PUBLISHER,
|
|
109
121
|
IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
|
|
110
122
|
address(LOANS_CONTRACT),
|
|
111
|
-
TRUSTED_FORWARDER
|
|
123
|
+
TRUSTED_FORWARDER,
|
|
124
|
+
address(REV_OWNER)
|
|
112
125
|
);
|
|
126
|
+
|
|
127
|
+
REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
|
|
113
128
|
vm.prank(multisig());
|
|
114
129
|
jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
|
|
115
130
|
_deployFeeProject();
|
|
@@ -45,6 +45,8 @@ import {IAllowanceTransfer} from "@uniswap/permit2/src/interfaces/IAllowanceTran
|
|
|
45
45
|
import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
|
|
46
46
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
47
47
|
import {JBFees} from "@bananapus/core-v6/src/libraries/JBFees.sol";
|
|
48
|
+
import {REVOwner} from "../src/REVOwner.sol";
|
|
49
|
+
import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
|
|
48
50
|
|
|
49
51
|
struct Permit2ProjectConfig {
|
|
50
52
|
REVConfig configuration;
|
|
@@ -64,6 +66,8 @@ contract TestPermit2Signatures is TestBaseWorkflow {
|
|
|
64
66
|
// forge-lint: disable-next-line(mixed-case-variable)
|
|
65
67
|
REVDeployer REV_DEPLOYER;
|
|
66
68
|
// forge-lint: disable-next-line(mixed-case-variable)
|
|
69
|
+
REVOwner REV_OWNER;
|
|
70
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
67
71
|
JB721TiersHook EXAMPLE_HOOK;
|
|
68
72
|
// forge-lint: disable-next-line(mixed-case-variable)
|
|
69
73
|
IJB721TiersHookDeployer HOOK_DEPLOYER;
|
|
@@ -261,6 +265,14 @@ contract TestPermit2Signatures is TestBaseWorkflow {
|
|
|
261
265
|
trustedForwarder: TRUSTED_FORWARDER
|
|
262
266
|
});
|
|
263
267
|
|
|
268
|
+
REV_OWNER = new REVOwner(
|
|
269
|
+
IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
|
|
270
|
+
jbDirectory(),
|
|
271
|
+
FEE_PROJECT_ID,
|
|
272
|
+
SUCKER_REGISTRY,
|
|
273
|
+
address(LOANS_CONTRACT)
|
|
274
|
+
);
|
|
275
|
+
|
|
264
276
|
REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
|
|
265
277
|
jbController(),
|
|
266
278
|
SUCKER_REGISTRY,
|
|
@@ -269,9 +281,12 @@ contract TestPermit2Signatures is TestBaseWorkflow {
|
|
|
269
281
|
PUBLISHER,
|
|
270
282
|
IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
|
|
271
283
|
address(LOANS_CONTRACT),
|
|
272
|
-
TRUSTED_FORWARDER
|
|
284
|
+
TRUSTED_FORWARDER,
|
|
285
|
+
address(REV_OWNER)
|
|
273
286
|
);
|
|
274
287
|
|
|
288
|
+
REV_OWNER.initialize(IREVDeployer(address(REV_DEPLOYER)));
|
|
289
|
+
|
|
275
290
|
// Approve the basic deployer to configure the project.
|
|
276
291
|
vm.prank(address(multisig()));
|
|
277
292
|
jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
|