Skip to content

Commit 3d85405

Browse files
authored
Merge pull request #639 from superform-xyz/tamara-sup-8873-guard-against-malicious-processors
fix: Guard against malicious processors [SUP-8873]
2 parents 0a7a58b + 48e3015 commit 3d85405

File tree

7 files changed

+120
-108
lines changed

7 files changed

+120
-108
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ build-sizes: ## Builds the project and shows sizes
121121

122122
.PHONY: test-vvv
123123
test-vvv: ## Runs tests with verbose output
124-
forge test --match-contract SDMVW0TokenInputNoSlippageAMB1323 --evm-version cancun -vvv
124+
forge test --match-test test_crossChainRebalance_updateSuperformData_allErrors --evm-version cancun -vvv
125125

126126
.PHONY: ftest
127127
ftest: ## Runs tests with cancun evm version

src/interfaces/ISuperformRouterPlusAsync.sol

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ interface ISuperformRouterPlusAsync is IBaseSuperformRouterPlus {
1818
/// @notice thrown if the caller is not router plus
1919
error NOT_ROUTER_PLUS();
2020

21+
/// @notice thrown if the caller is not core state registry rescuer
22+
error NOT_CORE_STATE_REGISTRY_RESCUER();
23+
2124
/// @notice thrown if the rebalance to update is invalid
2225
error COMPLETE_REBALANCE_INVALID_TX_DATA_UPDATE();
2326

@@ -44,17 +47,17 @@ interface ISuperformRouterPlusAsync is IBaseSuperformRouterPlus {
4447
/// @notice thrown to avoid processing the same rebalance payload twice
4548
error REBALANCE_ALREADY_PROCESSED();
4649

47-
/// @notice thrown when the refund proposer is invalid
48-
error INVALID_PROPOSER();
50+
/// @notice thrown when the refund requester is not the payload receiver
51+
error INVALID_REQUESTER();
4952

5053
/// @notice thrown when the refund payload is invalid
5154
error INVALID_REFUND_DATA();
5255

53-
/// @notice thrown when refund is already proposed
54-
error REFUND_ALREADY_PROPOSED();
56+
/// @notice thrown when requested refund amount is too high
57+
error REQUESTED_AMOUNT_TOO_HIGH();
5558

56-
/// @notice thrown if the refund is still in dispute phase
57-
error IN_DISPUTE_PHASE();
59+
/// @notice thrown when the refund payload is already approved
60+
error REFUND_ALREADY_APPROVED();
5861

5962
//////////////////////////////////////////////////////////////
6063
// EVENTS //
@@ -74,6 +77,15 @@ interface ISuperformRouterPlusAsync is IBaseSuperformRouterPlus {
7477
uint256 indexed routerPlusPayloadId, address indexed refundReceiver, address refundToken, uint256 refundAmount
7578
);
7679

80+
/// @notice emitted when a refund is proposed
81+
/// @param routerPlusPayloadId is the unique identifier for the payload
82+
/// @param refundReceiver is the address of the user who'll receiver the refund
83+
/// @param refundToken is the token to be refunded
84+
/// @param refundAmount is the new refund amount
85+
event refundRequested(
86+
uint256 indexed routerPlusPayloadId, address indexed refundReceiver, address refundToken, uint256 refundAmount
87+
);
88+
7789
/// @notice emitted when an existing refund got disputed
7890
/// @param routerPlusPayloadId is the unique identifier for the payload
7991
/// @param disputer is the address of the user who disputed the refund
@@ -97,7 +109,6 @@ interface ISuperformRouterPlusAsync is IBaseSuperformRouterPlus {
97109
address receiver;
98110
address interimToken;
99111
uint256 amount;
100-
uint256 proposedTime;
101112
}
102113

103114
struct DecodedRouterPlusRebalanceCallData {
@@ -168,16 +179,12 @@ interface ISuperformRouterPlusAsync is IBaseSuperformRouterPlus {
168179
payable
169180
returns (bool rebalanceSuccessful);
170181

171-
/// @notice allows the receiver / disputer to protect against malicious processors
172-
/// @param finalPayloadId_ is the unique identifier of the refund
173-
function disputeRefund(uint256 finalPayloadId_) external;
174-
175-
/// @notice allows the rescuer to propose a new refund amount after a successful dispute
176-
/// @param finalPayloadId_ is the unique identifier of the refund
177-
/// @param refundAmount_ is the new refund amount proposed
178-
function proposeRefund(uint256 finalPayloadId_, uint256 refundAmount_) external;
182+
/// @notice allows the user to request a refund for the rebalance
183+
/// @param routerplusPayloadId_ the router plus payload id
184+
function requestRefund(uint256 routerplusPayloadId_, uint256 requestedAmount) external;
179185

180-
/// @notice allows the user to claim their refund post the dispute period
181-
/// @param finalPayloadId_ is the unique identifier of the refund
182-
function finalizeRefund(uint256 finalPayloadId_) external;
183-
}
186+
/// @dev only callable by core state registry rescuer
187+
/// @notice approves a refund for the rebalance and sends funds to the receiver
188+
/// @param routerplusPayloadId_ the router plus payload id
189+
function approveRefund(uint256 routerplusPayloadId_) external;
190+
}

src/router-plus/SuperformRouterPlusAsync.sol

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ contract SuperformRouterPlusAsync is ISuperformRouterPlusAsync, BaseSuperformRou
3434

3535
mapping(uint256 routerPlusPayloadId => Refund) public refunds;
3636
mapping(uint256 routerPlusPayloadId => bool processed) public processedRebalancePayload;
37+
38+
mapping(uint256 routerPlusPayloadId => bool approvedRefund) public approvedRefund;
39+
3740
//////////////////////////////////////////////////////////////
3841
// MODIFIERS //
3942
//////////////////////////////////////////////////////////////
@@ -52,6 +55,13 @@ contract SuperformRouterPlusAsync is ISuperformRouterPlusAsync, BaseSuperformRou
5255
_;
5356
}
5457

58+
modifier onlyCoreStateRegistryRescuer() {
59+
if (!_hasRole(keccak256("CORE_STATE_REGISTRY_RESCUER_ROLE"), msg.sender)) {
60+
revert NOT_CORE_STATE_REGISTRY_RESCUER();
61+
}
62+
_;
63+
}
64+
5565
//////////////////////////////////////////////////////////////
5666
// CONSTRUCTOR //
5767
//////////////////////////////////////////////////////////////
@@ -252,8 +262,8 @@ contract SuperformRouterPlusAsync is ISuperformRouterPlusAsync, BaseSuperformRou
252262
ENTIRE_SLIPPAGE * args_.amountReceivedInterimAsset
253263
< ((data.expectedAmountInterimAsset * (ENTIRE_SLIPPAGE - data.slippage)))
254264
) {
255-
refunds[args_.routerPlusPayloadId] =
256-
Refund(args_.receiverAddressSP, data.interimAsset, args_.amountReceivedInterimAsset, block.timestamp);
265+
266+
refunds[args_.routerPlusPayloadId] = Refund(args_.receiverAddressSP, data.interimAsset, 0);
257267

258268
emit RefundInitiated(
259269
args_.routerPlusPayloadId, args_.receiverAddressSP, data.interimAsset, args_.amountReceivedInterimAsset
@@ -416,42 +426,30 @@ contract SuperformRouterPlusAsync is ISuperformRouterPlusAsync, BaseSuperformRou
416426
}
417427

418428
/// @inheritdoc ISuperformRouterPlusAsync
419-
function disputeRefund(uint256 routerPlusPayloadId_) external override {
420-
Refund storage r = refunds[routerPlusPayloadId_];
421-
422-
if (!(msg.sender == r.receiver || _hasRole(keccak256("CORE_STATE_REGISTRY_DISPUTER_ROLE"), msg.sender))) {
423-
revert Error.NOT_VALID_DISPUTER();
424-
}
425-
426-
if (r.proposedTime == 0 || block.timestamp > r.proposedTime + _getDelay()) revert Error.DISPUTE_TIME_ELAPSED();
427-
428-
/// @dev just can reset the last proposed time, since amounts should be updated again to
429-
/// pass the proposedTime zero check in finalize
430-
r.proposedTime = 0;
431-
432-
emit RefundDisputed(routerPlusPayloadId_, msg.sender);
433-
}
429+
function requestRefund(uint256 routerPlusPayloadId_, uint256 requestedAmount) external {
430+
Refund memory r = refunds[routerPlusPayloadId_];
434431

435-
/// @inheritdoc ISuperformRouterPlusAsync
436-
function proposeRefund(uint256 routerPlusPayloadId_, uint256 refundAmount_) external {
437-
if (!_hasRole(keccak256("CORE_STATE_REGISTRY_RESCUER_ROLE"), msg.sender)) revert INVALID_PROPOSER();
432+
if (msg.sender != r.receiver) revert INVALID_REQUESTER();
433+
if (r.interimToken == address(0)) revert INVALID_REFUND_DATA();
438434

439-
Refund storage r = refunds[routerPlusPayloadId_];
435+
XChainRebalanceData memory data = xChainRebalanceCallData[r.receiver][routerPlusPayloadId_];
440436

441-
if (r.interimToken == address(0) || r.receiver == address(0)) revert INVALID_REFUND_DATA();
442-
if (r.proposedTime != 0) revert REFUND_ALREADY_PROPOSED();
437+
if (requestedAmount > data.expectedAmountInterimAsset) {
438+
revert REQUESTED_AMOUNT_TOO_HIGH();
439+
}
443440

444-
r.proposedTime = block.timestamp;
445-
r.amount = refundAmount_;
441+
refunds[routerPlusPayloadId_] = Refund(msg.sender, data.interimAsset, requestedAmount);
446442

447-
emit NewRefundAmountProposed(routerPlusPayloadId_, refundAmount_);
443+
emit refundRequested(routerPlusPayloadId_, msg.sender, r.interimToken, requestedAmount);
448444
}
449445

450446
/// @inheritdoc ISuperformRouterPlusAsync
451-
function finalizeRefund(uint256 routerPlusPayloadId_) external {
447+
function approveRefund(uint256 routerPlusPayloadId_) external onlyCoreStateRegistryRescuer {
448+
if (approvedRefund[routerPlusPayloadId_]) revert REFUND_ALREADY_APPROVED();
449+
452450
Refund memory r = refunds[routerPlusPayloadId_];
453451

454-
if (r.proposedTime == 0 || block.timestamp <= r.proposedTime + _getDelay()) revert IN_DISPUTE_PHASE();
452+
approvedRefund[routerPlusPayloadId_] = true;
455453

456454
/// @dev deleting to prevent re-entrancy
457455
delete refunds[routerPlusPayloadId_];
@@ -465,15 +463,6 @@ contract SuperformRouterPlusAsync is ISuperformRouterPlusAsync, BaseSuperformRou
465463
// INTERNAL FUNCTIONS //
466464
//////////////////////////////////////////////////////////////
467465

468-
/// @dev returns the current dispute delay
469-
function _getDelay() internal view returns (uint256) {
470-
uint256 delay = superRegistry.delay();
471-
if (delay == 0) {
472-
revert Error.DELAY_NOT_SET();
473-
}
474-
return delay;
475-
}
476-
477466
function _updateSuperformData(
478467
MultiVaultSFData memory sfData,
479468
LiqRequest[] memory liqRequests,

test/fuzz/crosschain-data/adapters/HyperlaneImplementation.t.sol

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,21 +107,23 @@ contract HyperlaneImplementationTest is CommonProtocolActions {
107107
vm.startPrank(deployer);
108108
uint256 userIndex = userSeed_ % users.length;
109109

110+
111+
vm.assume(malice_ != getContract(ETH, "CoreStateRegistry"));
112+
vm.assume(malice_ != getContract(ETH, "TimelockStateRegistry"));
113+
vm.assume(malice_ != getContract(ETH, "BroadcastRegistry"));
114+
vm.assume(malice_ != getContract(ETH, "AsyncStateRegistry"));
115+
110116
AMBMessage memory ambMessage;
111117
BroadCastAMBExtraData memory ambExtraData;
112118
address coreStateRegistry;
113119

114120
(ambMessage, ambExtraData, coreStateRegistry) =
115121
setupBroadcastPayloadAMBData(users[userIndex], address(hyperlaneImplementation));
116-
117-
vm.expectRevert(Error.NOT_STATE_REGISTRY.selector);
118-
vm.assume(malice_ != getContract(ETH, "CoreStateRegistry"));
119-
vm.assume(malice_ != getContract(ETH, "TimelockStateRegistry"));
120-
vm.assume(malice_ != getContract(ETH, "BroadcastRegistry"));
121-
vm.assume(malice_ != getContract(ETH, "AsyncStateRegistry"));
122+
vm.stopPrank();
122123

123124
vm.deal(malice_, 100 ether);
124125
vm.prank(malice_);
126+
vm.expectRevert(Error.NOT_STATE_REGISTRY.selector);
125127
hyperlaneImplementation.dispatchPayload{ value: 0.1 ether }(
126128
users[userIndex], chainIds[5], abi.encode(ambMessage), abi.encode(ambExtraData)
127129
);

test/mainnet/SmokeTest.Staging.t.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -555,14 +555,13 @@ contract SmokeTestStaging is MainnetBaseSetup {
555555

556556
for (uint256 i; i < TARGET_DEPLOYMENT_CHAINS.length; ++i) {
557557
uint64 chainId = TARGET_DEPLOYMENT_CHAINS[i];
558-
559558
vm.selectFork(FORKS[chainId]);
560559
superFactory = SuperformFactory(getContract(chainId, "SuperformFactory"));
561560

562561
chainId != BLAST
563562
? assertEq(superFactory.getFormImplementation(5), getContract(chainId, "ERC5115Form"))
564563
: assertEq(superFactory.getFormImplementation(205), getContract(chainId, "ERC5115Form"));
565-
/// @dev in blast there are 6 forms (2 without operator, 2 with wrong state registry and 2 right superforms)
564+
566565
assertEq(superFactory.getFormCount(), chainId == LINEA ? 5 : chainId == BLAST ? 8 : 7);
567566
assertEq(superFactory.getFormStateRegistryId(5), 1);
568567
}

test/unit/payments/PaymentHelper.t.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,6 @@ contract PaymentHelperTest is ProtocolActions {
569569

570570
assertEq(fees3, 0);
571571
}
572-
573572

574573
function test_estimateSingleDirectMultiVault() public view {
575574
/// @dev scenario: single vault withdrawal

test/unit/router-plus/SuperformRouterPlus.t.sol

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1829,56 +1829,65 @@ contract SuperformRouterPlusTest is ProtocolActions {
18291829
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).completeCrossChainRebalance{ value: 1 ether }(completeArgs);
18301830
vm.stopPrank();
18311831

1832-
vm.expectRevert(Error.NOT_VALID_DISPUTER.selector);
1833-
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).disputeRefund(1);
1832+
// Step 5: Request refund
18341833

1835-
vm.startPrank(deployer);
1836-
vm.mockCall(
1837-
address(getContract(SOURCE_CHAIN, "SuperRegistry")),
1838-
abi.encodeWithSelector(ISuperRegistry.delay.selector),
1839-
abi.encode(0)
1840-
);
1841-
vm.expectRevert(Error.DELAY_NOT_SET.selector);
1842-
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).disputeRefund(1);
1843-
vm.clearMockedCalls();
1844-
1845-
vm.warp(block.timestamp + 100 days);
1846-
vm.expectRevert(Error.DISPUTE_TIME_ELAPSED.selector);
1847-
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).disputeRefund(1);
1848-
1849-
vm.warp(block.timestamp - 100 days);
1850-
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).disputeRefund(1);
1834+
/// @dev testing invalid requester (not receiver)
1835+
vm.startPrank(address(222));
1836+
vm.expectRevert(ISuperformRouterPlusAsync.INVALID_REQUESTER.selector);
1837+
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).requestRefund(1, 100);
1838+
vm.stopPrank();
18511839

1852-
vm.expectRevert(Error.DISPUTE_TIME_ELAPSED.selector);
1853-
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).disputeRefund(1);
1840+
// @dev testing refund amount exceeds expected amount
1841+
vm.startPrank(deployer);
1842+
vm.expectRevert(ISuperformRouterPlusAsync.REQUESTED_AMOUNT_TOO_HIGH.selector);
1843+
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).requestRefund(1, 1000e18);
18541844
vm.stopPrank();
18551845

1856-
vm.expectRevert(ISuperformRouterPlusAsync.INVALID_PROPOSER.selector);
1857-
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).proposeRefund(1, completeArgs.amountReceivedInterimAsset);
1846+
/// @dev testing valid refund request
1847+
vm.prank(deployer);
1848+
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).requestRefund(1, 100);
18581849

1859-
vm.startPrank(deployer);
1860-
vm.expectRevert(ISuperformRouterPlusAsync.INVALID_REFUND_DATA.selector);
1861-
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).proposeRefund(2, completeArgs.amountReceivedInterimAsset);
1850+
(,, uint256 requestedAmount) = SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).refunds(1);
1851+
assertEq(requestedAmount, 100);
18621852

1863-
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).proposeRefund(1, completeArgs.amountReceivedInterimAsset);
1853+
(, address refundToken,) = SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).refunds(1);
1854+
assertEq(refundToken, address(args.interimAsset));
18641855

1865-
vm.expectRevert(ISuperformRouterPlusAsync.REFUND_ALREADY_PROPOSED.selector);
1866-
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).proposeRefund(1, completeArgs.amountReceivedInterimAsset);
1856+
// Step 6: Approve refund
18671857

1868-
vm.expectRevert(ISuperformRouterPlusAsync.IN_DISPUTE_PHASE.selector);
1869-
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).finalizeRefund(1);
1858+
/// @dev testing invalid approver (not core state registry)
1859+
vm.startPrank(address(1234));
1860+
vm.expectRevert(ISuperformRouterPlusAsync.NOT_CORE_STATE_REGISTRY_RESCUER.selector);
1861+
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).approveRefund(1);
1862+
vm.stopPrank();
18701863

1871-
(, address refundToken,,) = SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).refunds(1);
1864+
/// @dev testing valid refund approval
18721865
uint256 balanceBefore = MockERC20(refundToken).balanceOf(deployer);
1866+
uint256 routerBalanceBefore = MockERC20(refundToken).balanceOf(address(ROUTER_PLUS_ASYNC_SOURCE));
1867+
vm.startPrank(deployer);
1868+
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).approveRefund(1);
1869+
vm.stopPrank();
18731870

1874-
vm.warp(block.timestamp + 100 days);
1875-
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).finalizeRefund(1);
18761871
uint256 balanceAfter = MockERC20(refundToken).balanceOf(deployer);
1877-
18781872
assertGt(balanceAfter, balanceBefore);
1873+
assertEq(MockERC20(refundToken).balanceOf(address(ROUTER_PLUS_ASYNC_SOURCE)), routerBalanceBefore - 100);
1874+
assertEq(MockERC20(refundToken).balanceOf(address(deployer)), balanceBefore + 100);
1875+
1876+
(, address interimToken,) = SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).refunds(1);
1877+
assertEq(interimToken, address(0));
1878+
1879+
(, address receiver,) = SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).refunds(1);
1880+
assertEq(receiver, address(0));
18791881

1880-
vm.expectRevert(ISuperformRouterPlusAsync.IN_DISPUTE_PHASE.selector);
1881-
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).finalizeRefund(1);
1882+
(,, uint256 updatedRequestedAmount) = SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).refunds(1);
1883+
assertEq(updatedRequestedAmount, 0);
1884+
vm.stopPrank();
1885+
1886+
/// @dev testing refund already approved
1887+
vm.startPrank(deployer);
1888+
vm.expectRevert(ISuperformRouterPlusAsync.REFUND_ALREADY_APPROVED.selector);
1889+
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).approveRefund(1);
1890+
vm.stopPrank();
18821891
}
18831892

18841893
function test_crossChainRebalance_negativeSlippage() public {
@@ -1935,6 +1944,7 @@ contract SuperformRouterPlusTest is ProtocolActions {
19351944

19361945
function test_crossChainRebalance_updateSuperformData_allErrors() public {
19371946
vm.selectFork(FORKS[SOURCE_CHAIN]);
1947+
19381948
SingleVaultSFData memory sfData = SingleVaultSFData({
19391949
superformId: superformId1,
19401950
amount: 1e18,
@@ -2042,14 +2052,19 @@ contract SuperformRouterPlusTest is ProtocolActions {
20422052

20432053
// Reset liqDstChainId
20442054
completeArgs.liqRequests[0][0].liqDstChainId = SOURCE_CHAIN;
2045-
2046-
/// @FIXME: This line is not reacheable
2047-
// vm.expectRevert(ISuperformRouterPlusAsync.COMPLETE_REBALANCE_DIFFERENT_RECEIVER.selector);
2048-
// completeArgs.receiverAddressSP = address(0x123);
2049-
// SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).completeCrossChainRebalance{ value: 1 ether
2050-
// }(completeArgs);
20512055
vm.stopPrank();
2056+
vm.startPrank(ROUTER_PLUS_SOURCE);
2057+
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).setXChainRebalanceCallData(address(0x123), 2, data);
2058+
vm.stopPrank();
2059+
vm.startPrank(deployer);
20522060

2061+
vm.expectRevert(ISuperformRouterPlusAsync.COMPLETE_REBALANCE_DIFFERENT_RECEIVER.selector);
2062+
completeArgs.routerPlusPayloadId = 2;
2063+
completeArgs.receiverAddressSP = address(0x123);
2064+
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).completeCrossChainRebalance{ value: 1 ether }(completeArgs);
2065+
vm.stopPrank();
2066+
completeArgs.routerPlusPayloadId = 1;
2067+
completeArgs.receiverAddressSP = deployer;
20532068
sfData.liqRequest.token = address(0);
20542069

20552070
data = IBaseSuperformRouterPlus.XChainRebalanceData({
@@ -2070,6 +2085,7 @@ contract SuperformRouterPlusTest is ProtocolActions {
20702085
completeArgs.routerPlusPayloadId = 2;
20712086
vm.expectRevert(ISuperformRouterPlusAsync.COMPLETE_REBALANCE_INVALID_TX_DATA_UPDATE.selector);
20722087
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).completeCrossChainRebalance{ value: 1 ether }(completeArgs);
2088+
vm.stopPrank();
20732089
}
20742090

20752091
//////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)