Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Guard against malicious processors [SUP-8873] #639

Merged
merged 54 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
5b5d429
fix: add RECEIVER_ADDRESS_EQUAL_TO_PROCESSOR_ADDRESS() error
TamaraRingas Oct 16, 2024
11cc0da
fix: move RECEIVER_ADDRESS_EQUAL_TO_PROCESSOR_ADDRESS() error to ISup…
TamaraRingas Oct 16, 2024
f380c7a
chore: rename IS_ROUTER_PLUS_PROCESSOR() error
TamaraRingas Oct 16, 2024
00151da
chore: rm unused error
TamaraRingas Oct 16, 2024
71d5769
add NOT_CORE_STATE_REGISTRY_RESCUER() error
TamaraRingas Oct 16, 2024
5f472a0
add REFUND_AMOUNT_EXCEEDS_RECEIVED_AMOUNT() error
TamaraRingas Oct 16, 2024
5fb5af1
chore: rename REFUND_AMOUNT_EXCEEDS_EXPECTED_AMOUNT() error
TamaraRingas Oct 16, 2024
c1c2f33
feat: add requestRefund() function
TamaraRingas Oct 16, 2024
4879f8e
fix: update requestRefund() params
TamaraRingas Oct 16, 2024
53ac164
chore: add onlyCoreStateRegistryRescuer() modifier
TamaraRingas Oct 16, 2024
a0a746e
feat: add approveRefund()
TamaraRingas Oct 16, 2024
a79275c
refactor: rm old refund functions
TamaraRingas Oct 16, 2024
9924cab
refactor: rm old refund functions
TamaraRingas Oct 16, 2024
e42992c
refactor: rm _getDelay()
TamaraRingas Oct 16, 2024
7f790d3
refactor: rm refund proposal time checks
TamaraRingas Oct 16, 2024
26d0e22
refactor: rm tests for old refund mechanism
TamaraRingas Oct 16, 2024
3506d86
chore: rm unused errors
TamaraRingas Oct 16, 2024
2ec1753
add REFUND_ALREADY_APPROVED() error
TamaraRingas Oct 16, 2024
b9cea45
fix: add checks for duplicate approvals
TamaraRingas Oct 16, 2024
a49417f
chore: add test case for duplicate approvals
TamaraRingas Oct 16, 2024
094e8d8
chore: formatting
TamaraRingas Oct 16, 2024
7ead146
style: formatting
TamaraRingas Oct 17, 2024
fcd9edf
Revert "chore: formatting"
TamaraRingas Oct 17, 2024
0efc2ba
Revert "style: formatting"
TamaraRingas Oct 17, 2024
fc6f31c
fix: formatting
0xTimepunk Oct 17, 2024
477fb32
fix: correct interface of async router plus
0xTimepunk Oct 17, 2024
6fda946
refactor: rm proposedTime from Refund struct
TamaraRingas Oct 17, 2024
c861efc
Merge pull request #640 from superform-xyz/joao-fix-formatting-8873
TamaraRingas Oct 17, 2024
4e9e104
fix: delegate prank
0xTimepunk Oct 17, 2024
aa52182
fix: remainder of files
0xTimepunk Oct 17, 2024
b6ce0b7
test: fix staging test
0xTimepunk Oct 17, 2024
81e9c40
Merge remote-tracking branch 'origin/v1.5' into tamara-sup-8873-guard…
0xTimepunk Oct 17, 2024
19878b6
refactor: rename RefundRequested event
TamaraRingas Oct 18, 2024
b89ec5a
refactor: rename RefundRequested event
TamaraRingas Oct 18, 2024
d6541b0
fix: typo
TamaraRingas Oct 18, 2024
62dd458
fix: update tests
TamaraRingas Oct 21, 2024
8d38ebd
refactor: rm amount from requestRefund()
TamaraRingas Oct 21, 2024
b85030d
refactor: rm amount from requestRefund()
TamaraRingas Oct 21, 2024
1779144
Merge branch 'tamara-sup-8873-guard-against-malicious-processors' of …
TamaraRingas Oct 23, 2024
186182d
fix: reorder test logic
TamaraRingas Oct 24, 2024
af8d01b
add check for expectedAmountInterimAsset == 0 in completeCrossCHainRe…
TamaraRingas Oct 24, 2024
a548dba
fix: simplify request amount to refund
0xTimepunk Oct 24, 2024
5c246e4
refactor: update refund mechanism
TamaraRingas Oct 24, 2024
3684d11
test requested refund amount > expected
TamaraRingas Oct 24, 2024
1d1e450
fix: update requested amount too high test
TamaraRingas Oct 24, 2024
c6191c5
chore: update refund tests
TamaraRingas Oct 24, 2024
ff9a43a
fix: update amount setter in requestRefund()
TamaraRingas Oct 24, 2024
7464511
chore: add extra tests for requestRefund()
TamaraRingas Oct 24, 2024
2375ce7
fix: update invalid approver error selector
TamaraRingas Oct 24, 2024
7d73256
chore: rm unused variable
TamaraRingas Oct 24, 2024
70d8021
fix: update test-vvv
TamaraRingas Oct 24, 2024
60dc92d
fix: update test_crossChainRebalance_updateSuperformData_DifferentRec…
TamaraRingas Oct 24, 2024
bde75d9
wip: update test_crossChainRebalance_updateSuperformData_DifferentRec…
TamaraRingas Oct 25, 2024
48e3015
test: fix
0xTimepunk Oct 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ build-sizes: ## Builds the project and shows sizes

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

.PHONY: ftest
ftest: ## Runs tests with cancun evm version
Expand Down
45 changes: 26 additions & 19 deletions src/interfaces/ISuperformRouterPlusAsync.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ interface ISuperformRouterPlusAsync is IBaseSuperformRouterPlus {
/// @notice thrown if the caller is not router plus
error NOT_ROUTER_PLUS();

/// @notice thrown if the caller is not core state registry rescuer
error NOT_CORE_STATE_REGISTRY_RESCUER();

/// @notice thrown if the rebalance to update is invalid
error COMPLETE_REBALANCE_INVALID_TX_DATA_UPDATE();

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

/// @notice thrown when the refund proposer is invalid
error INVALID_PROPOSER();
/// @notice thrown when the refund requester is not the payload receiver
error INVALID_REQUESTER();

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

/// @notice thrown when refund is already proposed
error REFUND_ALREADY_PROPOSED();
/// @notice thrown when requested refund amount is too high
error REQUESTED_AMOUNT_TOO_HIGH();

/// @notice thrown if the refund is still in dispute phase
error IN_DISPUTE_PHASE();
/// @notice thrown when the refund payload is already approved
error REFUND_ALREADY_APPROVED();

//////////////////////////////////////////////////////////////
// EVENTS //
Expand All @@ -74,6 +77,15 @@ interface ISuperformRouterPlusAsync is IBaseSuperformRouterPlus {
uint256 indexed routerPlusPayloadId, address indexed refundReceiver, address refundToken, uint256 refundAmount
);

/// @notice emitted when a refund is proposed
/// @param routerPlusPayloadId is the unique identifier for the payload
/// @param refundReceiver is the address of the user who'll receiver the refund
/// @param refundToken is the token to be refunded
/// @param refundAmount is the new refund amount
event refundRequested(
uint256 indexed routerPlusPayloadId, address indexed refundReceiver, address refundToken, uint256 refundAmount
);

/// @notice emitted when an existing refund got disputed
/// @param routerPlusPayloadId is the unique identifier for the payload
/// @param disputer is the address of the user who disputed the refund
Expand All @@ -97,7 +109,6 @@ interface ISuperformRouterPlusAsync is IBaseSuperformRouterPlus {
address receiver;
address interimToken;
uint256 amount;
uint256 proposedTime;
}

struct DecodedRouterPlusRebalanceCallData {
Expand Down Expand Up @@ -168,16 +179,12 @@ interface ISuperformRouterPlusAsync is IBaseSuperformRouterPlus {
payable
returns (bool rebalanceSuccessful);

/// @notice allows the receiver / disputer to protect against malicious processors
/// @param finalPayloadId_ is the unique identifier of the refund
function disputeRefund(uint256 finalPayloadId_) external;

/// @notice allows the rescuer to propose a new refund amount after a successful dispute
/// @param finalPayloadId_ is the unique identifier of the refund
/// @param refundAmount_ is the new refund amount proposed
function proposeRefund(uint256 finalPayloadId_, uint256 refundAmount_) external;
/// @notice allows the user to request a refund for the rebalance
/// @param routerplusPayloadId_ the router plus payload id
function requestRefund(uint256 routerplusPayloadId_, uint256 requestedAmount) external;

/// @notice allows the user to claim their refund post the dispute period
/// @param finalPayloadId_ is the unique identifier of the refund
function finalizeRefund(uint256 finalPayloadId_) external;
}
/// @dev only callable by core state registry rescuer
/// @notice approves a refund for the rebalance and sends funds to the receiver
/// @param routerplusPayloadId_ the router plus payload id
function approveRefund(uint256 routerplusPayloadId_) external;
}
63 changes: 26 additions & 37 deletions src/router-plus/SuperformRouterPlusAsync.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ contract SuperformRouterPlusAsync is ISuperformRouterPlusAsync, BaseSuperformRou

mapping(uint256 routerPlusPayloadId => Refund) public refunds;
mapping(uint256 routerPlusPayloadId => bool processed) public processedRebalancePayload;

mapping(uint256 routerPlusPayloadId => bool approvedRefund) public approvedRefund;

//////////////////////////////////////////////////////////////
// MODIFIERS //
//////////////////////////////////////////////////////////////
Expand All @@ -53,6 +56,13 @@ contract SuperformRouterPlusAsync is ISuperformRouterPlusAsync, BaseSuperformRou
_;
}

modifier onlyCoreStateRegistryRescuer() {
if (!_hasRole(keccak256("CORE_STATE_REGISTRY_RESCUER_ROLE"), msg.sender)) {
revert NOT_CORE_STATE_REGISTRY_RESCUER();
}
_;
}

//////////////////////////////////////////////////////////////
// CONSTRUCTOR //
//////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -253,8 +263,8 @@ contract SuperformRouterPlusAsync is ISuperformRouterPlusAsync, BaseSuperformRou
ENTIRE_SLIPPAGE * args_.amountReceivedInterimAsset
< ((data.expectedAmountInterimAsset * (ENTIRE_SLIPPAGE - data.slippage)))
) {
refunds[args_.routerPlusPayloadId] =
Refund(args_.receiverAddressSP, data.interimAsset, args_.amountReceivedInterimAsset, block.timestamp);

refunds[args_.routerPlusPayloadId] = Refund(args_.receiverAddressSP, data.interimAsset, 0);
0xTimepunk marked this conversation as resolved.
Show resolved Hide resolved

emit RefundInitiated(
args_.routerPlusPayloadId, args_.receiverAddressSP, data.interimAsset, args_.amountReceivedInterimAsset
Expand Down Expand Up @@ -415,42 +425,30 @@ contract SuperformRouterPlusAsync is ISuperformRouterPlusAsync, BaseSuperformRou
}

/// @inheritdoc ISuperformRouterPlusAsync
function disputeRefund(uint256 routerPlusPayloadId_) external override {
Refund storage r = refunds[routerPlusPayloadId_];

if (!(msg.sender == r.receiver || _hasRole(keccak256("CORE_STATE_REGISTRY_DISPUTER_ROLE"), msg.sender))) {
revert Error.NOT_VALID_DISPUTER();
}

if (r.proposedTime == 0 || block.timestamp > r.proposedTime + _getDelay()) revert Error.DISPUTE_TIME_ELAPSED();

/// @dev just can reset the last proposed time, since amounts should be updated again to
/// pass the proposedTime zero check in finalize
r.proposedTime = 0;

emit RefundDisputed(routerPlusPayloadId_, msg.sender);
}
function requestRefund(uint256 routerPlusPayloadId_, uint256 requestedAmount) external {
Refund memory r = refunds[routerPlusPayloadId_];

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

Refund storage r = refunds[routerPlusPayloadId_];
XChainRebalanceData memory data = xChainRebalanceCallData[r.receiver][routerPlusPayloadId_];

if (r.interimToken == address(0) || r.receiver == address(0)) revert INVALID_REFUND_DATA();
if (r.proposedTime != 0) revert REFUND_ALREADY_PROPOSED();
if (requestedAmount > data.expectedAmountInterimAsset) {
revert REQUESTED_AMOUNT_TOO_HIGH();
}

r.proposedTime = block.timestamp;
r.amount = refundAmount_;
refunds[routerPlusPayloadId_] = Refund(msg.sender, data.interimAsset, requestedAmount);
0xTimepunk marked this conversation as resolved.
Show resolved Hide resolved

emit NewRefundAmountProposed(routerPlusPayloadId_, refundAmount_);
emit refundRequested(routerPlusPayloadId_, msg.sender, r.interimToken, requestedAmount);
}

/// @inheritdoc ISuperformRouterPlusAsync
function finalizeRefund(uint256 routerPlusPayloadId_) external {
function approveRefund(uint256 routerPlusPayloadId_) external onlyCoreStateRegistryRescuer {
if (approvedRefund[routerPlusPayloadId_]) revert REFUND_ALREADY_APPROVED();

Refund memory r = refunds[routerPlusPayloadId_];
TamaraRingas marked this conversation as resolved.
Show resolved Hide resolved

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

/// @dev deleting to prevent re-entrancy
delete refunds[routerPlusPayloadId_];
Expand All @@ -464,15 +462,6 @@ contract SuperformRouterPlusAsync is ISuperformRouterPlusAsync, BaseSuperformRou
// INTERNAL FUNCTIONS //
//////////////////////////////////////////////////////////////

/// @dev returns the current dispute delay
function _getDelay() internal view returns (uint256) {
uint256 delay = superRegistry.delay();
if (delay == 0) {
revert Error.DELAY_NOT_SET();
}
return delay;
}

function _updateSuperformData(
MultiVaultSFData memory sfData,
LiqRequest[] memory liqRequests,
Expand Down
14 changes: 8 additions & 6 deletions test/fuzz/crosschain-data/adapters/HyperlaneImplementation.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,23 @@ contract HyperlaneImplementationTest is CommonProtocolActions {
vm.startPrank(deployer);
uint256 userIndex = userSeed_ % users.length;


vm.assume(malice_ != getContract(ETH, "CoreStateRegistry"));
vm.assume(malice_ != getContract(ETH, "TimelockStateRegistry"));
vm.assume(malice_ != getContract(ETH, "BroadcastRegistry"));
vm.assume(malice_ != getContract(ETH, "AsyncStateRegistry"));

AMBMessage memory ambMessage;
BroadCastAMBExtraData memory ambExtraData;
address coreStateRegistry;

(ambMessage, ambExtraData, coreStateRegistry) =
setupBroadcastPayloadAMBData(users[userIndex], address(hyperlaneImplementation));

vm.expectRevert(Error.NOT_STATE_REGISTRY.selector);
vm.assume(malice_ != getContract(ETH, "CoreStateRegistry"));
vm.assume(malice_ != getContract(ETH, "TimelockStateRegistry"));
vm.assume(malice_ != getContract(ETH, "BroadcastRegistry"));
vm.assume(malice_ != getContract(ETH, "AsyncStateRegistry"));
vm.stopPrank();

vm.deal(malice_, 100 ether);
vm.prank(malice_);
vm.expectRevert(Error.NOT_STATE_REGISTRY.selector);
hyperlaneImplementation.dispatchPayload{ value: 0.1 ether }(
users[userIndex], chainIds[5], abi.encode(ambMessage), abi.encode(ambExtraData)
);
Expand Down
3 changes: 1 addition & 2 deletions test/mainnet/SmokeTest.Staging.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -555,14 +555,13 @@ contract SmokeTestStaging is MainnetBaseSetup {

for (uint256 i; i < TARGET_DEPLOYMENT_CHAINS.length; ++i) {
uint64 chainId = TARGET_DEPLOYMENT_CHAINS[i];

vm.selectFork(FORKS[chainId]);
superFactory = SuperformFactory(getContract(chainId, "SuperformFactory"));

chainId != BLAST
? assertEq(superFactory.getFormImplementation(5), getContract(chainId, "ERC5115Form"))
: assertEq(superFactory.getFormImplementation(205), getContract(chainId, "ERC5115Form"));
/// @dev in blast there are 6 forms (2 without operator, 2 with wrong state registry and 2 right superforms)

assertEq(superFactory.getFormCount(), chainId == LINEA ? 5 : chainId == BLAST ? 8 : 7);
assertEq(superFactory.getFormStateRegistryId(5), 1);
}
Expand Down
1 change: 0 additions & 1 deletion test/unit/payments/PaymentHelper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,6 @@ contract PaymentHelperTest is ProtocolActions {

assertEq(fees3, 0);
}


function test_estimateSingleDirectMultiVault() public view {
/// @dev scenario: single vault withdrawal
Expand Down
100 changes: 58 additions & 42 deletions test/unit/router-plus/SuperformRouterPlus.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1698,56 +1698,65 @@ contract SuperformRouterPlusTest is ProtocolActions {
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).completeCrossChainRebalance{ value: 1 ether }(completeArgs);
vm.stopPrank();

vm.expectRevert(Error.NOT_VALID_DISPUTER.selector);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).disputeRefund(1);
// Step 5: Request refund

vm.startPrank(deployer);
vm.mockCall(
address(getContract(SOURCE_CHAIN, "SuperRegistry")),
abi.encodeWithSelector(ISuperRegistry.delay.selector),
abi.encode(0)
);
vm.expectRevert(Error.DELAY_NOT_SET.selector);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).disputeRefund(1);
vm.clearMockedCalls();

vm.warp(block.timestamp + 100 days);
vm.expectRevert(Error.DISPUTE_TIME_ELAPSED.selector);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).disputeRefund(1);

vm.warp(block.timestamp - 100 days);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).disputeRefund(1);
/// @dev testing invalid requester (not receiver)
vm.startPrank(address(222));
vm.expectRevert(ISuperformRouterPlusAsync.INVALID_REQUESTER.selector);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).requestRefund(1, 100);
vm.stopPrank();

vm.expectRevert(Error.DISPUTE_TIME_ELAPSED.selector);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).disputeRefund(1);
// @dev testing refund amount exceeds expected amount
vm.startPrank(deployer);
vm.expectRevert(ISuperformRouterPlusAsync.REQUESTED_AMOUNT_TOO_HIGH.selector);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).requestRefund(1, 1000e18);
vm.stopPrank();

vm.expectRevert(ISuperformRouterPlusAsync.INVALID_PROPOSER.selector);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).proposeRefund(1, completeArgs.amountReceivedInterimAsset);
/// @dev testing valid refund request
vm.prank(deployer);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).requestRefund(1, 100);

vm.startPrank(deployer);
vm.expectRevert(ISuperformRouterPlusAsync.INVALID_REFUND_DATA.selector);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).proposeRefund(2, completeArgs.amountReceivedInterimAsset);
(,, uint256 requestedAmount) = SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).refunds(1);
assertEq(requestedAmount, 100);

SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).proposeRefund(1, completeArgs.amountReceivedInterimAsset);
(, address refundToken,) = SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).refunds(1);
assertEq(refundToken, address(args.interimAsset));

vm.expectRevert(ISuperformRouterPlusAsync.REFUND_ALREADY_PROPOSED.selector);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).proposeRefund(1, completeArgs.amountReceivedInterimAsset);
// Step 6: Approve refund

vm.expectRevert(ISuperformRouterPlusAsync.IN_DISPUTE_PHASE.selector);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).finalizeRefund(1);
/// @dev testing invalid approver (not core state registry)
vm.startPrank(address(1234));
vm.expectRevert(ISuperformRouterPlusAsync.NOT_CORE_STATE_REGISTRY_RESCUER.selector);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).approveRefund(1);
vm.stopPrank();

(, address refundToken,,) = SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).refunds(1);
/// @dev testing valid refund approval
uint256 balanceBefore = MockERC20(refundToken).balanceOf(deployer);
uint256 routerBalanceBefore = MockERC20(refundToken).balanceOf(address(ROUTER_PLUS_ASYNC_SOURCE));
vm.startPrank(deployer);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).approveRefund(1);
vm.stopPrank();

vm.warp(block.timestamp + 100 days);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).finalizeRefund(1);
uint256 balanceAfter = MockERC20(refundToken).balanceOf(deployer);

assertGt(balanceAfter, balanceBefore);
assertEq(MockERC20(refundToken).balanceOf(address(ROUTER_PLUS_ASYNC_SOURCE)), routerBalanceBefore - 100);
assertEq(MockERC20(refundToken).balanceOf(address(deployer)), balanceBefore + 100);

(, address interimToken,) = SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).refunds(1);
assertEq(interimToken, address(0));

(, address receiver,) = SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).refunds(1);
assertEq(receiver, address(0));

vm.expectRevert(ISuperformRouterPlusAsync.IN_DISPUTE_PHASE.selector);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).finalizeRefund(1);
(,, uint256 updatedRequestedAmount) = SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).refunds(1);
assertEq(updatedRequestedAmount, 0);
vm.stopPrank();

/// @dev testing refund already approved
vm.startPrank(deployer);
vm.expectRevert(ISuperformRouterPlusAsync.REFUND_ALREADY_APPROVED.selector);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).approveRefund(1);
vm.stopPrank();
}

function test_crossChainRebalance_negativeSlippage() public {
Expand Down Expand Up @@ -1804,6 +1813,7 @@ contract SuperformRouterPlusTest is ProtocolActions {

function test_crossChainRebalance_updateSuperformData_allErrors() public {
vm.selectFork(FORKS[SOURCE_CHAIN]);

SingleVaultSFData memory sfData = SingleVaultSFData({
superformId: superformId1,
amount: 1e18,
Expand Down Expand Up @@ -1911,14 +1921,19 @@ contract SuperformRouterPlusTest is ProtocolActions {

// Reset liqDstChainId
completeArgs.liqRequests[0][0].liqDstChainId = SOURCE_CHAIN;

/// @FIXME: This line is not reacheable
// vm.expectRevert(ISuperformRouterPlusAsync.COMPLETE_REBALANCE_DIFFERENT_RECEIVER.selector);
// completeArgs.receiverAddressSP = address(0x123);
// SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).completeCrossChainRebalance{ value: 1 ether
// }(completeArgs);
vm.stopPrank();
vm.startPrank(ROUTER_PLUS_SOURCE);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).setXChainRebalanceCallData(address(0x123), 2, data);
vm.stopPrank();
vm.startPrank(deployer);

vm.expectRevert(ISuperformRouterPlusAsync.COMPLETE_REBALANCE_DIFFERENT_RECEIVER.selector);
completeArgs.routerPlusPayloadId = 2;
completeArgs.receiverAddressSP = address(0x123);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).completeCrossChainRebalance{ value: 1 ether }(completeArgs);
vm.stopPrank();
completeArgs.routerPlusPayloadId = 1;
completeArgs.receiverAddressSP = deployer;
sfData.liqRequest.token = address(0);

data = IBaseSuperformRouterPlus.XChainRebalanceData({
Expand All @@ -1939,6 +1954,7 @@ contract SuperformRouterPlusTest is ProtocolActions {
completeArgs.routerPlusPayloadId = 2;
vm.expectRevert(ISuperformRouterPlusAsync.COMPLETE_REBALANCE_INVALID_TX_DATA_UPDATE.selector);
SuperformRouterPlusAsync(ROUTER_PLUS_ASYNC_SOURCE).completeCrossChainRebalance{ value: 1 ether }(completeArgs);
vm.stopPrank();
}

//////////////////////////////////////////////////////////////
Expand Down