Skip to content

Commit 0a7a58b

Browse files
authored
Merge pull request #645 from superform-xyz/tamara-low-info-fixes
fix: insert tolerance into `redeemShare()` checks [SUP-8853]
2 parents ddd63f2 + 1ab28aa commit 0a7a58b

File tree

3 files changed

+141
-0
lines changed

3 files changed

+141
-0
lines changed

src/interfaces/ISuperformRouterPlus.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ interface ISuperformRouterPlus is IBaseSuperformRouterPlus {
4949
/// @notice thrown if the amount of assets received is lower than the slippage
5050
error ASSETS_RECEIVED_OUT_OF_SLIPPAGE();
5151

52+
/// @notice thrown if the tolerance is exceeded during shares redemption
53+
error TOLERANCE_EXCEEDED();
54+
5255
//////////////////////////////////////////////////////////////
5356
// EVENTS //
5457
//////////////////////////////////////////////////////////////

src/router-plus/SuperformRouterPlus.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ contract SuperformRouterPlus is ISuperformRouterPlus, BaseSuperformRouterPlus {
2727

2828
uint256 public ROUTER_PLUS_PAYLOAD_ID;
2929

30+
/// @dev Tolerance constant to account for tokens with rounding issues on transfer
31+
uint256 constant TOLERANCE_CONSTANT = 10 wei;
32+
3033
//////////////////////////////////////////////////////////////
3134
// CONSTRUCTOR //
3235
//////////////////////////////////////////////////////////////
@@ -518,6 +521,10 @@ contract SuperformRouterPlus is ISuperformRouterPlus, BaseSuperformRouterPlus {
518521
uint256 assetsBalanceAfter = asset.balanceOf(address(this));
519522
balanceDifference = assetsBalanceAfter - assetsBalanceBefore;
520523

524+
/// @dev validate the tolerance
525+
if (assets < TOLERANCE_CONSTANT || balanceDifference < TOLERANCE_CONSTANT) revert TOLERANCE_EXCEEDED();
526+
527+
/// @dev validate the slippage
521528
if (
522529
(balanceDifference != assets)
523530
|| (ENTIRE_SLIPPAGE * assets < ((expectedOutputAmount_ * (ENTIRE_SLIPPAGE - maxSlippage_))))

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

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ISuperformRouterPlusAsync } from "src/interfaces/ISuperformRouterPlusAs
88
import { IBaseSuperformRouterPlus } from "src/interfaces/IBaseSuperformRouterPlus.sol";
99
import { IBaseRouter } from "src/interfaces/IBaseRouter.sol";
1010
import { ERC20 } from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
11+
import { IERC4626 } from "openzeppelin-contracts/contracts/interfaces/IERC4626.sol";
1112

1213
contract RejectEther {
1314
// This function will revert when called, simulating a contract that can't receive native tokens
@@ -553,6 +554,136 @@ contract SuperformRouterPlusTest is ProtocolActions {
553554
SuperformRouterPlus(ROUTER_PLUS_SOURCE).deposit4626{ value: 1 ether }(address(mockVault), args);
554555
}
555556

557+
function test_deposit4626_toleranceExceeded() public {
558+
vm.startPrank(deployer);
559+
560+
// Deploy a mock ERC4626 vault
561+
VaultMock mockVault = new VaultMock(IERC20(getContract(SOURCE_CHAIN, "DAI")), "Mock Vault", "mVLT");
562+
563+
// Mint some DAI to the deployer
564+
uint256 daiAmount = 1e18;
565+
deal(getContract(SOURCE_CHAIN, "DAI"), deployer, daiAmount);
566+
567+
// Approve and deposit DAI into the mock vault
568+
MockERC20(getContract(SOURCE_CHAIN, "DAI")).approve(address(mockVault), daiAmount);
569+
uint256 vaultTokenAmount = mockVault.deposit(daiAmount, deployer);
570+
571+
// Mock the redeem function to return a value less than expected
572+
vm.mockCall(
573+
address(mockVault),
574+
abi.encodeWithSelector(IERC4626.redeem.selector, vaultTokenAmount, ROUTER_PLUS_SOURCE, ROUTER_PLUS_SOURCE),
575+
abi.encode(1) // Return 1 wei
576+
);
577+
578+
// Prepare deposit4626 args
579+
ISuperformRouterPlus.Deposit4626Args memory args = ISuperformRouterPlus.Deposit4626Args({
580+
amount: vaultTokenAmount,
581+
expectedOutputAmount: daiAmount,
582+
maxSlippage: 100, // 1%
583+
receiverAddressSP: deployer,
584+
depositCallData: _buildDepositCallData(superformId1, daiAmount)
585+
});
586+
587+
// Approve RouterPlus to spend vault tokens
588+
mockVault.approve(ROUTER_PLUS_SOURCE, vaultTokenAmount);
589+
590+
// Execute deposit4626
591+
vm.recordLogs();
592+
vm.expectRevert(ISuperformRouterPlus.TOLERANCE_EXCEEDED.selector);
593+
SuperformRouterPlus(ROUTER_PLUS_SOURCE).deposit4626{ value: 1 ether }(address(mockVault), args);
594+
595+
vm.stopPrank();
596+
}
597+
598+
function test_deposit4626_toleranceExceeded_noSlippage() public {
599+
vm.startPrank(deployer);
600+
601+
// Deploy a mock ERC4626 vault
602+
VaultMock mockVault = new VaultMock(IERC20(getContract(SOURCE_CHAIN, "DAI")), "Mock Vault", "mVLT");
603+
604+
// Mint some DAI to the deployer
605+
uint256 daiAmount = 1e18;
606+
deal(getContract(SOURCE_CHAIN, "DAI"), deployer, daiAmount);
607+
608+
// Approve and deposit DAI into the mock vault
609+
MockERC20(getContract(SOURCE_CHAIN, "DAI")).approve(address(mockVault), daiAmount);
610+
uint256 vaultTokenAmount = mockVault.deposit(daiAmount, deployer);
611+
612+
// Mock the redeem function to return a value less than expected
613+
vm.mockCall(
614+
address(mockVault),
615+
abi.encodeWithSelector(IERC4626.redeem.selector, vaultTokenAmount, ROUTER_PLUS_SOURCE, ROUTER_PLUS_SOURCE),
616+
abi.encode(daiAmount - 15 wei) // Return 15 wei less than expected
617+
);
618+
619+
// Prepare deposit4626 args
620+
ISuperformRouterPlus.Deposit4626Args memory args = ISuperformRouterPlus.Deposit4626Args({
621+
amount: vaultTokenAmount,
622+
expectedOutputAmount: daiAmount,
623+
maxSlippage: 100, // 1%
624+
receiverAddressSP: deployer,
625+
depositCallData: _buildDepositCallData(superformId1, daiAmount)
626+
});
627+
628+
// Approve RouterPlus to spend vault tokens
629+
mockVault.approve(ROUTER_PLUS_SOURCE, vaultTokenAmount);
630+
631+
// Execute deposit4626
632+
vm.recordLogs();
633+
vm.expectRevert(ISuperformRouterPlus.TOLERANCE_EXCEEDED.selector);
634+
SuperformRouterPlus(ROUTER_PLUS_SOURCE).deposit4626{ value: 1 ether }(address(mockVault), args);
635+
636+
vm.stopPrank();
637+
}
638+
639+
function test_deposit4626_withinTolerance() public {
640+
vm.startPrank(deployer);
641+
642+
// Deploy a mock ERC4626 vault
643+
VaultMock mockVault = new VaultMock(IERC20(getContract(SOURCE_CHAIN, "DAI")), "Mock Vault", "mVLT");
644+
645+
// Mint some DAI to the deployer
646+
uint256 daiAmount = 1e18 - 2 wei;
647+
648+
// Approve and deposit DAI into the mock vault
649+
MockERC20(getContract(SOURCE_CHAIN, "DAI")).approve(address(mockVault), daiAmount);
650+
uint256 vaultTokenAmount = mockVault.deposit(daiAmount, deployer);
651+
652+
// Prepare deposit4626 args
653+
ISuperformRouterPlus.Deposit4626Args memory args = ISuperformRouterPlus.Deposit4626Args({
654+
amount: vaultTokenAmount,
655+
expectedOutputAmount: daiAmount, // Assuming 1:1 ratio for simplicity
656+
maxSlippage: 100, // 1%
657+
receiverAddressSP: deployer,
658+
depositCallData: _buildDepositCallData(superformId1, daiAmount)
659+
});
660+
661+
// Approve RouterPlus to spend vault tokens
662+
mockVault.approve(ROUTER_PLUS_SOURCE, vaultTokenAmount);
663+
664+
// Execute deposit4626
665+
vm.recordLogs();
666+
SuperformRouterPlus(ROUTER_PLUS_SOURCE).deposit4626{ value: 1 ether }(address(mockVault), args);
667+
668+
// Verify the results
669+
assertGt(
670+
SuperPositions(SUPER_POSITIONS_SOURCE).balanceOf(deployer, superformId1),
671+
0,
672+
"Superform balance should be greater than 0"
673+
);
674+
675+
// Check that the vault tokens were transferred from the deployer
676+
assertEq(mockVault.balanceOf(deployer), 0, "Deployer's vault token balance should be 0");
677+
678+
// Check that the RouterPlus contract doesn't hold any tokens
679+
assertEq(mockVault.balanceOf(ROUTER_PLUS_SOURCE), 0, "RouterPlus should not hold any vault tokens");
680+
assertEq(
681+
MockERC20(getContract(SOURCE_CHAIN, "DAI")).balanceOf(ROUTER_PLUS_SOURCE),
682+
0,
683+
"RouterPlus should not hold any DAI"
684+
);
685+
}
686+
556687
function test_rebalanceSinglePosition_errors() public {
557688
vm.startPrank(deployer);
558689

0 commit comments

Comments
 (0)