@rev-net/core-v6 0.0.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/LICENSE +21 -0
- package/README.md +65 -0
- package/REVNET_SECURITY_CHECKLIST.md +164 -0
- package/SECURITY.md +68 -0
- package/SKILLS.md +166 -0
- package/deployments/revnet-core-v5/arbitrum/REVDeployer.json +2821 -0
- package/deployments/revnet-core-v5/arbitrum/REVLoans.json +2260 -0
- package/deployments/revnet-core-v5/arbitrum_sepolia/REVDeployer.json +2821 -0
- package/deployments/revnet-core-v5/arbitrum_sepolia/REVLoans.json +2260 -0
- package/deployments/revnet-core-v5/base/REVDeployer.json +2825 -0
- package/deployments/revnet-core-v5/base/REVLoans.json +2264 -0
- package/deployments/revnet-core-v5/base_sepolia/REVDeployer.json +2825 -0
- package/deployments/revnet-core-v5/base_sepolia/REVLoans.json +2264 -0
- package/deployments/revnet-core-v5/ethereum/REVDeployer.json +2825 -0
- package/deployments/revnet-core-v5/ethereum/REVLoans.json +2264 -0
- package/deployments/revnet-core-v5/optimism/REVDeployer.json +2821 -0
- package/deployments/revnet-core-v5/optimism/REVLoans.json +2260 -0
- package/deployments/revnet-core-v5/optimism_sepolia/REVDeployer.json +2825 -0
- package/deployments/revnet-core-v5/optimism_sepolia/REVLoans.json +2264 -0
- package/deployments/revnet-core-v5/sepolia/REVDeployer.json +2825 -0
- package/deployments/revnet-core-v5/sepolia/REVLoans.json +2264 -0
- package/docs/book.css +13 -0
- package/docs/book.toml +13 -0
- package/docs/solidity.min.js +74 -0
- package/docs/src/README.md +88 -0
- package/docs/src/SUMMARY.md +20 -0
- package/docs/src/src/README.md +7 -0
- package/docs/src/src/REVDeployer.sol/contract.REVDeployer.md +968 -0
- package/docs/src/src/REVLoans.sol/contract.REVLoans.md +1047 -0
- package/docs/src/src/interfaces/IREVDeployer.sol/interface.IREVDeployer.md +243 -0
- package/docs/src/src/interfaces/IREVLoans.sol/interface.IREVLoans.md +296 -0
- package/docs/src/src/interfaces/README.md +5 -0
- package/docs/src/src/structs/README.md +14 -0
- package/docs/src/src/structs/REVAutoIssuance.sol/struct.REVAutoIssuance.md +19 -0
- package/docs/src/src/structs/REVBuybackHookConfig.sol/struct.REVBuybackHookConfig.md +19 -0
- package/docs/src/src/structs/REVBuybackPoolConfig.sol/struct.REVBuybackPoolConfig.md +21 -0
- package/docs/src/src/structs/REVConfig.sol/struct.REVConfig.md +35 -0
- package/docs/src/src/structs/REVCroptopAllowedPost.sol/struct.REVCroptopAllowedPost.md +28 -0
- package/docs/src/src/structs/REVDeploy721TiersHookConfig.sol/struct.REVDeploy721TiersHookConfig.md +34 -0
- package/docs/src/src/structs/REVDescription.sol/struct.REVDescription.md +23 -0
- package/docs/src/src/structs/REVLoan.sol/struct.REVLoan.md +28 -0
- package/docs/src/src/structs/REVLoanSource.sol/struct.REVLoanSource.md +16 -0
- package/docs/src/src/structs/REVStageConfig.sol/struct.REVStageConfig.md +44 -0
- package/docs/src/src/structs/REVSuckerDeploymentConfig.sol/struct.REVSuckerDeploymentConfig.md +16 -0
- package/foundry.lock +11 -0
- package/foundry.toml +23 -0
- package/package.json +31 -0
- package/remappings.txt +1 -0
- package/script/Deploy.s.sol +350 -0
- package/script/helpers/RevnetCoreDeploymentLib.sol +72 -0
- package/slither-ci.config.json +10 -0
- package/sphinx.lock +507 -0
- package/src/REVDeployer.sol +1257 -0
- package/src/REVLoans.sol +1333 -0
- package/src/interfaces/IREVDeployer.sol +198 -0
- package/src/interfaces/IREVLoans.sol +241 -0
- package/src/structs/REVAutoIssuance.sol +11 -0
- package/src/structs/REVConfig.sol +17 -0
- package/src/structs/REVCroptopAllowedPost.sol +20 -0
- package/src/structs/REVDeploy721TiersHookConfig.sol +25 -0
- package/src/structs/REVDescription.sol +14 -0
- package/src/structs/REVLoan.sol +19 -0
- package/src/structs/REVLoanSource.sol +11 -0
- package/src/structs/REVStageConfig.sol +34 -0
- package/src/structs/REVSuckerDeploymentConfig.sol +11 -0
- package/test/REV.integrations.t.sol +420 -0
- package/test/REVAutoIssuanceFuzz.t.sol +276 -0
- package/test/REVDeployerAuditRegressions.t.sol +328 -0
- package/test/REVInvincibility.t.sol +1275 -0
- package/test/REVInvincibilityHandler.sol +357 -0
- package/test/REVLifecycle.t.sol +364 -0
- package/test/REVLoans.invariants.t.sol +642 -0
- package/test/REVLoansAttacks.t.sol +739 -0
- package/test/REVLoansAuditRegressions.t.sol +314 -0
- package/test/REVLoansFeeRecovery.t.sol +704 -0
- package/test/REVLoansSourced.t.sol +1732 -0
- package/test/REVLoansUnSourced.t.sol +331 -0
- package/test/TestPR09_ConversionDocumentation.t.sol +304 -0
- package/test/TestPR10_LiquidationBehavior.t.sol +340 -0
- package/test/TestPR11_LowFindings.t.sol +571 -0
- package/test/TestPR12_FlashLoanSurplus.t.sol +305 -0
- package/test/TestPR13_CrossSourceReallocation.t.sol +302 -0
- package/test/TestPR15_CashOutCallerValidation.t.sol +320 -0
- package/test/TestPR16_ZeroRepayment.t.sol +297 -0
- package/test/TestPR21_Uint112Overflow.t.sol +251 -0
- package/test/TestPR22_HookArrayOOB.t.sol +221 -0
- package/test/TestPR26_BurnHeldTokens.t.sol +331 -0
- package/test/TestPR27_CEIPattern.t.sol +448 -0
- package/test/TestPR29_SwapTerminalPermission.t.sol +206 -0
- package/test/TestPR32_MixedFixes.t.sol +529 -0
- package/test/helpers/MaliciousContracts.sol +233 -0
- package/test/mock/MockBuybackDataHook.sol +61 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.23;
|
|
3
|
+
|
|
4
|
+
import "forge-std/Test.sol";
|
|
5
|
+
import /* {*} from */ "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
6
|
+
import /* {*} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
|
|
7
|
+
import /* {*} from */ "./../src/REVDeployer.sol";
|
|
8
|
+
import "@croptop/core-v6/src/CTPublisher.sol";
|
|
9
|
+
import {MockBuybackDataHook} from "./mock/MockBuybackDataHook.sol";
|
|
10
|
+
|
|
11
|
+
import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
|
|
12
|
+
import "@bananapus/721-hook-v6/script/helpers/Hook721DeploymentLib.sol";
|
|
13
|
+
import "@bananapus/suckers-v6/script/helpers/SuckerDeploymentLib.sol";
|
|
14
|
+
import "@croptop/core-v6/script/helpers/CroptopDeploymentLib.sol";
|
|
15
|
+
import "@bananapus/swap-terminal-v6/script/helpers/SwapTerminalDeploymentLib.sol";
|
|
16
|
+
|
|
17
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
18
|
+
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
19
|
+
import {REVLoans} from "../src/REVLoans.sol";
|
|
20
|
+
import {REVStageConfig, REVAutoIssuance} from "../src/structs/REVStageConfig.sol";
|
|
21
|
+
import {REVLoanSource} from "../src/structs/REVLoanSource.sol";
|
|
22
|
+
import {REVDescription} from "../src/structs/REVDescription.sol";
|
|
23
|
+
import {IREVLoans} from "./../src/interfaces/IREVLoans.sol";
|
|
24
|
+
import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
|
|
25
|
+
import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
|
|
26
|
+
import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
|
|
27
|
+
import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
|
|
28
|
+
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
29
|
+
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
30
|
+
import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
|
|
31
|
+
|
|
32
|
+
struct FeeProjectConfig {
|
|
33
|
+
REVConfig configuration;
|
|
34
|
+
JBTerminalConfig[] terminalConfigurations;
|
|
35
|
+
REVSuckerDeploymentConfig suckerDeploymentConfiguration;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
contract REVLoansUnsourcedTests is TestBaseWorkflow, JBTest {
|
|
39
|
+
/// @notice the salts that are used to deploy the contracts.
|
|
40
|
+
bytes32 REV_DEPLOYER_SALT = "REVDeployer";
|
|
41
|
+
bytes32 ERC20_SALT = "REV_TOKEN";
|
|
42
|
+
|
|
43
|
+
REVDeployer REV_DEPLOYER;
|
|
44
|
+
JB721TiersHook EXAMPLE_HOOK;
|
|
45
|
+
|
|
46
|
+
/// @notice Deploys tiered ERC-721 hooks for revnets.
|
|
47
|
+
IJB721TiersHookDeployer HOOK_DEPLOYER;
|
|
48
|
+
IJB721TiersHookStore HOOK_STORE;
|
|
49
|
+
IJBAddressRegistry ADDRESS_REGISTRY;
|
|
50
|
+
|
|
51
|
+
IREVLoans LOANS_CONTRACT;
|
|
52
|
+
|
|
53
|
+
/// @notice Deploys and tracks suckers for revnets.
|
|
54
|
+
IJBSuckerRegistry SUCKER_REGISTRY;
|
|
55
|
+
|
|
56
|
+
CTPublisher PUBLISHER;
|
|
57
|
+
MockBuybackDataHook MOCK_BUYBACK;
|
|
58
|
+
|
|
59
|
+
uint256 FEE_PROJECT_ID;
|
|
60
|
+
uint256 REVNET_ID;
|
|
61
|
+
|
|
62
|
+
address USER = makeAddr("user");
|
|
63
|
+
|
|
64
|
+
/// @notice The address that is allowed to forward calls.
|
|
65
|
+
address private constant TRUSTED_FORWARDER = 0xB2b5841DBeF766d4b521221732F9B618fCf34A87;
|
|
66
|
+
|
|
67
|
+
function getFeeProjectConfig() internal view returns (FeeProjectConfig memory) {
|
|
68
|
+
// Define constants
|
|
69
|
+
string memory name = "Revnet";
|
|
70
|
+
string memory symbol = "$REV";
|
|
71
|
+
string memory projectUri = "ipfs://QmNRHT91HcDgMcenebYX7rJigt77cgNcosvuhX21wkF3tx";
|
|
72
|
+
uint8 decimals = 18;
|
|
73
|
+
uint256 decimalMultiplier = 10 ** decimals;
|
|
74
|
+
|
|
75
|
+
// The tokens that the project accepts and stores.
|
|
76
|
+
JBAccountingContext[] memory accountingContextsToAccept = new JBAccountingContext[](1);
|
|
77
|
+
|
|
78
|
+
// Accept the chain's native currency through the multi terminal.
|
|
79
|
+
accountingContextsToAccept[0] = JBAccountingContext({
|
|
80
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// The terminals that the project will accept funds through.
|
|
84
|
+
JBTerminalConfig[] memory terminalConfigurations = new JBTerminalConfig[](1);
|
|
85
|
+
terminalConfigurations[0] =
|
|
86
|
+
JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: accountingContextsToAccept});
|
|
87
|
+
|
|
88
|
+
// The project's revnet stage configurations.
|
|
89
|
+
REVStageConfig[] memory stageConfigurations = new REVStageConfig[](3);
|
|
90
|
+
|
|
91
|
+
JBSplit[] memory splits = new JBSplit[](1);
|
|
92
|
+
splits[0].beneficiary = payable(multisig());
|
|
93
|
+
splits[0].percent = 10_000;
|
|
94
|
+
|
|
95
|
+
{
|
|
96
|
+
REVAutoIssuance[] memory issuanceConfs = new REVAutoIssuance[](1);
|
|
97
|
+
issuanceConfs[0] = REVAutoIssuance({
|
|
98
|
+
chainId: uint32(block.chainid), count: uint104(70_000 * decimalMultiplier), beneficiary: multisig()
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
stageConfigurations[0] = REVStageConfig({
|
|
102
|
+
startsAtOrAfter: uint40(block.timestamp),
|
|
103
|
+
autoIssuances: issuanceConfs,
|
|
104
|
+
splitPercent: 2000, // 20%
|
|
105
|
+
splits: splits,
|
|
106
|
+
initialIssuance: uint112(1000 * decimalMultiplier),
|
|
107
|
+
issuanceCutFrequency: 90 days,
|
|
108
|
+
issuanceCutPercent: JBConstants.MAX_WEIGHT_CUT_PERCENT / 2,
|
|
109
|
+
cashOutTaxRate: 6000, // 0.6
|
|
110
|
+
extraMetadata: 0
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
stageConfigurations[1] = REVStageConfig({
|
|
115
|
+
startsAtOrAfter: uint40(stageConfigurations[0].startsAtOrAfter + 720 days),
|
|
116
|
+
autoIssuances: new REVAutoIssuance[](0),
|
|
117
|
+
splitPercent: 2000, // 20%
|
|
118
|
+
splits: splits,
|
|
119
|
+
initialIssuance: 0, // inherit from previous cycle.
|
|
120
|
+
issuanceCutFrequency: 180 days,
|
|
121
|
+
issuanceCutPercent: JBConstants.MAX_WEIGHT_CUT_PERCENT / 2,
|
|
122
|
+
cashOutTaxRate: 6000, // 0.6
|
|
123
|
+
extraMetadata: 0
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
stageConfigurations[2] = REVStageConfig({
|
|
127
|
+
startsAtOrAfter: uint40(stageConfigurations[1].startsAtOrAfter + (20 * 365 days)),
|
|
128
|
+
autoIssuances: new REVAutoIssuance[](0),
|
|
129
|
+
splits: splits,
|
|
130
|
+
splitPercent: 0,
|
|
131
|
+
initialIssuance: 1, // this is a special number that is as close to max price as we can get.
|
|
132
|
+
issuanceCutFrequency: 0,
|
|
133
|
+
issuanceCutPercent: 0,
|
|
134
|
+
cashOutTaxRate: 6000, // 0.6
|
|
135
|
+
extraMetadata: 0
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// The project's revnet configuration
|
|
139
|
+
REVConfig memory revnetConfiguration = REVConfig({
|
|
140
|
+
description: REVDescription(name, symbol, projectUri, ERC20_SALT),
|
|
141
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
142
|
+
splitOperator: multisig(),
|
|
143
|
+
stageConfigurations: stageConfigurations
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return FeeProjectConfig({
|
|
147
|
+
configuration: revnetConfiguration,
|
|
148
|
+
terminalConfigurations: terminalConfigurations,
|
|
149
|
+
suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
|
|
150
|
+
deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256(abi.encodePacked("REV"))
|
|
151
|
+
})
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function getSecondProjectConfig() internal view returns (FeeProjectConfig memory) {
|
|
156
|
+
// Define constants
|
|
157
|
+
string memory name = "NANA";
|
|
158
|
+
string memory symbol = "$NANA";
|
|
159
|
+
string memory projectUri = "ipfs://QmNRHT91HcDgMcenebYX7rJigt77cgNxosvuhX21wkF3tx";
|
|
160
|
+
uint8 decimals = 18;
|
|
161
|
+
uint256 decimalMultiplier = 10 ** decimals;
|
|
162
|
+
|
|
163
|
+
// The tokens that the project accepts and stores.
|
|
164
|
+
JBAccountingContext[] memory accountingContextsToAccept = new JBAccountingContext[](1);
|
|
165
|
+
|
|
166
|
+
// Accept the chain's native currency through the multi terminal.
|
|
167
|
+
accountingContextsToAccept[0] = JBAccountingContext({
|
|
168
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// The terminals that the project will accept funds through.
|
|
172
|
+
JBTerminalConfig[] memory terminalConfigurations = new JBTerminalConfig[](1);
|
|
173
|
+
terminalConfigurations[0] =
|
|
174
|
+
JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: accountingContextsToAccept});
|
|
175
|
+
|
|
176
|
+
JBSplit[] memory splits = new JBSplit[](1);
|
|
177
|
+
splits[0].beneficiary = payable(multisig());
|
|
178
|
+
splits[0].percent = 10_000;
|
|
179
|
+
|
|
180
|
+
// The project's revnet stage configurations.
|
|
181
|
+
REVStageConfig[] memory stageConfigurations = new REVStageConfig[](3);
|
|
182
|
+
|
|
183
|
+
{
|
|
184
|
+
REVAutoIssuance[] memory issuanceConfs = new REVAutoIssuance[](1);
|
|
185
|
+
issuanceConfs[0] = REVAutoIssuance({
|
|
186
|
+
chainId: uint32(block.chainid), count: uint104(70_000 * decimalMultiplier), beneficiary: multisig()
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
stageConfigurations[0] = REVStageConfig({
|
|
190
|
+
startsAtOrAfter: uint40(block.timestamp),
|
|
191
|
+
autoIssuances: issuanceConfs,
|
|
192
|
+
splitPercent: 2000, // 20%
|
|
193
|
+
splits: splits,
|
|
194
|
+
initialIssuance: uint112(1000 * decimalMultiplier),
|
|
195
|
+
issuanceCutFrequency: 90 days,
|
|
196
|
+
issuanceCutPercent: JBConstants.MAX_WEIGHT_CUT_PERCENT / 2,
|
|
197
|
+
cashOutTaxRate: 6000, // 0.6
|
|
198
|
+
extraMetadata: 0
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
stageConfigurations[1] = REVStageConfig({
|
|
203
|
+
startsAtOrAfter: uint40(stageConfigurations[0].startsAtOrAfter + 720 days),
|
|
204
|
+
autoIssuances: new REVAutoIssuance[](0),
|
|
205
|
+
splitPercent: 2000, // 20%
|
|
206
|
+
splits: splits,
|
|
207
|
+
initialIssuance: 0, // inherit from previous cycle.
|
|
208
|
+
issuanceCutFrequency: 180 days,
|
|
209
|
+
issuanceCutPercent: JBConstants.MAX_WEIGHT_CUT_PERCENT / 2,
|
|
210
|
+
cashOutTaxRate: 6000, // 0.6
|
|
211
|
+
extraMetadata: 0
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
stageConfigurations[2] = REVStageConfig({
|
|
215
|
+
startsAtOrAfter: uint40(stageConfigurations[1].startsAtOrAfter + (20 * 365 days)),
|
|
216
|
+
autoIssuances: new REVAutoIssuance[](0),
|
|
217
|
+
splits: splits,
|
|
218
|
+
splitPercent: 0,
|
|
219
|
+
initialIssuance: 1, // this is a special number that is as close to max price as we can get.
|
|
220
|
+
issuanceCutFrequency: 0,
|
|
221
|
+
issuanceCutPercent: 0,
|
|
222
|
+
cashOutTaxRate: 6000, // 0.6
|
|
223
|
+
extraMetadata: 0
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// The project's revnet configuration
|
|
227
|
+
REVConfig memory revnetConfiguration = REVConfig({
|
|
228
|
+
description: REVDescription(name, symbol, projectUri, "NANA_TOKEN"),
|
|
229
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
230
|
+
splitOperator: multisig(),
|
|
231
|
+
stageConfigurations: stageConfigurations
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return FeeProjectConfig({
|
|
235
|
+
configuration: revnetConfiguration,
|
|
236
|
+
terminalConfigurations: terminalConfigurations,
|
|
237
|
+
suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
|
|
238
|
+
deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256(abi.encodePacked("NANA"))
|
|
239
|
+
})
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function setUp() public override {
|
|
244
|
+
super.setUp();
|
|
245
|
+
|
|
246
|
+
FEE_PROJECT_ID = jbProjects().createFor(multisig());
|
|
247
|
+
|
|
248
|
+
SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
|
|
249
|
+
|
|
250
|
+
HOOK_STORE = new JB721TiersHookStore();
|
|
251
|
+
|
|
252
|
+
EXAMPLE_HOOK = new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, multisig());
|
|
253
|
+
|
|
254
|
+
ADDRESS_REGISTRY = new JBAddressRegistry();
|
|
255
|
+
|
|
256
|
+
HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
|
|
257
|
+
|
|
258
|
+
PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
|
|
259
|
+
MOCK_BUYBACK = new MockBuybackDataHook();
|
|
260
|
+
|
|
261
|
+
LOANS_CONTRACT = new REVLoans({
|
|
262
|
+
controller: jbController(),
|
|
263
|
+
projects: jbProjects(),
|
|
264
|
+
revId: FEE_PROJECT_ID,
|
|
265
|
+
owner: address(this),
|
|
266
|
+
permit2: permit2(),
|
|
267
|
+
trustedForwarder: TRUSTED_FORWARDER
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
|
|
271
|
+
jbController(),
|
|
272
|
+
SUCKER_REGISTRY,
|
|
273
|
+
FEE_PROJECT_ID,
|
|
274
|
+
HOOK_DEPLOYER,
|
|
275
|
+
PUBLISHER,
|
|
276
|
+
IJBRulesetDataHook(address(MOCK_BUYBACK)),
|
|
277
|
+
address(LOANS_CONTRACT),
|
|
278
|
+
TRUSTED_FORWARDER
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
// Approve the basic deployer to configure the project.
|
|
282
|
+
vm.prank(address(multisig()));
|
|
283
|
+
jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
|
|
284
|
+
|
|
285
|
+
// Build the config.
|
|
286
|
+
FeeProjectConfig memory feeProjectConfig = getFeeProjectConfig();
|
|
287
|
+
|
|
288
|
+
vm.prank(address(multisig()));
|
|
289
|
+
// Configure the project.
|
|
290
|
+
REVNET_ID = REV_DEPLOYER.deployFor({
|
|
291
|
+
revnetId: FEE_PROJECT_ID, // Zero to deploy a new revnet
|
|
292
|
+
configuration: feeProjectConfig.configuration,
|
|
293
|
+
terminalConfigurations: feeProjectConfig.terminalConfigurations,
|
|
294
|
+
suckerDeploymentConfiguration: feeProjectConfig.suckerDeploymentConfiguration
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Configure second revnet
|
|
298
|
+
FeeProjectConfig memory fee2Config = getSecondProjectConfig();
|
|
299
|
+
|
|
300
|
+
// Configure the project.
|
|
301
|
+
REVNET_ID = REV_DEPLOYER.deployFor({
|
|
302
|
+
revnetId: 0, // Zero to deploy a new revnet
|
|
303
|
+
configuration: fee2Config.configuration,
|
|
304
|
+
terminalConfigurations: fee2Config.terminalConfigurations,
|
|
305
|
+
suckerDeploymentConfiguration: fee2Config.suckerDeploymentConfiguration
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Give Eth for the user experience
|
|
309
|
+
vm.deal(USER, 100e18);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/// @notice Loan fund access limits are now auto-derived from terminal configs.
|
|
313
|
+
/// Borrowing still fails if the user hasn't approved LOANS to burn their tokens as collateral.
|
|
314
|
+
function test_Pay_Borrow_Without_Loan_Source() public {
|
|
315
|
+
vm.prank(USER);
|
|
316
|
+
uint256 tokens = jbMultiTerminal().pay{value: 1e18}(REVNET_ID, JBConstants.NATIVE_TOKEN, 1e18, USER, 0, "", "");
|
|
317
|
+
|
|
318
|
+
uint256 loanable =
|
|
319
|
+
LOANS_CONTRACT.borrowableAmountFrom(REVNET_ID, tokens, 18, uint32(uint160(JBConstants.NATIVE_TOKEN)));
|
|
320
|
+
assertGt(loanable, 0);
|
|
321
|
+
|
|
322
|
+
// Loan fund access limits are now auto-derived from terminal configs, so the surplus allowance exists.
|
|
323
|
+
// The borrow still fails because USER hasn't approved LOANS to burn their tokens as collateral.
|
|
324
|
+
vm.expectRevert();
|
|
325
|
+
|
|
326
|
+
REVLoanSource memory sauce = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
|
|
327
|
+
|
|
328
|
+
vm.prank(USER);
|
|
329
|
+
LOANS_CONTRACT.borrowFrom(REVNET_ID, sauce, loanable, tokens, payable(USER), 500);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.23;
|
|
3
|
+
|
|
4
|
+
import "forge-std/Test.sol";
|
|
5
|
+
import /* {*} from */ "@bananapus/core-v6/test/helpers/TestBaseWorkflow.sol";
|
|
6
|
+
import /* {*} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
|
|
7
|
+
import /* {*} from */ "./../src/REVDeployer.sol";
|
|
8
|
+
import "@croptop/core-v6/src/CTPublisher.sol";
|
|
9
|
+
import {MockBuybackDataHook} from "./mock/MockBuybackDataHook.sol";
|
|
10
|
+
|
|
11
|
+
import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
|
|
12
|
+
import "@bananapus/721-hook-v6/script/helpers/Hook721DeploymentLib.sol";
|
|
13
|
+
import "@bananapus/suckers-v6/script/helpers/SuckerDeploymentLib.sol";
|
|
14
|
+
import "@croptop/core-v6/script/helpers/CroptopDeploymentLib.sol";
|
|
15
|
+
import "@bananapus/swap-terminal-v6/script/helpers/SwapTerminalDeploymentLib.sol";
|
|
16
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
17
|
+
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
18
|
+
import {MockPriceFeed} from "@bananapus/core-v6/test/mock/MockPriceFeed.sol";
|
|
19
|
+
import {MockERC20} from "@bananapus/core-v6/test/mock/MockERC20.sol";
|
|
20
|
+
import {REVLoans} from "../src/REVLoans.sol";
|
|
21
|
+
import {REVLoan} from "../src/structs/REVLoan.sol";
|
|
22
|
+
import {REVStageConfig, REVAutoIssuance} from "../src/structs/REVStageConfig.sol";
|
|
23
|
+
import {REVLoanSource} from "../src/structs/REVLoanSource.sol";
|
|
24
|
+
import {REVDescription} from "../src/structs/REVDescription.sol";
|
|
25
|
+
import {IREVLoans} from "./../src/interfaces/IREVLoans.sol";
|
|
26
|
+
import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
|
|
27
|
+
import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
|
|
28
|
+
import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
|
|
29
|
+
import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
|
|
30
|
+
import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
|
|
31
|
+
import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
|
|
32
|
+
import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
|
|
33
|
+
|
|
34
|
+
contract TestPR09_ConversionDocumentation is TestBaseWorkflow, JBTest {
|
|
35
|
+
bytes32 REV_DEPLOYER_SALT = "REVDeployer";
|
|
36
|
+
bytes32 ERC20_SALT = "REV_TOKEN";
|
|
37
|
+
|
|
38
|
+
REVDeployer REV_DEPLOYER;
|
|
39
|
+
JB721TiersHook EXAMPLE_HOOK;
|
|
40
|
+
IJB721TiersHookDeployer HOOK_DEPLOYER;
|
|
41
|
+
IJB721TiersHookStore HOOK_STORE;
|
|
42
|
+
IJBAddressRegistry ADDRESS_REGISTRY;
|
|
43
|
+
IREVLoans LOANS_CONTRACT;
|
|
44
|
+
MockERC20 TOKEN;
|
|
45
|
+
IJBSuckerRegistry SUCKER_REGISTRY;
|
|
46
|
+
CTPublisher PUBLISHER;
|
|
47
|
+
MockBuybackDataHook MOCK_BUYBACK;
|
|
48
|
+
|
|
49
|
+
uint256 FEE_PROJECT_ID;
|
|
50
|
+
|
|
51
|
+
address USER = makeAddr("user");
|
|
52
|
+
|
|
53
|
+
address private constant TRUSTED_FORWARDER = 0xB2b5841DBeF766d4b521221732F9B618fCf34A87;
|
|
54
|
+
|
|
55
|
+
function _getRevnetConfig(
|
|
56
|
+
string memory name,
|
|
57
|
+
string memory symbol,
|
|
58
|
+
bytes32 salt
|
|
59
|
+
)
|
|
60
|
+
internal
|
|
61
|
+
view
|
|
62
|
+
returns (
|
|
63
|
+
REVConfig memory configuration,
|
|
64
|
+
JBTerminalConfig[] memory terminalConfigurations,
|
|
65
|
+
REVSuckerDeploymentConfig memory suckerDeploymentConfiguration
|
|
66
|
+
)
|
|
67
|
+
{
|
|
68
|
+
uint8 decimals = 18;
|
|
69
|
+
uint256 decimalMultiplier = 10 ** decimals;
|
|
70
|
+
|
|
71
|
+
JBAccountingContext[] memory accountingContextsToAccept = new JBAccountingContext[](1);
|
|
72
|
+
accountingContextsToAccept[0] = JBAccountingContext({
|
|
73
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
terminalConfigurations = new JBTerminalConfig[](1);
|
|
77
|
+
terminalConfigurations[0] =
|
|
78
|
+
JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: accountingContextsToAccept});
|
|
79
|
+
|
|
80
|
+
REVStageConfig[] memory stageConfigurations = new REVStageConfig[](1);
|
|
81
|
+
JBSplit[] memory splits = new JBSplit[](1);
|
|
82
|
+
splits[0].beneficiary = payable(multisig());
|
|
83
|
+
splits[0].percent = 10_000;
|
|
84
|
+
|
|
85
|
+
stageConfigurations[0] = REVStageConfig({
|
|
86
|
+
startsAtOrAfter: uint40(block.timestamp),
|
|
87
|
+
autoIssuances: new REVAutoIssuance[](0),
|
|
88
|
+
splitPercent: 2000,
|
|
89
|
+
splits: splits,
|
|
90
|
+
initialIssuance: uint112(1000 * decimalMultiplier),
|
|
91
|
+
issuanceCutFrequency: 90 days,
|
|
92
|
+
issuanceCutPercent: JBConstants.MAX_WEIGHT_CUT_PERCENT / 2,
|
|
93
|
+
cashOutTaxRate: 6000,
|
|
94
|
+
extraMetadata: 0
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
configuration = REVConfig({
|
|
98
|
+
description: REVDescription(name, symbol, "ipfs://test", salt),
|
|
99
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
100
|
+
splitOperator: multisig(),
|
|
101
|
+
stageConfigurations: stageConfigurations
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
suckerDeploymentConfiguration = REVSuckerDeploymentConfig({
|
|
105
|
+
deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256(abi.encodePacked(salt))
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function setUp() public override {
|
|
110
|
+
super.setUp();
|
|
111
|
+
|
|
112
|
+
FEE_PROJECT_ID = jbProjects().createFor(multisig());
|
|
113
|
+
SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
|
|
114
|
+
HOOK_STORE = new JB721TiersHookStore();
|
|
115
|
+
EXAMPLE_HOOK = new JB721TiersHook(jbDirectory(), jbPermissions(), jbRulesets(), HOOK_STORE, multisig());
|
|
116
|
+
ADDRESS_REGISTRY = new JBAddressRegistry();
|
|
117
|
+
HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
|
|
118
|
+
PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
|
|
119
|
+
MOCK_BUYBACK = new MockBuybackDataHook();
|
|
120
|
+
TOKEN = new MockERC20("1/2 ETH", "1/2");
|
|
121
|
+
|
|
122
|
+
LOANS_CONTRACT = new REVLoans({
|
|
123
|
+
controller: jbController(),
|
|
124
|
+
projects: jbProjects(),
|
|
125
|
+
revId: FEE_PROJECT_ID,
|
|
126
|
+
owner: address(this),
|
|
127
|
+
permit2: permit2(),
|
|
128
|
+
trustedForwarder: TRUSTED_FORWARDER
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
|
|
132
|
+
jbController(),
|
|
133
|
+
SUCKER_REGISTRY,
|
|
134
|
+
FEE_PROJECT_ID,
|
|
135
|
+
HOOK_DEPLOYER,
|
|
136
|
+
PUBLISHER,
|
|
137
|
+
IJBRulesetDataHook(address(MOCK_BUYBACK)),
|
|
138
|
+
address(LOANS_CONTRACT),
|
|
139
|
+
TRUSTED_FORWARDER
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Deploy fee project as revnet.
|
|
143
|
+
vm.prank(multisig());
|
|
144
|
+
jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
|
|
145
|
+
|
|
146
|
+
(REVConfig memory cfg, JBTerminalConfig[] memory terms, REVSuckerDeploymentConfig memory suckerCfg) =
|
|
147
|
+
_getRevnetConfig("Revnet", "$REV", ERC20_SALT);
|
|
148
|
+
|
|
149
|
+
vm.prank(multisig());
|
|
150
|
+
REV_DEPLOYER.deployFor({
|
|
151
|
+
revnetId: FEE_PROJECT_ID,
|
|
152
|
+
configuration: cfg,
|
|
153
|
+
terminalConfigurations: terms,
|
|
154
|
+
suckerDeploymentConfiguration: suckerCfg
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// @notice Converting a blank project (no controller, no rulesets) into a revnet succeeds.
|
|
159
|
+
function test_convertBlankProject_succeeds() public {
|
|
160
|
+
// Create blank project owned by USER.
|
|
161
|
+
vm.prank(USER);
|
|
162
|
+
uint256 blankId = jbProjects().createFor(USER);
|
|
163
|
+
|
|
164
|
+
// Approve NFT transfer to REV_DEPLOYER.
|
|
165
|
+
vm.prank(USER);
|
|
166
|
+
jbProjects().approve(address(REV_DEPLOYER), blankId);
|
|
167
|
+
|
|
168
|
+
// Get revnet config.
|
|
169
|
+
(REVConfig memory cfg, JBTerminalConfig[] memory terms, REVSuckerDeploymentConfig memory suckerCfg) =
|
|
170
|
+
_getRevnetConfig("BlankConvert", "$BLK", "BLANK_TOKEN");
|
|
171
|
+
|
|
172
|
+
// Deploy as revnet — should succeed since project is blank.
|
|
173
|
+
vm.prank(USER);
|
|
174
|
+
uint256 deployed = REV_DEPLOYER.deployFor({
|
|
175
|
+
revnetId: blankId,
|
|
176
|
+
configuration: cfg,
|
|
177
|
+
terminalConfigurations: terms,
|
|
178
|
+
suckerDeploymentConfiguration: suckerCfg
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
assertEq(deployed, blankId, "Should return the same project ID");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// @notice Converting a project that already has a controller/rulesets reverts.
|
|
185
|
+
function test_convertProjectWithController_reverts() public {
|
|
186
|
+
// Create a project owned by USER (an EOA that can receive ERC721s).
|
|
187
|
+
vm.prank(USER);
|
|
188
|
+
uint256 projectId = jbProjects().createFor(USER);
|
|
189
|
+
|
|
190
|
+
// Launch rulesets on it directly via the controller (setting a controller + rulesets).
|
|
191
|
+
JBAccountingContext[] memory accountingContextsToAccept = new JBAccountingContext[](1);
|
|
192
|
+
accountingContextsToAccept[0] = JBAccountingContext({
|
|
193
|
+
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
194
|
+
});
|
|
195
|
+
JBTerminalConfig[] memory termConfigs = new JBTerminalConfig[](1);
|
|
196
|
+
termConfigs[0] =
|
|
197
|
+
JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: accountingContextsToAccept});
|
|
198
|
+
|
|
199
|
+
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
200
|
+
rulesetConfigs[0] = JBRulesetConfig({
|
|
201
|
+
mustStartAtOrAfter: uint48(block.timestamp),
|
|
202
|
+
duration: uint32(90 days),
|
|
203
|
+
weight: uint112(1000e18),
|
|
204
|
+
weightCutPercent: 0,
|
|
205
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
206
|
+
metadata: JBRulesetMetadata({
|
|
207
|
+
reservedPercent: 0,
|
|
208
|
+
cashOutTaxRate: 0,
|
|
209
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
210
|
+
pausePay: false,
|
|
211
|
+
pauseCreditTransfers: false,
|
|
212
|
+
allowOwnerMinting: false,
|
|
213
|
+
allowSetCustomToken: false,
|
|
214
|
+
allowTerminalMigration: false,
|
|
215
|
+
allowSetTerminals: false,
|
|
216
|
+
allowSetController: false,
|
|
217
|
+
allowAddAccountingContext: false,
|
|
218
|
+
allowAddPriceFeed: false,
|
|
219
|
+
ownerMustSendPayouts: false,
|
|
220
|
+
holdFees: false,
|
|
221
|
+
useTotalSurplusForCashOuts: false,
|
|
222
|
+
useDataHookForPay: false,
|
|
223
|
+
useDataHookForCashOut: false,
|
|
224
|
+
dataHook: address(0),
|
|
225
|
+
metadata: 0
|
|
226
|
+
}),
|
|
227
|
+
splitGroups: new JBSplitGroup[](0),
|
|
228
|
+
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
vm.prank(USER);
|
|
232
|
+
jbController()
|
|
233
|
+
.launchRulesetsFor({
|
|
234
|
+
projectId: projectId,
|
|
235
|
+
rulesetConfigurations: rulesetConfigs,
|
|
236
|
+
terminalConfigurations: termConfigs,
|
|
237
|
+
memo: ""
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Now try to convert this project to a revnet — should revert.
|
|
241
|
+
// Approve NFT to REV_DEPLOYER.
|
|
242
|
+
vm.prank(USER);
|
|
243
|
+
jbProjects().approve(address(REV_DEPLOYER), projectId);
|
|
244
|
+
|
|
245
|
+
(REVConfig memory cfg, JBTerminalConfig[] memory terms2, REVSuckerDeploymentConfig memory suckerCfg) =
|
|
246
|
+
_getRevnetConfig("FailConvert", "$FAIL", "FAIL_TOKEN");
|
|
247
|
+
|
|
248
|
+
// Should revert because rulesets already launched.
|
|
249
|
+
vm.prank(USER);
|
|
250
|
+
vm.expectRevert(abi.encodeWithSignature("JBController_RulesetsAlreadyLaunched(uint256)", projectId));
|
|
251
|
+
REV_DEPLOYER.deployFor({
|
|
252
|
+
revnetId: projectId,
|
|
253
|
+
configuration: cfg,
|
|
254
|
+
terminalConfigurations: terms2,
|
|
255
|
+
suckerDeploymentConfiguration: suckerCfg
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/// @notice After deploying a revnet from an existing project, the owner is REVDeployer (irreversible).
|
|
260
|
+
function test_conversionIsIrreversible() public {
|
|
261
|
+
// Create blank project owned by USER.
|
|
262
|
+
vm.prank(USER);
|
|
263
|
+
uint256 blankId = jbProjects().createFor(USER);
|
|
264
|
+
|
|
265
|
+
// Approve NFT transfer.
|
|
266
|
+
vm.prank(USER);
|
|
267
|
+
jbProjects().approve(address(REV_DEPLOYER), blankId);
|
|
268
|
+
|
|
269
|
+
// Get config.
|
|
270
|
+
(REVConfig memory cfg, JBTerminalConfig[] memory terms, REVSuckerDeploymentConfig memory suckerCfg) =
|
|
271
|
+
_getRevnetConfig("Irreversible", "$IRR", "IRR_TOKEN");
|
|
272
|
+
|
|
273
|
+
// Deploy as revnet.
|
|
274
|
+
vm.prank(USER);
|
|
275
|
+
REV_DEPLOYER.deployFor({
|
|
276
|
+
revnetId: blankId,
|
|
277
|
+
configuration: cfg,
|
|
278
|
+
terminalConfigurations: terms,
|
|
279
|
+
suckerDeploymentConfiguration: suckerCfg
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Verify the project's owner is now the REVDeployer (NFT transferred permanently).
|
|
283
|
+
assertEq(jbProjects().ownerOf(blankId), address(REV_DEPLOYER), "Owner should be REVDeployer after conversion");
|
|
284
|
+
|
|
285
|
+
// Verify the original user is no longer the owner.
|
|
286
|
+
assertTrue(jbProjects().ownerOf(blankId) != USER, "Original user should no longer own the project");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/// @notice Deploy with revnetId=0 creates a new project.
|
|
290
|
+
function test_deployNewRevnet_zeroRevnetId() public {
|
|
291
|
+
(REVConfig memory cfg, JBTerminalConfig[] memory terms, REVSuckerDeploymentConfig memory suckerCfg) =
|
|
292
|
+
_getRevnetConfig("NewRevnet", "$NEW", "NEW_TOKEN");
|
|
293
|
+
|
|
294
|
+
uint256 newId = REV_DEPLOYER.deployFor({
|
|
295
|
+
revnetId: 0, configuration: cfg, terminalConfigurations: terms, suckerDeploymentConfiguration: suckerCfg
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Verify the project was created (ID > fee project).
|
|
299
|
+
assertGt(newId, FEE_PROJECT_ID, "New project ID should be greater than fee project ID");
|
|
300
|
+
|
|
301
|
+
// Verify the owner is the REVDeployer.
|
|
302
|
+
assertEq(jbProjects().ownerOf(newId), address(REV_DEPLOYER), "New revnet owner should be REVDeployer");
|
|
303
|
+
}
|
|
304
|
+
}
|