@bananapus/router-terminal-v6 0.0.42 → 0.0.43
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/foundry.toml +1 -0
- package/package.json +1 -1
- package/src/JBRouterTerminalRegistry.sol +47 -19
package/foundry.toml
CHANGED
package/package.json
CHANGED
|
@@ -26,6 +26,8 @@ import {IJBForwardingTerminal} from "./interfaces/IJBForwardingTerminal.sol";
|
|
|
26
26
|
import {IJBPayerTracker} from "./interfaces/IJBPayerTracker.sol";
|
|
27
27
|
import {IJBRouterTerminalRegistry} from "./interfaces/IJBRouterTerminalRegistry.sol";
|
|
28
28
|
|
|
29
|
+
import {JBForwardingCheck} from "./libraries/JBForwardingCheck.sol";
|
|
30
|
+
|
|
29
31
|
import {DefaultTerminalSegment} from "./structs/DefaultTerminalSegment.sol";
|
|
30
32
|
|
|
31
33
|
/// @notice A forwarding layer that lets each project choose which router terminal receives its payments, with an
|
|
@@ -306,26 +308,45 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
|
|
|
306
308
|
return ERC2771Context._msgSender();
|
|
307
309
|
}
|
|
308
310
|
|
|
309
|
-
/// @notice
|
|
311
|
+
/// @notice Returns the original payer to record in transient storage. If `_msgSender()` is a
|
|
312
|
+
/// contract that exposes `IJBPayerTracker.originalPayer()` and that getter returns a non-zero
|
|
313
|
+
/// value, the upstream payer is propagated so a forwarding chain (project payer -> registry
|
|
314
|
+
/// -> router) refunds the true originator instead of the intermediary. Otherwise the direct
|
|
315
|
+
/// caller is recorded — the common direct-call case.
|
|
316
|
+
/// @return originalPayerOrSender The upstream payer if the resolved sender is a forwarding
|
|
317
|
+
/// tracker that returns a non-zero address; otherwise the resolved sender itself.
|
|
318
|
+
function _originalPayerOrSender() internal view returns (address originalPayerOrSender) {
|
|
319
|
+
// Resolve through ERC-2771 so a trusted meta-tx forwarder is transparently unwrapped.
|
|
320
|
+
address sender = _msgSender();
|
|
321
|
+
|
|
322
|
+
// EOAs and contracts without code can't implement IJBPayerTracker — record them directly.
|
|
323
|
+
if (sender.code.length == 0) return sender;
|
|
324
|
+
|
|
325
|
+
// Probe the caller for IJBPayerTracker.originalPayer() via staticcall so a reverting or
|
|
326
|
+
// non-conformant caller does not bubble up — fall back to the resolved sender on failure.
|
|
327
|
+
(bool ok, bytes memory data) = sender.staticcall(abi.encodeWithSelector(IJBPayerTracker.originalPayer.selector));
|
|
328
|
+
|
|
329
|
+
// Caller doesn't implement the interface (revert) or returned a truncated payload — treat
|
|
330
|
+
// it as a direct payment from the caller itself.
|
|
331
|
+
if (!ok || data.length < 32) return sender;
|
|
332
|
+
|
|
333
|
+
// Decode the upstream payer the caller advertised. A zero value means "no upstream tracked",
|
|
334
|
+
// which only happens when the caller is itself receiving a direct call — record the caller.
|
|
335
|
+
address upstream = abi.decode(data, (address));
|
|
336
|
+
return upstream == address(0) ? sender : upstream;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/// @notice Reject terminal choices that would forward the project back into this registry,
|
|
340
|
+
/// directly or transitively. Walks up to the depth limit `JBForwardingCheck` enforces so a
|
|
341
|
+
/// chain registry -> A -> B -> registry is caught (the previous one-hop probe missed those
|
|
342
|
+
/// transitive cycles, letting a project lock itself into a route that always loops).
|
|
310
343
|
/// @param projectId The project to validate forwarding for.
|
|
311
344
|
/// @param terminal The terminal to validate.
|
|
312
345
|
function _requireNonCircularTerminalFor(uint256 projectId, IJBTerminal terminal) internal view {
|
|
313
346
|
// Reject direct self-selection so the registry cannot forward a project to itself.
|
|
314
347
|
if (address(terminal) == address(this)) revert JBRouterTerminalRegistry_CircularForward(terminal);
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (address(terminal).code.length == 0) return;
|
|
318
|
-
|
|
319
|
-
// If the candidate is another forwarding terminal, ask where this project would end up.
|
|
320
|
-
try IJBForwardingTerminal(address(terminal)).terminalOf({projectId: projectId}) returns (
|
|
321
|
-
IJBTerminal downstreamTerminal
|
|
322
|
-
) {
|
|
323
|
-
// Reject one-hop forwarding cycles that bounce this project back into the registry.
|
|
324
|
-
if (address(downstreamTerminal) == address(this)) {
|
|
325
|
-
revert JBRouterTerminalRegistry_CircularForward({terminal: terminal});
|
|
326
|
-
}
|
|
327
|
-
} catch {
|
|
328
|
-
// Non-forwarding terminals are valid choices; failed interface probes should not block them.
|
|
348
|
+
if (JBForwardingCheck.isCircularTerminal({target: address(this), projectId: projectId, terminal: terminal})) {
|
|
349
|
+
revert JBRouterTerminalRegistry_CircularForward({terminal: terminal});
|
|
329
350
|
}
|
|
330
351
|
}
|
|
331
352
|
|
|
@@ -409,8 +430,9 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
|
|
|
409
430
|
address previousPayer = originalPayer;
|
|
410
431
|
|
|
411
432
|
// Store the original payer in transient storage so downstream router terminals can refund partial-fill
|
|
412
|
-
// leftovers to the true payer.
|
|
413
|
-
|
|
433
|
+
// leftovers to the true payer. If the immediate caller is itself a forwarding intermediary that exposes
|
|
434
|
+
// its own original payer, propagate that — otherwise refunds in nested forwards stop at the intermediary.
|
|
435
|
+
originalPayer = _originalPayerOrSender();
|
|
414
436
|
|
|
415
437
|
// Reject forwards that would bounce straight back into this call's immediate caller.
|
|
416
438
|
_enforceNoCircularForward(terminal);
|
|
@@ -559,8 +581,9 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
|
|
|
559
581
|
address previousPayer = originalPayer;
|
|
560
582
|
|
|
561
583
|
// Store the original payer in transient storage so downstream router terminals can refund partial-fill
|
|
562
|
-
// leftovers to the true payer.
|
|
563
|
-
|
|
584
|
+
// leftovers to the true payer. If the immediate caller is itself a forwarding intermediary that exposes
|
|
585
|
+
// its own original payer, propagate that — otherwise refunds in nested forwards stop at the intermediary.
|
|
586
|
+
originalPayer = _originalPayerOrSender();
|
|
564
587
|
|
|
565
588
|
// Reject forwards that would bounce straight back into this call's immediate caller.
|
|
566
589
|
_enforceNoCircularForward(terminal);
|
|
@@ -598,6 +621,11 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
|
|
|
598
621
|
|
|
599
622
|
uint256 count = PROJECTS.count();
|
|
600
623
|
|
|
624
|
+
// Reject defaults that would route any current project back into the registry through a
|
|
625
|
+
// transitive forwarding chain. Probed at the current project-count snapshot since the
|
|
626
|
+
// default is what unconfigured (and all-future) projects will resolve to.
|
|
627
|
+
_requireNonCircularTerminalFor({projectId: count, terminal: terminal});
|
|
628
|
+
|
|
601
629
|
// Snapshot the OUTGOING default so existing projects (ID <= count) continue resolving to
|
|
602
630
|
// it, not to the new default. First-ever call has defaultTerminal == 0 and nothing to snapshot.
|
|
603
631
|
if (address(defaultTerminal) != address(0)) {
|