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: update approvals in routerplus [SUP-8878] #643

Draft
wants to merge 17 commits into
base: v1.5
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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_rebalanceSinglePosition_multiDstMultiVaultDepositSelector --evm-version cancun -vvv

.PHONY: ftest
ftest: ## Runs tests with cancun evm version
Expand Down
5 changes: 5 additions & 0 deletions src/interfaces/ISuperformRouterPlus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,9 @@ interface ISuperformRouterPlus is IBaseSuperformRouterPlus {
/// @dev Forwards dust to Paymaster
/// @param token_ the token to forward
function forwardDustToPaymaster(address token_) external;

/// @dev only callable by Emergency Admin
/// @notice sets the global slippage for all rebalances
/// @param slippage_ The slippage tolerance for same chain rebalances
function setGlobalSlippage(uint256 slippage_) external;
}
105 changes: 102 additions & 3 deletions src/router-plus/SuperformRouterPlus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,24 @@ import {
import { IBaseRouter } from "src/interfaces/IBaseRouter.sol";
import { ISuperformRouterPlus, IERC20 } from "src/interfaces/ISuperformRouterPlus.sol";
import { ISuperformRouterPlusAsync } from "src/interfaces/ISuperformRouterPlusAsync.sol";
import { LiqRequest } from "src/types/DataTypes.sol";
import { ISuperRBAC } from "src/interfaces/ISuperRBAC.sol";
import { IBridgeValidator } from "src/interfaces/IBridgeValidator.sol";

/// @title SuperformRouterPlus
/// @dev Performs rebalances and deposits on the Superform platform
/// @author Zeropoint Labs
contract SuperformRouterPlus is ISuperformRouterPlus, BaseSuperformRouterPlus {
using SafeERC20 for IERC20;

uint256 public GLOBAL_SLIPPAGE;
uint256 public ROUTER_PLUS_PAYLOAD_ID;

//////////////////////////////////////////////////////////////
// CONSTRUCTOR //
//////////////////////////////////////////////////////////////

constructor(address superRegistry_) BaseSuperformRouterPlus(superRegistry_) { }
constructor(address superRegistry_) BaseSuperformRouterPlus(superRegistry_) {}

//////////////////////////////////////////////////////////////
// EXTERNAL WRITE FUNCTIONS //
Expand Down Expand Up @@ -389,6 +393,17 @@ contract SuperformRouterPlus is ISuperformRouterPlus, BaseSuperformRouterPlus {
}
}

/// @inheritdoc ISuperformRouterPlus
function setGlobalSlippage(uint256 slippage_) external override {
if (!_hasRole(keccak256("EMERGENCY_ADMIN_ROLE"), msg.sender)) {
revert Error.NOT_PRIVILEGED_CALLER(keccak256("EMERGENCY_ADMIN_ROLE"));
}

require(slippage_ <= ENTIRE_SLIPPAGE && slippage_ > 0, "Invalid slippage");

GLOBAL_SLIPPAGE = slippage_;
}

//////////////////////////////////////////////////////////////
// INTERNAL FUNCTIONS //
//////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -460,12 +475,88 @@ contract SuperformRouterPlus is ISuperformRouterPlus, BaseSuperformRouterPlus {
revert Error.VAULT_IMPLEMENTATION_FAILED();
}

bytes4 rebalanceToSelector = _parseSelectorMem(rebalanceToCallData);
/// @dev step 3: rebalance into a new superform with rebalanceCallData
if (!whitelistedSelectors[Actions.DEPOSIT][_parseSelectorMem(rebalanceToCallData)]) {
if (!whitelistedSelectors[Actions.DEPOSIT][rebalanceToSelector]) {
revert INVALID_DEPOSIT_SELECTOR();
}

_deposit(router_, interimAsset, amountToDeposit, args.rebalanceToMsgValue, rebalanceToCallData);
uint256 amountIn;
bool containsSwapData;

if (rebalanceToSelector == IBaseRouter.singleDirectSingleVaultDeposit.selector) {
SingleVaultSFData memory sfData =
abi.decode(_parseCallData(rebalanceToCallData), (SingleDirectSingleVaultStateReq)).superformData;
(amountIn, containsSwapData) = _takeAmountIn(sfData.liqRequest, sfData.amount);
} else if (rebalanceToSelector == IBaseRouter.singleXChainSingleVaultDeposit.selector) {
SingleVaultSFData memory sfData =
abi.decode(_parseCallData(rebalanceToCallData), (SingleXChainSingleVaultStateReq)).superformData;
(amountIn, containsSwapData) = _takeAmountIn(sfData.liqRequest, sfData.amount);
} else if (rebalanceToSelector == IBaseRouter.singleDirectMultiVaultDeposit.selector) {
MultiVaultSFData memory sfData =
abi.decode(_parseCallData(rebalanceToCallData), (SingleDirectMultiVaultStateReq)).superformData;
uint256 len = sfData.liqRequests.length;
uint256 amountInTemp;
for (uint256 i; i < len; ++i) {
(amountInTemp, containsSwapData) = _takeAmountIn(sfData.liqRequests[i], sfData.amounts[i]);
amountIn += amountInTemp;
}
} else if (rebalanceToSelector == IBaseRouter.singleXChainMultiVaultDeposit.selector) {
MultiVaultSFData memory sfData =
abi.decode(_parseCallData(rebalanceToCallData), (SingleXChainMultiVaultStateReq)).superformsData;
uint256 len = sfData.liqRequests.length;
uint256 amountInTemp;
for (uint256 i; i < len; ++i) {
(amountInTemp, containsSwapData) = _takeAmountIn(sfData.liqRequests[i], sfData.amounts[i]);
amountIn += amountInTemp;
}
} else if (rebalanceToSelector == IBaseRouter.multiDstSingleVaultDeposit.selector) {
SingleVaultSFData[] memory sfData =
abi.decode(_parseCallData(rebalanceToCallData), (MultiDstSingleVaultStateReq)).superformsData;
uint256 lenDst = sfData.length;
uint256 amountInTemp;
for (uint256 i; i < lenDst; ++i) {
(amountInTemp, containsSwapData) = _takeAmountIn(sfData[i].liqRequest, sfData[i].amount);
amountIn += amountInTemp;
}
} else if (rebalanceToSelector == IBaseRouter.multiDstMultiVaultDeposit.selector) {
MultiVaultSFData[] memory sfData =
abi.decode(_parseCallData(rebalanceToCallData), (MultiDstMultiVaultStateReq)).superformsData;
uint256 lenDst = sfData.length;
uint256 amountInTemp;
for (uint256 i; i < lenDst; ++i) {
uint256 len = sfData[i].liqRequests.length;
for (uint256 j; j < len; ++j) {
(amountInTemp, containsSwapData) = _takeAmountIn(sfData[i].liqRequests[j], sfData[i].amounts[j]);
amountIn += amountInTemp;
}
}
}

if (containsSwapData) {
if (ENTIRE_SLIPPAGE * amountToDeposit < ((amountIn * (ENTIRE_SLIPPAGE - GLOBAL_SLIPPAGE)))) {
revert ASSETS_RECEIVED_OUT_OF_SLIPPAGE();
}
}

_deposit(router_, interimAsset, amountIn, args.rebalanceToMsgValue, rebalanceToCallData);
}

function _takeAmountIn(
LiqRequest memory liqReq,
uint256 sfDataAmount
)
internal
view
returns (uint256 amountIn, bool containsSwapData)
{
bytes memory txData = liqReq.txData;
if (txData.length == 0) {
amountIn = sfDataAmount;
} else {
amountIn = IBridgeValidator(superRegistry.getBridgeValidator(liqReq.bridgeId)).decodeAmountIn(txData, false);
containsSwapData = true;
}
}

function _transferSuperPositions(
Expand Down Expand Up @@ -596,4 +687,12 @@ contract SuperformRouterPlus is ISuperformRouterPlus, BaseSuperformRouterPlus {
}
}
}

/// @dev returns if an address has a specific role
/// @param id_ the role id
/// @param addressToCheck_ the address to check
/// @return true if the address has the role, false otherwise
function _hasRole(bytes32 id_, address addressToCheck_) internal view returns (bool) {
return ISuperRBAC(superRegistry.getAddress(keccak256("SUPER_RBAC"))).hasRole(id_, addressToCheck_);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,14 @@ contract HyperlaneImplementationTest is CommonProtocolActions {
(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.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
181 changes: 181 additions & 0 deletions test/unit/router-plus/SuperformRouterPlus.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ISuperformRouterPlusAsync } from "src/interfaces/ISuperformRouterPlusAs
import { IBaseSuperformRouterPlus } from "src/interfaces/IBaseSuperformRouterPlus.sol";
import { IBaseRouter } from "src/interfaces/IBaseRouter.sol";
import { ERC20 } from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import { MultiVaultSFData } from "src/types/DataTypes.sol";

contract RejectEther {
// This function will revert when called, simulating a contract that can't receive native tokens
Expand Down Expand Up @@ -503,6 +504,161 @@ contract SuperformRouterPlusTest is ProtocolActions {
vm.stopPrank();
}

function test_rebalanceSinglePosition_invalidDepositSelector() public {
vm.startPrank(deployer);

_directDeposit(superformId1);

ISuperformRouterPlus.RebalanceSinglePositionSyncArgs memory args =
_buildRebalanceSinglePositionToOneVaultArgs(deployer);

SuperPositions(SUPER_POSITIONS_SOURCE).increaseAllowance(ROUTER_PLUS_SOURCE, superformId1, args.sharesToRedeem);

args.rebalanceToCallData = abi.encodeWithSelector(bytes4(keccak256("invalidSelector()")));

vm.expectRevert(ISuperformRouterPlus.INVALID_DEPOSIT_SELECTOR.selector);
SuperformRouterPlus(ROUTER_PLUS_SOURCE).rebalanceSinglePosition{ value: 2 ether }(args);

vm.stopPrank();
}

function test_rebalanceSinglePosition_noSwapData() public {
vm.startPrank(deployer);

_directDeposit(superformId1);

ISuperformRouterPlus.RebalanceSinglePositionSyncArgs memory args =
_buildRebalanceSinglePositionToOneVaultArgs(deployer);
SingleVaultSFData memory sfData =
abi.decode(_parseCallData(args.rebalanceToCallData), (SingleDirectSingleVaultStateReq)).superformData;
bytes memory emptyData;
sfData.liqRequest.txData = emptyData;

SuperPositions(SUPER_POSITIONS_SOURCE).increaseAllowance(ROUTER_PLUS_SOURCE, superformId1, args.sharesToRedeem);
SuperformRouterPlus(ROUTER_PLUS_SOURCE).rebalanceSinglePosition{ value: 2 ether }(args);

assertEq(SuperPositions(SUPER_POSITIONS_SOURCE).balanceOf(deployer, superformId1), 0);

assertGt(SuperPositions(SUPER_POSITIONS_SOURCE).balanceOf(deployer, superformId2), 0);
}

function test_rebalanceSinglePosition_singleDirectSingleVaultDepositSelector() public {
vm.startPrank(deployer);

_directDeposit(superformId1);

ISuperformRouterPlus.RebalanceSinglePositionSyncArgs memory args =
_buildRebalanceSinglePositionToOneVaultArgs(deployer);

SuperPositions(SUPER_POSITIONS_SOURCE).increaseAllowance(ROUTER_PLUS_SOURCE, superformId1, args.sharesToRedeem);
SuperformRouterPlus(ROUTER_PLUS_SOURCE).rebalanceSinglePosition{ value: 2 ether }(args);

vm.stopPrank();

assertEq(SuperPositions(SUPER_POSITIONS_SOURCE).balanceOf(deployer, superformId1), 0);

assertGt(SuperPositions(SUPER_POSITIONS_SOURCE).balanceOf(deployer, superformId2), 0);
}

function test_rebalanceSinglePosition_singleXChainSingleVaultDepositSelector() public {
vm.startPrank(deployer);

_directDeposit(superformId1);

ISuperformRouterPlus.RebalanceSinglePositionSyncArgs memory args =
_buildRebalanceSinglePositionToOneVaultArgs(deployer);

SuperPositions(SUPER_POSITIONS_SOURCE).increaseAllowance(ROUTER_PLUS_SOURCE, superformId1, args.sharesToRedeem);
SuperformRouterPlus(ROUTER_PLUS_SOURCE).rebalanceSinglePosition{ value: 2 ether }(args);

assertEq(SuperPositions(SUPER_POSITIONS_SOURCE).balanceOf(deployer, superformId1), 0);

assertGt(SuperPositions(SUPER_POSITIONS_SOURCE).balanceOf(deployer, superformId2), 0);
}

function test_rebalanceSinglePosition_singleXChainMultiVaultDeposit() public { }

function test_rebalanceSinglePosition_multiDstSingleVaultDepositSelector() public { }

function test_rebalanceSinglePosition_multiDstMultiVaultDepositSelector() public {
vm.startPrank(deployer);

_directDeposit(superformId1);
_directDeposit(superformId2);

SuperPositions(SUPER_POSITIONS_SOURCE).increaseAllowance(ROUTER_PLUS_SOURCE, superformId1, 1e18);
SuperPositions(SUPER_POSITIONS_SOURCE).increaseAllowance(ROUTER_PLUS_SOURCE, superformId2, 1e18);

uint256[] memory sharesToRedeem = new uint256[](2);
sharesToRedeem[0] = 1e18;
sharesToRedeem[1] = 1e18;

uint256[] memory ids = new uint256[](2);
ids[0] = superformId1;
ids[1] = superformId2;

uint8[][] memory ambIds_ = new uint8[][](2);
ambIds_[0] = new uint8[](2);
ambIds_[0][0] = AMBs[0];
ambIds_[0][1] = AMBs[1];
ambIds_[1] = new uint8[](2);
ambIds_[1][0] = AMBs[0];
ambIds_[1][1] = AMBs[1];

uint64[] memory dstChainIds = new uint64[](2);
dstChainIds[0] = OP;
dstChainIds[1] = ARBI;

MultiVaultSFData[] memory mvSfData = new MultiVaultSFData[](2);
mvSfData[0] = MultiVaultSFData({
superformIds: new uint256[](2),
amounts: new uint256[](2),
outputAmounts: new uint256[](2),
maxSlippages: new uint256[](2),
liqRequests: new LiqRequest[](2),
permit2data: "",
hasDstSwaps: new bool[](2),
retain4626s: new bool[](2),
receiverAddress: address(deployer),
receiverAddressSP: address(deployer),
extraFormData: ""
});
mvSfData[1] = MultiVaultSFData({
superformIds: new uint256[](2),
amounts: new uint256[](2),
outputAmounts: new uint256[](2),
maxSlippages: new uint256[](2),
liqRequests: new LiqRequest[](2),
permit2data: "",
hasDstSwaps: new bool[](2),
retain4626s: new bool[](2),
receiverAddress: address(deployer),
receiverAddressSP: address(deployer),
extraFormData: ""
});
MultiDstMultiVaultStateReq memory req = MultiDstMultiVaultStateReq(ambIds_, dstChainIds, mvSfData);
ISuperformRouterPlus.RebalanceMultiPositionsSyncArgs memory positionArgs = ISuperformRouterPlus
.RebalanceMultiPositionsSyncArgs({
ids: ids,
sharesToRedeem: sharesToRedeem,
expectedAmountToReceivePostRebalanceFrom: 10_000,
rebalanceFromMsgValue: 1 ether,
rebalanceToMsgValue: 1 ether,
interimAsset: getContract(SOURCE_CHAIN, "DAI"),
slippage: 300,
receiverAddressSP: address(deployer),
callData: _callDataRebalanceFromTwoVaults(getContract(SOURCE_CHAIN, "DAI")),
rebalanceToCallData: abi.encodeCall(IBaseRouter.multiDstMultiVaultDeposit, req)
});

SuperformRouterPlus(ROUTER_PLUS_SOURCE).rebalanceMultiPositions{ value: 5 ether }(positionArgs);
vm.stopPrank();

assertEq(SuperPositions(SUPER_POSITIONS_SOURCE).balanceOf(deployer, superformId1), 0);

assertGt(SuperPositions(SUPER_POSITIONS_SOURCE).balanceOf(deployer, superformId2), 0);
}

function test_refundUnusedAndResetApprovals_failedToSendNative() public {
address rejectEther = address(new RejectEther());
deal(rejectEther, 3 ether);
Expand Down Expand Up @@ -4167,4 +4323,29 @@ contract SuperformRouterPlusTest is ProtocolActions {
calldata_ := add(data, 0x04)
}
}

function test_setGlobalSlippage() public {
// Test invalid caller
vm.startPrank(address(12_345));
vm.expectRevert();
SuperformRouterPlus(getContract(SOURCE_CHAIN, "SuperformRouterPlus")).setGlobalSlippage(100);
vm.stopPrank();

// Test slippage greater than ENTIRE_SLIPPAGE
vm.startPrank(deployer);
vm.expectRevert();
SuperformRouterPlus(getContract(SOURCE_CHAIN, "SuperformRouterPlus")).setGlobalSlippage(1_000_000);
vm.stopPrank();

// Test slippage 0
vm.startPrank(deployer);
vm.expectRevert();
SuperformRouterPlus(getContract(SOURCE_CHAIN, "SuperformRouterPlus")).setGlobalSlippage(0);
vm.stopPrank();

// Test slippage valid
vm.startPrank(deployer);
SuperformRouterPlus(getContract(SOURCE_CHAIN, "SuperformRouterPlus")).setGlobalSlippage(100);
vm.stopPrank();
}
}
4 changes: 4 additions & 0 deletions test/utils/BaseSetup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,10 @@ abstract contract BaseSetup is StdInvariant, Test {
vars.superformRouterPlus = address(new SuperformRouterPlus{ salt: salt }(vars.superRegistry));
contracts[vars.chainId][bytes32(bytes("SuperformRouterPlus"))] = vars.superformRouterPlus;

/// Set the global slippage
SuperformRouterPlus(vars.superformRouterPlus).setGlobalSlippage(100);

/// @dev deploy Superform Router Plus Async
vars.superformRouterPlusAsync = address(new SuperformRouterPlusAsync{ salt: salt }(vars.superRegistry));
contracts[vars.chainId][bytes32(bytes("SuperformRouterPlusAsync"))] = vars.superformRouterPlusAsync;

Expand Down