@rev-net/core-v6 0.0.36 → 0.0.39

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.
Files changed (101) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/README.md +6 -7
  3. package/foundry.toml +1 -1
  4. package/package.json +23 -16
  5. package/references/operations.md +1 -1
  6. package/references/runtime.md +1 -1
  7. package/script/Deploy.s.sol +12 -9
  8. package/src/REVDeployer.sol +60 -65
  9. package/src/REVHiddenTokens.sol +2 -2
  10. package/src/REVLoans.sol +134 -90
  11. package/src/REVOwner.sol +124 -17
  12. package/src/interfaces/IREVDeployer.sol +2 -1
  13. package/src/interfaces/IREVHiddenTokens.sol +4 -1
  14. package/src/interfaces/IREVOwner.sol +5 -0
  15. package/ADMINISTRATION.md +0 -73
  16. package/ARCHITECTURE.md +0 -116
  17. package/AUDIT_INSTRUCTIONS.md +0 -90
  18. package/RISKS.md +0 -97
  19. package/SKILLS.md +0 -46
  20. package/STYLE_GUIDE.md +0 -610
  21. package/USER_JOURNEYS.md +0 -195
  22. package/foundry.lock +0 -11
  23. package/slither-ci.config.json +0 -10
  24. package/sphinx.lock +0 -507
  25. package/test/REV.integrations.t.sol +0 -573
  26. package/test/REVAutoIssuanceFuzz.t.sol +0 -328
  27. package/test/REVDeployerRegressions.t.sol +0 -396
  28. package/test/REVInvincibility.t.sol +0 -1371
  29. package/test/REVInvincibilityHandler.sol +0 -387
  30. package/test/REVLifecycle.t.sol +0 -420
  31. package/test/REVLoans.invariants.t.sol +0 -724
  32. package/test/REVLoansAttacks.t.sol +0 -816
  33. package/test/REVLoansFeeRecovery.t.sol +0 -783
  34. package/test/REVLoansFindings.t.sol +0 -711
  35. package/test/REVLoansRegressions.t.sol +0 -364
  36. package/test/REVLoansSourceFeeRecovery.t.sol +0 -517
  37. package/test/REVLoansSourced.t.sol +0 -1839
  38. package/test/REVLoansUnSourced.t.sol +0 -409
  39. package/test/TestAuditFixVerification.t.sol +0 -675
  40. package/test/TestBurnHeldTokens.t.sol +0 -394
  41. package/test/TestCEIPattern.t.sol +0 -508
  42. package/test/TestCashOutCallerValidation.t.sol +0 -452
  43. package/test/TestConversionDocumentation.t.sol +0 -368
  44. package/test/TestCrossCurrencyReclaim.t.sol +0 -610
  45. package/test/TestCrossSourceReallocation.t.sol +0 -361
  46. package/test/TestERC2771MetaTx.t.sol +0 -585
  47. package/test/TestEmptyBuybackSpecs.t.sol +0 -300
  48. package/test/TestFlashLoanSurplus.t.sol +0 -365
  49. package/test/TestHiddenTokens.t.sol +0 -474
  50. package/test/TestHookArrayOOB.t.sol +0 -278
  51. package/test/TestLiquidationBehavior.t.sol +0 -398
  52. package/test/TestLoanSourceRotation.t.sol +0 -553
  53. package/test/TestLoansCashOutDelay.t.sol +0 -493
  54. package/test/TestLongTailEconomics.t.sol +0 -677
  55. package/test/TestLowFindings.t.sol +0 -677
  56. package/test/TestMixedFixes.t.sol +0 -593
  57. package/test/TestPermit2Signatures.t.sol +0 -683
  58. package/test/TestReallocationSandwich.t.sol +0 -412
  59. package/test/TestRevnetRegressions.t.sol +0 -350
  60. package/test/TestSplitWeightAdjustment.t.sol +0 -527
  61. package/test/TestSplitWeightE2E.t.sol +0 -605
  62. package/test/TestSplitWeightFork.t.sol +0 -855
  63. package/test/TestStageTransitionBorrowable.t.sol +0 -301
  64. package/test/TestSwapTerminalPermission.t.sol +0 -262
  65. package/test/TestTerminalEncodingInHash.t.sol +0 -326
  66. package/test/TestUint112Overflow.t.sol +0 -311
  67. package/test/TestZeroAmountLoanGuard.t.sol +0 -378
  68. package/test/TestZeroRepayment.t.sol +0 -354
  69. package/test/audit/CodexCrossChainBuybackRouteMismatch.t.sol +0 -184
  70. package/test/audit/CodexPhantomSurplusTerminal.t.sol +0 -367
  71. package/test/audit/CodexREVOwnerRemoteSurplusCurrencyMismatch.t.sol +0 -142
  72. package/test/audit/LoanIdOverflowGuard.t.sol +0 -523
  73. package/test/audit/NemesisOperatorDelegation.t.sol +0 -356
  74. package/test/audit/SupportsInterfaceTest.t.sol +0 -51
  75. package/test/audit/TestFeeAllowanceLeak.t.sol +0 -197
  76. package/test/audit/TestLoansAndDeployerFixes.t.sol +0 -576
  77. package/test/fork/ForkTestBase.sol +0 -727
  78. package/test/fork/TestAutoIssuanceFork.t.sol +0 -148
  79. package/test/fork/TestCashOutFork.t.sol +0 -253
  80. package/test/fork/TestIssuanceDecayFork.t.sol +0 -158
  81. package/test/fork/TestLoanBorrowFork.t.sol +0 -163
  82. package/test/fork/TestLoanCrossRulesetFork.t.sol +0 -308
  83. package/test/fork/TestLoanERC20Fork.t.sol +0 -465
  84. package/test/fork/TestLoanLiquidationFork.t.sol +0 -135
  85. package/test/fork/TestLoanReallocateFork.t.sol +0 -113
  86. package/test/fork/TestLoanRepayFork.t.sol +0 -188
  87. package/test/fork/TestLoanTransferFork.t.sol +0 -143
  88. package/test/fork/TestPermit2PaymentFork.t.sol +0 -300
  89. package/test/fork/TestSplitWeightFork.t.sol +0 -189
  90. package/test/helpers/MaliciousContracts.sol +0 -247
  91. package/test/helpers/REVEmpty721Config.sol +0 -45
  92. package/test/mock/MockBuybackCashOutRecorder.sol +0 -84
  93. package/test/mock/MockBuybackDataHook.sol +0 -112
  94. package/test/mock/MockBuybackDataHookMintPath.sol +0 -68
  95. package/test/mock/MockSuckerRegistry.sol +0 -17
  96. package/test/regression/TestBurnPermissionRequired.t.sol +0 -294
  97. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +0 -232
  98. package/test/regression/TestCrossRevnetLiquidation.t.sol +0 -255
  99. package/test/regression/TestCumulativeLoanCounter.t.sol +0 -361
  100. package/test/regression/TestLiquidateGapHandling.t.sol +0 -394
  101. package/test/regression/TestZeroPriceFeed.t.sol +0 -422
@@ -1,585 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.28;
3
-
4
- // forge-lint: disable-next-line(unaliased-plain-import)
5
- import "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 {JBSingleAllowance} from "@bananapus/core-v6/src/structs/JBSingleAllowance.sol";
28
- import {MockPriceFeed} from "@bananapus/core-v6/test/mock/MockPriceFeed.sol";
29
- import {MockERC20} from "@bananapus/core-v6/test/mock/MockERC20.sol";
30
- import {REVLoans} from "../src/REVLoans.sol";
31
- import {REVLoan} from "../src/structs/REVLoan.sol";
32
- import {REVStageConfig, REVAutoIssuance} from "../src/structs/REVStageConfig.sol";
33
- import {REVLoanSource} from "../src/structs/REVLoanSource.sol";
34
- import {REVDescription} from "../src/structs/REVDescription.sol";
35
- import {IREVLoans} from "./../src/interfaces/IREVLoans.sol";
36
- import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
37
- import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
38
- import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
39
- import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
40
- import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
41
- import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
42
- import {IJB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721CheckpointsDeployer.sol";
43
- import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
44
- import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
45
- import {REVEmpty721Config} from "./helpers/REVEmpty721Config.sol";
46
- import {ERC2771Forwarder} from "@openzeppelin/contracts/metatx/ERC2771Forwarder.sol";
47
- import {ERC2771ForwarderMock, ForwardRequest} from "@bananapus/core-v6/test/mock/ERC2771ForwarderMock.sol";
48
- import {REVOwner} from "../src/REVOwner.sol";
49
- import {IREVDeployer} from "../src/interfaces/IREVDeployer.sol";
50
- import {MockSuckerRegistry} from "./mock/MockSuckerRegistry.sol";
51
-
52
- struct MetaTxProjectConfig {
53
- REVConfig configuration;
54
- JBTerminalConfig[] terminalConfigurations;
55
- REVSuckerDeploymentConfig suckerDeploymentConfiguration;
56
- }
57
-
58
- /// @title TestERC2771MetaTx
59
- /// @notice Tests that REVLoans and REVDeployer correctly use ERC2771Context,
60
- /// ensuring _msgSender() returns the actual user when called through a trusted forwarder,
61
- /// and falls back to msg.sender when called through an untrusted one.
62
- contract TestERC2771MetaTx is TestBaseWorkflow {
63
- // forge-lint: disable-next-line(mixed-case-variable)
64
- bytes32 REV_DEPLOYER_SALT = "REVDeployer";
65
- // forge-lint: disable-next-line(mixed-case-variable)
66
- bytes32 ERC20_SALT = "REV_TOKEN";
67
-
68
- // forge-lint: disable-next-line(mixed-case-variable)
69
- REVDeployer REV_DEPLOYER;
70
- // forge-lint: disable-next-line(mixed-case-variable)
71
- REVOwner REV_OWNER;
72
- // forge-lint: disable-next-line(mixed-case-variable)
73
- JB721TiersHook EXAMPLE_HOOK;
74
- // forge-lint: disable-next-line(mixed-case-variable)
75
- IJB721TiersHookDeployer HOOK_DEPLOYER;
76
- // forge-lint: disable-next-line(mixed-case-variable)
77
- IJB721TiersHookStore HOOK_STORE;
78
- // forge-lint: disable-next-line(mixed-case-variable)
79
- IJBAddressRegistry ADDRESS_REGISTRY;
80
- // forge-lint: disable-next-line(mixed-case-variable)
81
- REVLoans LOANS_CONTRACT;
82
- // forge-lint: disable-next-line(mixed-case-variable)
83
- MockERC20 TOKEN;
84
- // forge-lint: disable-next-line(mixed-case-variable)
85
- IJBSuckerRegistry SUCKER_REGISTRY;
86
- // forge-lint: disable-next-line(mixed-case-variable)
87
- CTPublisher PUBLISHER;
88
- // forge-lint: disable-next-line(mixed-case-variable)
89
- MockBuybackDataHook MOCK_BUYBACK;
90
-
91
- // forge-lint: disable-next-line(mixed-case-variable)
92
- uint256 FEE_PROJECT_ID;
93
- // forge-lint: disable-next-line(mixed-case-variable)
94
- uint256 REVNET_ID;
95
-
96
- // The trusted forwarder mock deployed at a specific address.
97
- ERC2771ForwarderMock internal erc2771Forwarder;
98
- address internal constant FORWARDER_ADDRESS = address(123_456);
99
-
100
- // Meta-tx signer and relayer.
101
- uint256 internal signerPrivateKey;
102
- uint256 internal relayerPrivateKey;
103
- address internal signerAddr;
104
- address internal relayerAddr;
105
-
106
- function _getFeeProjectConfig() internal view returns (MetaTxProjectConfig memory) {
107
- string memory name = "Revnet";
108
- string memory symbol = "$REV";
109
- string memory projectUri = "ipfs://QmNRHT91HcDgMcenebYX7rJigt77cgNcosvuhX21wkF3tx";
110
- uint8 decimals = 18;
111
- uint256 decimalMultiplier = 10 ** decimals;
112
-
113
- JBAccountingContext[] memory accountingContextsToAccept = new JBAccountingContext[](2);
114
- accountingContextsToAccept[0] = JBAccountingContext({
115
- token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
116
- });
117
- accountingContextsToAccept[1] =
118
- JBAccountingContext({token: address(TOKEN), decimals: 6, currency: uint32(uint160(address(TOKEN)))});
119
-
120
- JBTerminalConfig[] memory terminalConfigurations = new JBTerminalConfig[](1);
121
- terminalConfigurations[0] =
122
- JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: accountingContextsToAccept});
123
-
124
- REVStageConfig[] memory stageConfigurations = new REVStageConfig[](1);
125
- JBSplit[] memory splits = new JBSplit[](1);
126
- splits[0].beneficiary = payable(multisig());
127
- splits[0].percent = 10_000;
128
-
129
- REVAutoIssuance[] memory issuanceConfs = new REVAutoIssuance[](1);
130
- issuanceConfs[0] = REVAutoIssuance({
131
- // forge-lint: disable-next-line(unsafe-typecast)
132
- chainId: uint32(block.chainid),
133
- // forge-lint: disable-next-line(unsafe-typecast)
134
- count: uint104(70_000 * decimalMultiplier),
135
- beneficiary: multisig()
136
- });
137
-
138
- stageConfigurations[0] = REVStageConfig({
139
- startsAtOrAfter: uint40(block.timestamp),
140
- autoIssuances: issuanceConfs,
141
- splitPercent: 2000,
142
- splits: splits,
143
- // forge-lint: disable-next-line(unsafe-typecast)
144
- initialIssuance: uint112(1000 * decimalMultiplier),
145
- issuanceCutFrequency: 90 days,
146
- issuanceCutPercent: JBConstants.MAX_WEIGHT_CUT_PERCENT / 2,
147
- cashOutTaxRate: 6000,
148
- extraMetadata: 0
149
- });
150
-
151
- REVConfig memory revnetConfiguration = REVConfig({
152
- // forge-lint: disable-next-line(named-struct-fields)
153
- description: REVDescription(name, symbol, projectUri, ERC20_SALT),
154
- baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
155
- splitOperator: multisig(),
156
- stageConfigurations: stageConfigurations
157
- });
158
-
159
- return MetaTxProjectConfig({
160
- configuration: revnetConfiguration,
161
- terminalConfigurations: terminalConfigurations,
162
- suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
163
- deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256(abi.encodePacked("REV"))
164
- })
165
- });
166
- }
167
-
168
- function _getRevnetConfig() internal view returns (MetaTxProjectConfig memory) {
169
- string memory name = "NANA";
170
- string memory symbol = "$NANA";
171
- string memory projectUri = "ipfs://QmNRHT91HcDgMcenebYX7rJigt77cgNxosvuhX21wkF3tx";
172
- uint8 decimals = 18;
173
- uint256 decimalMultiplier = 10 ** decimals;
174
-
175
- JBAccountingContext[] memory accountingContextsToAccept = new JBAccountingContext[](2);
176
- accountingContextsToAccept[0] = JBAccountingContext({
177
- token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
178
- });
179
- accountingContextsToAccept[1] =
180
- JBAccountingContext({token: address(TOKEN), decimals: 6, currency: uint32(uint160(address(TOKEN)))});
181
-
182
- JBTerminalConfig[] memory terminalConfigurations = new JBTerminalConfig[](1);
183
- terminalConfigurations[0] =
184
- JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: accountingContextsToAccept});
185
-
186
- JBSplit[] memory splits = new JBSplit[](1);
187
- splits[0].beneficiary = payable(multisig());
188
- splits[0].percent = 10_000;
189
-
190
- REVStageConfig[] memory stageConfigurations = new REVStageConfig[](1);
191
- REVAutoIssuance[] memory issuanceConfs = new REVAutoIssuance[](1);
192
- issuanceConfs[0] = REVAutoIssuance({
193
- // forge-lint: disable-next-line(unsafe-typecast)
194
- chainId: uint32(block.chainid),
195
- // forge-lint: disable-next-line(unsafe-typecast)
196
- count: uint104(70_000 * decimalMultiplier),
197
- beneficiary: multisig()
198
- });
199
-
200
- stageConfigurations[0] = REVStageConfig({
201
- startsAtOrAfter: uint40(block.timestamp),
202
- autoIssuances: issuanceConfs,
203
- splitPercent: 2000,
204
- splits: splits,
205
- // forge-lint: disable-next-line(unsafe-typecast)
206
- initialIssuance: uint112(1000 * decimalMultiplier),
207
- issuanceCutFrequency: 90 days,
208
- issuanceCutPercent: JBConstants.MAX_WEIGHT_CUT_PERCENT / 2,
209
- cashOutTaxRate: 6000,
210
- extraMetadata: 0
211
- });
212
-
213
- REVConfig memory revnetConfiguration = REVConfig({
214
- // forge-lint: disable-next-line(named-struct-fields)
215
- description: REVDescription(name, symbol, projectUri, "NANA_TOKEN"),
216
- baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
217
- splitOperator: multisig(),
218
- stageConfigurations: stageConfigurations
219
- });
220
-
221
- return MetaTxProjectConfig({
222
- configuration: revnetConfiguration,
223
- terminalConfigurations: terminalConfigurations,
224
- suckerDeploymentConfiguration: REVSuckerDeploymentConfig({
225
- deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: keccak256(abi.encodePacked("NANA"))
226
- })
227
- });
228
- }
229
-
230
- // =========================================================================
231
- // Helper: construct a ForwardRequestData from a ForwardRequest
232
- // =========================================================================
233
-
234
- function _forgeRequestData(
235
- uint256 value,
236
- uint256 nonce,
237
- uint48 deadline,
238
- bytes memory data,
239
- address target
240
- )
241
- internal
242
- view
243
- returns (ERC2771Forwarder.ForwardRequestData memory)
244
- {
245
- ForwardRequest memory request = ForwardRequest({
246
- from: signerAddr, to: target, value: value, gas: 3_000_000, nonce: nonce, deadline: deadline, data: data
247
- });
248
-
249
- bytes32 digest = erc2771Forwarder.structHash(request);
250
- (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, digest);
251
- bytes memory signature = abi.encodePacked(r, s, v);
252
-
253
- return ERC2771Forwarder.ForwardRequestData({
254
- from: request.from,
255
- to: request.to,
256
- value: request.value,
257
- gas: request.gas,
258
- deadline: request.deadline,
259
- data: request.data,
260
- signature: signature
261
- });
262
- }
263
-
264
- function setUp() public override {
265
- super.setUp();
266
-
267
- signerPrivateKey = 0xA11CE;
268
- relayerPrivateKey = 0xB0B;
269
- signerAddr = vm.addr(signerPrivateKey);
270
- relayerAddr = vm.addr(relayerPrivateKey);
271
-
272
- // Deploy ERC2771ForwarderMock at the FORWARDER_ADDRESS using deployCodeTo.
273
- deployCodeTo("ERC2771ForwarderMock.sol", abi.encode("ERC2771Forwarder"), FORWARDER_ADDRESS);
274
- erc2771Forwarder = ERC2771ForwarderMock(FORWARDER_ADDRESS);
275
-
276
- FEE_PROJECT_ID = jbProjects().createFor(multisig());
277
-
278
- SUCKER_REGISTRY = new JBSuckerRegistry(jbDirectory(), jbPermissions(), multisig(), address(0));
279
- HOOK_STORE = new JB721TiersHookStore();
280
- EXAMPLE_HOOK = new JB721TiersHook(
281
- jbDirectory(),
282
- jbPermissions(),
283
- jbPrices(),
284
- jbRulesets(),
285
- HOOK_STORE,
286
- jbSplits(),
287
- IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
288
- multisig()
289
- );
290
- ADDRESS_REGISTRY = new JBAddressRegistry();
291
- HOOK_DEPLOYER = new JB721TiersHookDeployer(EXAMPLE_HOOK, HOOK_STORE, ADDRESS_REGISTRY, multisig());
292
- PUBLISHER = new CTPublisher(jbDirectory(), jbPermissions(), FEE_PROJECT_ID, multisig());
293
- MOCK_BUYBACK = new MockBuybackDataHook();
294
- TOKEN = new MockERC20("1/2 ETH", "1/2");
295
-
296
- MockPriceFeed priceFeed = new MockPriceFeed(1e21, 6);
297
- vm.prank(multisig());
298
- jbPrices()
299
- .addPriceFeedFor(0, uint32(uint160(address(TOKEN))), uint32(uint160(JBConstants.NATIVE_TOKEN)), priceFeed);
300
-
301
- // Deploy LOANS_CONTRACT with the forwarder as trusted forwarder.
302
- LOANS_CONTRACT = new REVLoans({
303
- controller: jbController(),
304
- suckerRegistry: IJBSuckerRegistry(address(new MockSuckerRegistry())),
305
- revId: FEE_PROJECT_ID,
306
- owner: address(this),
307
- permit2: permit2(),
308
- trustedForwarder: FORWARDER_ADDRESS
309
- });
310
-
311
- REV_OWNER = new REVOwner(
312
- IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
313
- jbDirectory(),
314
- FEE_PROJECT_ID,
315
- SUCKER_REGISTRY,
316
- address(LOANS_CONTRACT),
317
- address(0)
318
- );
319
-
320
- REV_DEPLOYER = new REVDeployer{salt: REV_DEPLOYER_SALT}(
321
- jbController(),
322
- SUCKER_REGISTRY,
323
- FEE_PROJECT_ID,
324
- HOOK_DEPLOYER,
325
- PUBLISHER,
326
- IJBBuybackHookRegistry(address(MOCK_BUYBACK)),
327
- address(LOANS_CONTRACT),
328
- FORWARDER_ADDRESS,
329
- address(REV_OWNER)
330
- );
331
-
332
- REV_OWNER.setDeployer(REV_DEPLOYER);
333
-
334
- // Approve the deployer to configure the project.
335
- vm.prank(address(multisig()));
336
- jbProjects().approve(address(REV_DEPLOYER), FEE_PROJECT_ID);
337
-
338
- // Deploy fee project.
339
- MetaTxProjectConfig memory feeProjectConfig = _getFeeProjectConfig();
340
- vm.prank(address(multisig()));
341
- REV_DEPLOYER.deployFor({
342
- revnetId: FEE_PROJECT_ID,
343
- configuration: feeProjectConfig.configuration,
344
- terminalConfigurations: feeProjectConfig.terminalConfigurations,
345
- suckerDeploymentConfiguration: feeProjectConfig.suckerDeploymentConfiguration,
346
- tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
347
- allowedPosts: REVEmpty721Config.emptyAllowedPosts()
348
- });
349
-
350
- // Deploy second revnet.
351
- MetaTxProjectConfig memory revnetConfig = _getRevnetConfig();
352
- (REVNET_ID,) = REV_DEPLOYER.deployFor({
353
- revnetId: 0,
354
- configuration: revnetConfig.configuration,
355
- terminalConfigurations: revnetConfig.terminalConfigurations,
356
- suckerDeploymentConfiguration: revnetConfig.suckerDeploymentConfiguration,
357
- tiered721HookConfiguration: REVEmpty721Config.empty721Config(uint32(uint160(JBConstants.NATIVE_TOKEN))),
358
- allowedPosts: REVEmpty721Config.emptyAllowedPosts()
359
- });
360
-
361
- // Fund the signer and relayer.
362
- vm.deal(signerAddr, 1000e18);
363
- vm.deal(relayerAddr, 1000e18);
364
- }
365
-
366
- // =========================================================================
367
- // Test: ERC2771 trusted forwarder is correctly configured
368
- // =========================================================================
369
-
370
- /// @notice Verifies that the trusted forwarder is set correctly on REVLoans.
371
- function test_erc2771_trustedForwarderIsSet() public view {
372
- assertTrue(LOANS_CONTRACT.isTrustedForwarder(FORWARDER_ADDRESS), "Forwarder should be trusted");
373
- assertFalse(LOANS_CONTRACT.isTrustedForwarder(address(0x999)), "Random address should not be trusted");
374
- }
375
-
376
- // =========================================================================
377
- // Test: borrow via trusted forwarder — loan owned by signer, not relayer
378
- // =========================================================================
379
-
380
- /// @notice When borrowFrom() is called through the trusted forwarder, the loan NFT
381
- /// should be minted to the actual signer (from the appended calldata),
382
- /// not the relayer who submitted the transaction.
383
- function test_erc2771_borrowViaForwarder() public {
384
- // First, signer pays into the revnet to get tokens.
385
- vm.prank(signerAddr);
386
- uint256 tokenCount =
387
- jbMultiTerminal().pay{value: 10e18}(REVNET_ID, JBConstants.NATIVE_TOKEN, 10e18, signerAddr, 0, "", "");
388
- assertTrue(tokenCount > 0, "Should receive tokens from payment");
389
-
390
- // Check borrowable amount.
391
- uint256 borrowable =
392
- LOANS_CONTRACT.borrowableAmountFrom(REVNET_ID, tokenCount, 18, uint32(uint160(JBConstants.NATIVE_TOKEN)));
393
- vm.assume(borrowable > 0);
394
-
395
- // Mock permission for loans contract to burn the signer's tokens.
396
- mockExpect(
397
- address(jbPermissions()),
398
- abi.encodeCall(
399
- IJBPermissions.hasPermission, (address(LOANS_CONTRACT), signerAddr, REVNET_ID, 11, true, true)
400
- ),
401
- abi.encode(true)
402
- );
403
-
404
- // Encode the borrowFrom call.
405
- REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
406
-
407
- bytes memory borrowData = abi.encodeWithSelector(
408
- IREVLoans.borrowFrom.selector,
409
- REVNET_ID,
410
- source,
411
- 0, // minBorrowAmount
412
- tokenCount,
413
- payable(signerAddr),
414
- uint256(25), // MIN_PREPAID_FEE_PERCENT
415
- signerAddr // holder
416
- );
417
-
418
- // Build the forwarded request signed by the signer.
419
- ERC2771Forwarder.ForwardRequestData memory requestData = _forgeRequestData({
420
- value: 0, nonce: 0, deadline: uint48(block.timestamp + 1), data: borrowData, target: address(LOANS_CONTRACT)
421
- });
422
-
423
- // Relayer submits the meta-tx.
424
- vm.prank(relayerAddr);
425
- erc2771Forwarder.execute{value: 0}(requestData);
426
-
427
- // Verify the loan was created and is owned by the signer (not the relayer).
428
- uint256 loansBalance = LOANS_CONTRACT.balanceOf(signerAddr);
429
- assertTrue(loansBalance > 0, "Signer should own the loan NFT");
430
-
431
- uint256 relayerLoans = LOANS_CONTRACT.balanceOf(relayerAddr);
432
- assertEq(relayerLoans, 0, "Relayer should not own any loan NFTs");
433
- }
434
-
435
- // =========================================================================
436
- // Test: repay via trusted forwarder
437
- // =========================================================================
438
-
439
- /// @notice When repayLoan() is called through the trusted forwarder, the loan owner
440
- /// check should use _msgSender() (the signer), not msg.sender (the forwarder).
441
- function test_erc2771_repayViaForwarder() public {
442
- // Signer pays to get tokens and creates a loan.
443
- vm.prank(signerAddr);
444
- uint256 tokenCount =
445
- jbMultiTerminal().pay{value: 10e18}(REVNET_ID, JBConstants.NATIVE_TOKEN, 10e18, signerAddr, 0, "", "");
446
-
447
- uint256 borrowable =
448
- LOANS_CONTRACT.borrowableAmountFrom(REVNET_ID, tokenCount, 18, uint32(uint160(JBConstants.NATIVE_TOKEN)));
449
- vm.assume(borrowable > 0);
450
-
451
- // Mock permission.
452
- mockExpect(
453
- address(jbPermissions()),
454
- abi.encodeCall(
455
- IJBPermissions.hasPermission, (address(LOANS_CONTRACT), signerAddr, REVNET_ID, 11, true, true)
456
- ),
457
- abi.encode(true)
458
- );
459
-
460
- REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
461
-
462
- vm.prank(signerAddr);
463
- (uint256 loanId,) =
464
- LOANS_CONTRACT.borrowFrom(REVNET_ID, source, 0, tokenCount, payable(signerAddr), 25, signerAddr);
465
-
466
- REVLoan memory loan = LOANS_CONTRACT.loanOf(loanId);
467
- assertTrue(loan.amount > 0, "Loan should exist");
468
-
469
- // Calculate repay amount.
470
- uint256 sourceFee = LOANS_CONTRACT.determineSourceFeeAmount(loan, loan.amount);
471
- uint256 totalRepay = loan.amount + sourceFee;
472
-
473
- // Encode the repayLoan call.
474
- bytes memory repayData = abi.encodeWithSelector(
475
- IREVLoans.repayLoan.selector,
476
- loanId,
477
- totalRepay * 2, // maxRepayBorrowAmount
478
- loan.collateral,
479
- payable(signerAddr),
480
- JBSingleAllowance({sigDeadline: 0, amount: 0, expiration: 0, nonce: 0, signature: ""})
481
- );
482
-
483
- // Build the forwarded request signed by the signer.
484
- ERC2771Forwarder.ForwardRequestData memory requestData = _forgeRequestData({
485
- value: totalRepay * 2,
486
- nonce: 0,
487
- deadline: uint48(block.timestamp + 1),
488
- data: repayData,
489
- target: address(LOANS_CONTRACT)
490
- });
491
-
492
- // Relayer submits the meta-tx with ETH.
493
- vm.prank(relayerAddr);
494
- erc2771Forwarder.execute{value: totalRepay * 2}(requestData);
495
-
496
- // Verify loan was repaid: collateral should be zero.
497
- uint256 totalCollateral = LOANS_CONTRACT.totalCollateralOf(REVNET_ID);
498
- assertEq(totalCollateral, 0, "All collateral should be returned after repay via forwarder");
499
- }
500
-
501
- // =========================================================================
502
- // Test: untrusted forwarder uses msg.sender, not appended address
503
- // =========================================================================
504
-
505
- /// @notice When a call is forwarded through a forwarder that is NOT the trusted one,
506
- /// OpenZeppelin's ERC2771Forwarder checks `isTrustedForwarder` on the target
507
- /// and reverts with `ERC2771UntrustfulTarget`. This prevents identity spoofing
508
- /// at the forwarder level itself.
509
- function test_erc2771_untrustedForwarder_usesMsgSender() public {
510
- // Deploy a different forwarder at a different address (NOT the trusted one).
511
- address untrustedForwarderAddr = address(789_012);
512
- deployCodeTo("ERC2771ForwarderMock.sol", abi.encode("UntrustedForwarder"), untrustedForwarderAddr);
513
- ERC2771ForwarderMock untrustedForwarder = ERC2771ForwarderMock(untrustedForwarderAddr);
514
-
515
- // Verify the untrusted forwarder is not trusted by the LOANS_CONTRACT.
516
- assertFalse(
517
- LOANS_CONTRACT.isTrustedForwarder(untrustedForwarderAddr), "Untrusted forwarder should not be trusted"
518
- );
519
-
520
- // Signer pays to get tokens.
521
- vm.prank(signerAddr);
522
- uint256 tokenCount =
523
- jbMultiTerminal().pay{value: 10e18}(REVNET_ID, JBConstants.NATIVE_TOKEN, 10e18, signerAddr, 0, "", "");
524
-
525
- uint256 borrowable =
526
- LOANS_CONTRACT.borrowableAmountFrom(REVNET_ID, tokenCount, 18, uint32(uint160(JBConstants.NATIVE_TOKEN)));
527
- vm.assume(borrowable > 0);
528
-
529
- // Encode the borrowFrom call.
530
- REVLoanSource memory source = REVLoanSource({token: JBConstants.NATIVE_TOKEN, terminal: jbMultiTerminal()});
531
-
532
- bytes memory borrowData = abi.encodeWithSelector(
533
- IREVLoans.borrowFrom.selector, REVNET_ID, source, 0, tokenCount, payable(signerAddr), uint256(25)
534
- );
535
-
536
- // Build a forwarded request using the signer's key via the UNTRUSTED forwarder.
537
- ForwardRequest memory request = ForwardRequest({
538
- from: signerAddr,
539
- to: address(LOANS_CONTRACT),
540
- value: 0,
541
- gas: 3_000_000,
542
- nonce: 0,
543
- deadline: uint48(block.timestamp + 1),
544
- data: borrowData
545
- });
546
-
547
- bytes32 digest = untrustedForwarder.structHash(request);
548
- (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, digest);
549
- bytes memory signature = abi.encodePacked(r, s, v);
550
-
551
- ERC2771Forwarder.ForwardRequestData memory requestData = ERC2771Forwarder.ForwardRequestData({
552
- from: request.from,
553
- to: request.to,
554
- value: request.value,
555
- gas: request.gas,
556
- deadline: request.deadline,
557
- data: request.data,
558
- signature: signature
559
- });
560
-
561
- // OpenZeppelin's ERC2771Forwarder.execute() checks isTrustedForwarder on the
562
- // target contract. Since the untrusted forwarder is not the trusted one,
563
- // it reverts with ERC2771UntrustfulTarget(target, forwarder).
564
- vm.prank(relayerAddr);
565
- vm.expectRevert(
566
- abi.encodeWithSelector(
567
- ERC2771Forwarder.ERC2771UntrustfulTarget.selector, address(LOANS_CONTRACT), untrustedForwarderAddr
568
- )
569
- );
570
- untrustedForwarder.execute{value: 0}(requestData);
571
-
572
- // Verify no loan was created for the signer.
573
- uint256 signerLoansBalance = LOANS_CONTRACT.balanceOf(signerAddr);
574
- assertEq(signerLoansBalance, 0, "No loan should exist since untrusted forwarder was rejected");
575
- }
576
-
577
- // =========================================================================
578
- // Test: forwarder is correctly deployed and functional
579
- // =========================================================================
580
-
581
- /// @notice Sanity check that the forwarder mock deployed correctly.
582
- function test_erc2771_forwarderDeployed() public view {
583
- assertTrue(erc2771Forwarder.deployed(), "Forwarder should report as deployed");
584
- }
585
- }