@rev-net/core-v6 0.0.37 → 0.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) 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 +69 -67
  9. package/src/REVHiddenTokens.sol +2 -2
  10. package/src/REVLoans.sol +26 -22
  11. package/src/REVOwner.sol +147 -29
  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/src/structs/REVAutoIssuance.sol +4 -2
  16. package/src/structs/REVConfig.sol +8 -5
  17. package/src/structs/REVDescription.sol +6 -5
  18. package/src/structs/REVLoan.sol +8 -5
  19. package/src/structs/REVStageConfig.sol +14 -16
  20. package/ADMINISTRATION.md +0 -73
  21. package/ARCHITECTURE.md +0 -116
  22. package/AUDIT_INSTRUCTIONS.md +0 -90
  23. package/RISKS.md +0 -107
  24. package/SKILLS.md +0 -46
  25. package/STYLE_GUIDE.md +0 -610
  26. package/USER_JOURNEYS.md +0 -195
  27. package/foundry.lock +0 -11
  28. package/slither-ci.config.json +0 -10
  29. package/sphinx.lock +0 -507
  30. package/test/REV.integrations.t.sol +0 -573
  31. package/test/REVAutoIssuanceFuzz.t.sol +0 -328
  32. package/test/REVDeployerRegressions.t.sol +0 -396
  33. package/test/REVInvincibility.t.sol +0 -1371
  34. package/test/REVInvincibilityHandler.sol +0 -387
  35. package/test/REVLifecycle.t.sol +0 -420
  36. package/test/REVLoans.invariants.t.sol +0 -724
  37. package/test/REVLoansAttacks.t.sol +0 -816
  38. package/test/REVLoansFeeRecovery.t.sol +0 -783
  39. package/test/REVLoansFindings.t.sol +0 -711
  40. package/test/REVLoansRegressions.t.sol +0 -364
  41. package/test/REVLoansSourceFeeRecovery.t.sol +0 -517
  42. package/test/REVLoansSourced.t.sol +0 -1839
  43. package/test/REVLoansUnSourced.t.sol +0 -409
  44. package/test/TestAuditFixVerification.t.sol +0 -675
  45. package/test/TestBurnHeldTokens.t.sol +0 -394
  46. package/test/TestCEIPattern.t.sol +0 -508
  47. package/test/TestCashOutCallerValidation.t.sol +0 -452
  48. package/test/TestConversionDocumentation.t.sol +0 -365
  49. package/test/TestCrossCurrencyReclaim.t.sol +0 -610
  50. package/test/TestCrossSourceReallocation.t.sol +0 -361
  51. package/test/TestERC2771MetaTx.t.sol +0 -585
  52. package/test/TestEmptyBuybackSpecs.t.sol +0 -300
  53. package/test/TestFlashLoanSurplus.t.sol +0 -365
  54. package/test/TestHiddenTokens.t.sol +0 -474
  55. package/test/TestHookArrayOOB.t.sol +0 -278
  56. package/test/TestLiquidationBehavior.t.sol +0 -398
  57. package/test/TestLoanSourceRotation.t.sol +0 -553
  58. package/test/TestLoansCashOutDelay.t.sol +0 -493
  59. package/test/TestLongTailEconomics.t.sol +0 -677
  60. package/test/TestLowFindings.t.sol +0 -677
  61. package/test/TestMixedFixes.t.sol +0 -593
  62. package/test/TestPermit2Signatures.t.sol +0 -683
  63. package/test/TestReallocationSandwich.t.sol +0 -412
  64. package/test/TestRevnetRegressions.t.sol +0 -350
  65. package/test/TestSplitWeightAdjustment.t.sol +0 -527
  66. package/test/TestSplitWeightE2E.t.sol +0 -605
  67. package/test/TestSplitWeightFork.t.sol +0 -855
  68. package/test/TestStageTransitionBorrowable.t.sol +0 -301
  69. package/test/TestSwapTerminalPermission.t.sol +0 -262
  70. package/test/TestTerminalEncodingInHash.t.sol +0 -326
  71. package/test/TestUint112Overflow.t.sol +0 -311
  72. package/test/TestZeroAmountLoanGuard.t.sol +0 -378
  73. package/test/TestZeroRepayment.t.sol +0 -354
  74. package/test/audit/CrossChainBuybackRouteMismatch.t.sol +0 -184
  75. package/test/audit/HiddenSupplyCashout.t.sol +0 -61
  76. package/test/audit/LoanIdOverflowGuard.t.sol +0 -523
  77. package/test/audit/NemesisVerification.t.sol +0 -97
  78. package/test/audit/OperatorDelegation.t.sol +0 -356
  79. package/test/audit/PhantomSurplusTerminal.t.sol +0 -367
  80. package/test/audit/REVOwnerCurrencyMismatch.t.sol +0 -188
  81. package/test/audit/REVOwnerRemoteSurplusCurrencyMismatch.t.sol +0 -140
  82. package/test/audit/ReallocatePermission.t.sol +0 -363
  83. package/test/audit/RemoteLoanAccountingGap.t.sol +0 -74
  84. package/test/audit/SupportsInterfaceTest.t.sol +0 -51
  85. package/test/audit/TestFeeAllowanceLeak.t.sol +0 -197
  86. package/test/audit/TestLoansAndDeployerFixes.t.sol +0 -576
  87. package/test/fork/ForkTestBase.sol +0 -727
  88. package/test/fork/TestAutoIssuanceFork.t.sol +0 -148
  89. package/test/fork/TestCashOutFork.t.sol +0 -253
  90. package/test/fork/TestIssuanceDecayFork.t.sol +0 -158
  91. package/test/fork/TestLoanAdversarialFork.t.sol +0 -744
  92. package/test/fork/TestLoanBorrowFork.t.sol +0 -163
  93. package/test/fork/TestLoanCrossRulesetFork.t.sol +0 -308
  94. package/test/fork/TestLoanERC20Fork.t.sol +0 -459
  95. package/test/fork/TestLoanLiquidationFork.t.sol +0 -135
  96. package/test/fork/TestLoanReallocateFork.t.sol +0 -113
  97. package/test/fork/TestLoanRepayFork.t.sol +0 -188
  98. package/test/fork/TestLoanTransferFork.t.sol +0 -143
  99. package/test/fork/TestPermit2PaymentFork.t.sol +0 -300
  100. package/test/fork/TestSplitWeightFork.t.sol +0 -189
  101. package/test/helpers/MaliciousContracts.sol +0 -247
  102. package/test/helpers/REVEmpty721Config.sol +0 -45
  103. package/test/mock/MockBuybackCashOutRecorder.sol +0 -84
  104. package/test/mock/MockBuybackDataHook.sol +0 -112
  105. package/test/mock/MockBuybackDataHookMintPath.sol +0 -68
  106. package/test/mock/MockSuckerRegistry.sol +0 -17
  107. package/test/regression/TestBurnPermissionRequired.t.sol +0 -294
  108. package/test/regression/TestCashOutBuybackFeeLeak.t.sol +0 -232
  109. package/test/regression/TestCrossRevnetLiquidation.t.sol +0 -255
  110. package/test/regression/TestCumulativeLoanCounter.t.sol +0 -361
  111. package/test/regression/TestLiquidateGapHandling.t.sol +0 -394
  112. 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
- }