From 718b35219b56f7d2d883d0db23a25b9a3ad81319 Mon Sep 17 00:00:00 2001 From: samooyo Date: Thu, 13 Nov 2025 13:56:23 +0100 Subject: [PATCH 1/9] refactor: switch the aggregator command from Odos to Enso --- flake.lock | 30 +++++++-------- src/UniversalRouter.sol | 13 ++++--- src/base/Dispatcher.sol | 37 +++++++++++-------- src/interfaces/lido/IStETH.sol | 4 +- src/libraries/smardex/SmardexRouterLib.sol | 10 ++--- src/libraries/usdn/UsdnProtocolRouterLib.sol | 12 +++--- src/modules/{Odos.sol => Enso.sol} | 2 +- src/utils/Payment.sol | 4 +- test/integration/ExecuteOdos.t.sol | 4 +- .../ExecuteSmardexRemoveLiquidity.t.sol | 6 +-- .../ExecuteUsdnInitiateActionsPermit.t.sol | 5 ++- .../UsdnProtocolInitiateClosePosition.t.sol | 30 ++++++++------- test/integration/UsdnProtocolLiquidate.t.sol | 4 +- ...sdnProtocolTransferPositionOwnership.t.sol | 4 +- .../UsdnProtocolValidateClosePosition.t.sol | 4 +- test/integration/utils/Fixtures.sol | 10 +++-- test/integration/utils/SigUtils.sol | 5 ++- 17 files changed, 98 insertions(+), 86 deletions(-) rename src/modules/{Odos.sol => Enso.sol} (98%) diff --git a/flake.lock b/flake.lock index 0f2f9d15..ce5be09e 100644 --- a/flake.lock +++ b/flake.lock @@ -59,11 +59,11 @@ ] }, "locked": { - "lastModified": 1746090613, - "narHash": "sha256-Si8nvKH31lzwbJ8aSncgQDYxf3IHreNVJVzSvovnLSM=", + "lastModified": 1761901832, + "narHash": "sha256-/01qznpB/Smmy1srjhUz89wM146S6AYMGJeiIcsm0/Q=", "owner": "shazow", "repo": "foundry.nix", - "rev": "2b56cb8bae12b0a0608e5ba6dd4af4a9cf9609b4", + "rev": "c2d2e814e79ed9937bd6a6238f5bb206d8048e96", "type": "github" }, "original": { @@ -75,11 +75,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1746576598, - "narHash": "sha256-FshoQvr6Aor5SnORVvh/ZdJ1Sa2U4ZrIMwKBX5k2wu0=", + "lastModified": 1762943920, + "narHash": "sha256-ITeH8GBpQTw9457ICZBddQEBjlXMmilML067q0e6vqY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b3582c75c7f21ce0b429898980eddbbf05c68e55", + "rev": "91c9a64ce2a84e648d0cf9671274bb9c2fb9ba60", "type": "github" }, "original": { @@ -105,11 +105,11 @@ ] }, "locked": { - "lastModified": 1746671794, - "narHash": "sha256-V+mpk2frYIEm85iYf+KPDmCGG3zBRAEhbv0E3lHdG2U=", + "lastModified": 1763001554, + "narHash": "sha256-wsfhRTuxu6f06RMmP4JWcq3wWRlmYtQaJZ6b3f+EJ94=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "ceec434b8741c66bb8df5db70d7e629a9d9c598f", + "rev": "315d97eb753cee8e1aa039a5e622b84d32a454bb", "type": "github" }, "original": { @@ -127,11 +127,11 @@ "solc-macos-amd64-list-json": "solc-macos-amd64-list-json" }, "locked": { - "lastModified": 1742758229, - "narHash": "sha256-FrU9rhab/0vOjjeFoQF+Ej43zRLv3enUIYjgLrH3Gd8=", + "lastModified": 1756368702, + "narHash": "sha256-cqEHv7uCV0LibmQphyiXZ1+jYtGjMNb9Pae4tfcAcF8=", "owner": "hellwolf", "repo": "solc.nix", - "rev": "6885b61bac89da19a6e3c70b89fdd592e2cef884", + "rev": "d83e90df2fa8359a690f6baabf76099432193c3f", "type": "github" }, "original": { @@ -143,13 +143,13 @@ "solc-macos-amd64-list-json": { "flake": false, "locked": { - "narHash": "sha256-U5ckttxwKO13gIKggel6iybG5oTDbSidPR5nH3Gs+kY=", + "narHash": "sha256-AvITkfpNYgCypXuLJyqco0li+unVw39BAfdOZvd/SPE=", "type": "file", - "url": "https://github.com/ethereum/solc-bin/raw/30a3695/macosx-amd64/list.json" + "url": "https://github.com/argotorg/solc-bin/raw/26fc3fd/macosx-amd64/list.json" }, "original": { "type": "file", - "url": "https://github.com/ethereum/solc-bin/raw/30a3695/macosx-amd64/list.json" + "url": "https://github.com/argotorg/solc-bin/raw/26fc3fd/macosx-amd64/list.json" } }, "systems": { diff --git a/src/UniversalRouter.sol b/src/UniversalRouter.sol index ae6db168..fbb5f84c 100644 --- a/src/UniversalRouter.sol +++ b/src/UniversalRouter.sol @@ -2,7 +2,8 @@ pragma solidity 0.8.26; import { - PaymentsImmutables, PaymentsParameters + PaymentsImmutables, + PaymentsParameters } from "@uniswap/universal-router/contracts/modules/PaymentsImmutables.sol"; import { UniswapImmutables, @@ -13,7 +14,7 @@ import { Dispatcher } from "./base/Dispatcher.sol"; import { RouterParameters } from "./base/RouterImmutables.sol"; import { IUniversalRouter } from "./interfaces/IUniversalRouter.sol"; import { Commands } from "./libraries/Commands.sol"; -import { Odos } from "./modules/Odos.sol"; +import { Enso } from "./modules/Enso.sol"; import { LidoImmutables } from "./modules/lido/LidoImmutables.sol"; import { SmardexRouter } from "./modules/smardex/SmardexRouter.sol"; import { UsdnProtocolImmutables, UsdnProtocolParameters } from "./modules/usdn/UsdnProtocolImmutables.sol"; @@ -32,14 +33,14 @@ contract UniversalRouter is IUniversalRouter, Dispatcher { * @param params The immutable parameters of the router */ constructor(RouterParameters memory params) - UniswapImmutables( - UniswapParameters(params.v2Factory, params.v3Factory, params.pairInitCodeHash, params.poolInitCodeHash) - ) + UniswapImmutables(UniswapParameters( + params.v2Factory, params.v3Factory, params.pairInitCodeHash, params.poolInitCodeHash + )) PaymentsImmutables(PaymentsParameters(params.permit2, params.weth9, address(0), address(0))) UsdnProtocolImmutables(UsdnProtocolParameters(params.usdnProtocol, params.wusdn)) LidoImmutables(params.wstEth) SmardexRouter(params.smardexFactory) - Odos(params.odosSorRouter) + Enso(params.odosSorRouter) { } /// @inheritdoc IUniversalRouter diff --git a/src/base/Dispatcher.sol b/src/base/Dispatcher.sol index be4310c1..883a599c 100644 --- a/src/base/Dispatcher.sol +++ b/src/base/Dispatcher.sol @@ -17,7 +17,7 @@ import { LidoRouterLib } from "../libraries/lido/LidoRouterLib.sol"; import { SmardexRouterLib } from "../libraries/smardex/SmardexRouterLib.sol"; import { UniswapV2RouterLib } from "../libraries/uniswap/UniswapV2RouterLib.sol"; import { UsdnProtocolRouterLib } from "../libraries/usdn/UsdnProtocolRouterLib.sol"; -import { Odos } from "../modules/Odos.sol"; +import { Enso } from "../modules/Enso.sol"; import { Sweep } from "../modules/Sweep.sol"; import { LidoImmutables } from "../modules/lido/LidoImmutables.sol"; import { SmardexRouter } from "../modules/smardex/SmardexRouter.sol"; @@ -36,7 +36,7 @@ abstract contract Dispatcher is LockAndMap, UsdnProtocolRouter, LidoImmutables, - Odos + Enso { using BytesLib for bytes; @@ -433,16 +433,20 @@ abstract contract Dispatcher is USDN_PROTOCOL, previousActionsData, maxValidations, ethAmount ); } else if (command == Commands.TRANSFER_POSITION_OWNERSHIP) { - (IUsdnProtocolTypes.PositionId memory posId, address newOwner, bytes memory delegationSignature) - = abi.decode(inputs, (IUsdnProtocolTypes.PositionId, address, bytes)); - (success_, output_) = address(USDN_PROTOCOL).call( - abi.encodeWithSelector( - USDN_PROTOCOL.transferPositionOwnership.selector, - posId, - _mapSafe(newOwner), - delegationSignature - ) - ); + ( + IUsdnProtocolTypes.PositionId memory posId, + address newOwner, + bytes memory delegationSignature + ) = abi.decode(inputs, (IUsdnProtocolTypes.PositionId, address, bytes)); + (success_, output_) = address(USDN_PROTOCOL) + .call( + abi.encodeWithSelector( + USDN_PROTOCOL.transferPositionOwnership.selector, + posId, + _mapSafe(newOwner), + delegationSignature + ) + ); } else if (command == Commands.REBALANCER_INITIATE_DEPOSIT) { // equivalent: abi.decode(inputs, (uint256, address)) uint256 amount; @@ -545,9 +549,12 @@ abstract contract Dispatcher is recipient := calldataload(inputs.offset) sharesAmount := calldataload(add(inputs.offset, 0x20)) } - (success_, output_) = address(USDN).call( - abi.encodeWithSelector(USDN.transferSharesFrom.selector, lockedBy, map(recipient), sharesAmount) - ); + (success_, output_) = address(USDN) + .call( + abi.encodeWithSelector( + USDN.transferSharesFrom.selector, lockedBy, map(recipient), sharesAmount + ) + ); } else { revert InvalidCommandType(command); } diff --git a/src/interfaces/lido/IStETH.sol b/src/interfaces/lido/IStETH.sol index 320f8b75..0cfb3bd3 100644 --- a/src/interfaces/lido/IStETH.sol +++ b/src/interfaces/lido/IStETH.sol @@ -147,9 +147,7 @@ interface IStETH is IERC20Metadata, IERC20Permit { * @param _sharesAmount The shares amount * @return The amount of transferred tokens */ - function transferSharesFrom(address _sender, address _recipient, uint256 _sharesAmount) - external - returns (uint256); + function transferSharesFrom(address _sender, address _recipient, uint256 _sharesAmount) external returns (uint256); /** * @notice Get whether the contract is paused diff --git a/src/libraries/smardex/SmardexRouterLib.sol b/src/libraries/smardex/SmardexRouterLib.sol index 299724ef..2e430461 100644 --- a/src/libraries/smardex/SmardexRouterLib.sol +++ b/src/libraries/smardex/SmardexRouterLib.sol @@ -260,9 +260,8 @@ library SmardexRouterLib { (address tokenOut, address tokenIn) = data.path.decodeFirstPool(); bool zeroForOne = tokenIn < tokenOut; - (int256 amount0, int256 amount1) = ISmardexPair(smardexFactory.getPair(tokenIn, tokenOut)).swap( - to, zeroForOne, -amountOut.toInt256(), abi.encode(data) - ); + (int256 amount0, int256 amount1) = ISmardexPair(smardexFactory.getPair(tokenIn, tokenOut)) + .swap(to, zeroForOne, -amountOut.toInt256(), abi.encode(data)); if (zeroForOne) { amountIn_ = uint256(amount0); @@ -292,9 +291,8 @@ library SmardexRouterLib { (address tokenIn, address tokenOut) = data.path.decodeFirstPool(); bool _zeroForOne = tokenIn < tokenOut; - (int256 amount0, int256 amount1) = ISmardexPair(smardexFactory.getPair(tokenIn, tokenOut)).swap( - to, _zeroForOne, amountIn.toInt256(), abi.encode(data) - ); + (int256 amount0, int256 amount1) = ISmardexPair(smardexFactory.getPair(tokenIn, tokenOut)) + .swap(to, _zeroForOne, amountIn.toInt256(), abi.encode(data)); amountOut_ = (_zeroForOne ? -amount1 : -amount0).toUint256(); } diff --git a/src/libraries/usdn/UsdnProtocolRouterLib.sol b/src/libraries/usdn/UsdnProtocolRouterLib.sol index 9de5f4c0..cc4682d5 100644 --- a/src/libraries/usdn/UsdnProtocolRouterLib.sol +++ b/src/libraries/usdn/UsdnProtocolRouterLib.sol @@ -31,7 +31,8 @@ library UsdnProtocolRouterLib { modifier usePayment(IPaymentLibTypes.PaymentType payment, IPaymentLibTypes.PaymentAction action) { if ( payment == IPaymentLibTypes.PaymentType.None - || (action == IPaymentLibTypes.PaymentAction.Withdrawal && payment == IPaymentLibTypes.PaymentType.Permit2) + || (action == IPaymentLibTypes.PaymentAction.Withdrawal + && payment == IPaymentLibTypes.PaymentType.Permit2) ) { revert IUsdnProtocolRouterErrors.UsdnProtocolRouterInvalidPayment(); } @@ -59,8 +60,8 @@ library UsdnProtocolRouterLib { if (data.amount == Constants.CONTRACT_BALANCE) { data.amount = protocolAsset.balanceOf(address(this)); } - success_ = usdnProtocol.initiateDeposit{ value: data.ethAmount }( - // cast is made here to allow the {CONTRACT_BALANCE} value + success_ = usdnProtocol.initiateDeposit{ value: data.ethAmount }( // cast is made here to allow the + // {CONTRACT_BALANCE} value data.amount.toUint128(), data.sharesOutMin, data.to, @@ -88,8 +89,9 @@ library UsdnProtocolRouterLib { IUsdnProtocolTypes.PreviousActionsData memory previousActionsData, uint256 ethAmount ) external returns (bool success_) { - success_ = - usdnProtocol.validateDeposit{ value: ethAmount }(payable(validator), depositPriceData, previousActionsData); + success_ = usdnProtocol.validateDeposit{ value: ethAmount }( + payable(validator), depositPriceData, previousActionsData + ); } /** diff --git a/src/modules/Odos.sol b/src/modules/Enso.sol similarity index 98% rename from src/modules/Odos.sol rename to src/modules/Enso.sol index 67c6a247..bb0af1c4 100644 --- a/src/modules/Odos.sol +++ b/src/modules/Enso.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.26; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -abstract contract Odos { +abstract contract Enso { using SafeERC20 for IERC20; /// @notice The address of the Odos SOR router. diff --git a/src/utils/Payment.sol b/src/utils/Payment.sol index 6f0ff578..c580cd45 100644 --- a/src/utils/Payment.sol +++ b/src/utils/Payment.sol @@ -23,9 +23,7 @@ library Payment { * @param recipient The recipient of the transfer * @param amount The amount to transfer */ - function pay(IAllowanceTransfer permit2, address token, address payer, address recipient, uint256 amount) - external - { + function pay(IAllowanceTransfer permit2, address token, address payer, address recipient, uint256 amount) external { if (payer == address(this)) { if (token == Constants.ETH) { recipient.safeTransferETH(amount); diff --git a/test/integration/ExecuteOdos.t.sol b/test/integration/ExecuteOdos.t.sol index 830991ea..c488aa67 100644 --- a/test/integration/ExecuteOdos.t.sol +++ b/test/integration/ExecuteOdos.t.sol @@ -8,7 +8,7 @@ import { UniversalRouterBaseFixture } from "./utils/Fixtures.sol"; import { IOdosRouterV2 } from "./utils/IOdosRouterV2.sol"; import { Commands } from "../../src/libraries/Commands.sol"; -import { Odos } from "../../src/modules/Odos.sol"; +import { Enso } from "../../src/modules/Enso.sol"; /** * @custom:feature Test the `ODOS` command @@ -115,7 +115,7 @@ contract TestForkOdos is UniversalRouterBaseFixture { bytes[] memory inputs = new bytes[](1); inputs[0] = abi.encode(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, 0, data); - vm.expectRevert(Odos.OdosSwapFailed.selector); + vm.expectRevert(Enso.OdosSwapFailed.selector); router.execute(commands, inputs); } } diff --git a/test/integration/ExecuteSmardexRemoveLiquidity.t.sol b/test/integration/ExecuteSmardexRemoveLiquidity.t.sol index 2364a950..f14b2f5a 100644 --- a/test/integration/ExecuteSmardexRemoveLiquidity.t.sol +++ b/test/integration/ExecuteSmardexRemoveLiquidity.t.sol @@ -20,11 +20,7 @@ contract TestForkUniversalRouterSmardexRemoveLiquidity is UniversalRouterBaseFix uint256 constant BASE_AMOUNT = 1000 ether; ISmardexRouter.RemoveLiquidityParams internal _removeParams = ISmardexRouter.RemoveLiquidityParams({ - tokenA: WETH, - tokenB: WSTETH, - liquidity: 0, - amountAMin: 0, - amountBMin: 0 + tokenA: WETH, tokenB: WSTETH, liquidity: 0, amountAMin: 0, amountBMin: 0 }); function setUp() external { diff --git a/test/integration/ExecuteUsdnInitiateActionsPermit.t.sol b/test/integration/ExecuteUsdnInitiateActionsPermit.t.sol index 0b0a1057..240b66a5 100644 --- a/test/integration/ExecuteUsdnInitiateActionsPermit.t.sol +++ b/test/integration/ExecuteUsdnInitiateActionsPermit.t.sol @@ -256,8 +256,9 @@ contract TestForkUniversalRouterUsdnInitiateActionsPermit is UniversalRouterBase _getDigest(sigUser1, address(router), usdnTokensToTransfer, 0, type(uint256).max, usdn.DOMAIN_SEPARATOR()) ); - bytes memory commands = - abi.encodePacked(uint8(Commands.PERMIT), uint8(Commands.TRANSFER_FROM), uint8(Commands.INITIATE_WITHDRAWAL)); + bytes memory commands = abi.encodePacked( + uint8(Commands.PERMIT), uint8(Commands.TRANSFER_FROM), uint8(Commands.INITIATE_WITHDRAWAL) + ); bytes[] memory inputs = new bytes[](3); inputs[0] = diff --git a/test/integration/UsdnProtocolInitiateClosePosition.t.sol b/test/integration/UsdnProtocolInitiateClosePosition.t.sol index 2f27f378..1ce942f3 100644 --- a/test/integration/UsdnProtocolInitiateClosePosition.t.sol +++ b/test/integration/UsdnProtocolInitiateClosePosition.t.sol @@ -59,7 +59,9 @@ contract TestForkUniversalRouterInitiateClose is UniversalRouterBaseFixture, Sig protocol.validateOpenPosition{ value: oracleMiddleware.validationCost(data, IUsdnProtocolTypes.ProtocolAction.ValidateOpenPosition) - }(payable(user), data, EMPTY_PREVIOUS_DATA); + }( + payable(user), data, EMPTY_PREVIOUS_DATA + ); vm.stopPrank(); @@ -128,19 +130,19 @@ contract TestForkUniversalRouterInitiateClose is UniversalRouterBaseFixture, Sig bytes memory commands = abi.encodePacked(uint8(Commands.INITIATE_CLOSE)); bytes[] memory inputs = new bytes[](1); - IUsdnProtocolRouterTypes.InitiateClosePositionData memory closeData = IUsdnProtocolRouterTypes - .InitiateClosePositionData( - _posId, - _delegation.amountToClose, - _delegation.userMinPrice, - _delegation.to, - USER_1, - _delegation.deadline, - "", - EMPTY_PREVIOUS_DATA, - _delegationSignature, - _securityDeposit - ); + IUsdnProtocolRouterTypes.InitiateClosePositionData memory closeData = + IUsdnProtocolRouterTypes.InitiateClosePositionData( + _posId, + _delegation.amountToClose, + _delegation.userMinPrice, + _delegation.to, + USER_1, + _delegation.deadline, + "", + EMPTY_PREVIOUS_DATA, + _delegationSignature, + _securityDeposit + ); inputs[0] = abi.encode(closeData); diff --git a/test/integration/UsdnProtocolLiquidate.t.sol b/test/integration/UsdnProtocolLiquidate.t.sol index 3c7d56fb..fe3d81dc 100644 --- a/test/integration/UsdnProtocolLiquidate.t.sol +++ b/test/integration/UsdnProtocolLiquidate.t.sol @@ -51,7 +51,9 @@ contract TestForkUniversalRouterLiquidate is UniversalRouterBaseFixture { protocol.validateOpenPosition{ value: oracleMiddleware.validationCost(actionData, ProtocolAction.ValidateOpenPosition) - }(payable(address(this)), actionData, EMPTY_PREVIOUS_DATA); + }( + payable(address(this)), actionData, EMPTY_PREVIOUS_DATA + ); } /** diff --git a/test/integration/UsdnProtocolTransferPositionOwnership.t.sol b/test/integration/UsdnProtocolTransferPositionOwnership.t.sol index 36da188b..07d8a159 100644 --- a/test/integration/UsdnProtocolTransferPositionOwnership.t.sol +++ b/test/integration/UsdnProtocolTransferPositionOwnership.t.sol @@ -51,7 +51,9 @@ contract TestForkUniversalRouterTransferPositionOwnership is UniversalRouterBase (,,,, bytes memory data) = getHermesApiSignature(PYTH_ETH_USD, ts1 + oracleMiddleware.getValidationDelay()); protocol.validateOpenPosition{ value: oracleMiddleware.validationCost(data, IUsdnProtocolTypes.ProtocolAction.ValidateOpenPosition) - }(payable(this), data, EMPTY_PREVIOUS_DATA); + }( + payable(this), data, EMPTY_PREVIOUS_DATA + ); (_pos,) = protocol.getLongPosition(_posId); assertEq(_pos.user, user, "position should be owned by the user"); diff --git a/test/integration/UsdnProtocolValidateClosePosition.t.sol b/test/integration/UsdnProtocolValidateClosePosition.t.sol index 6b690003..1d8e87cb 100644 --- a/test/integration/UsdnProtocolValidateClosePosition.t.sol +++ b/test/integration/UsdnProtocolValidateClosePosition.t.sol @@ -38,7 +38,9 @@ contract TestForkUniversalRouterValidateClosePosition is UniversalRouterBaseFixt (,,,, bytes memory data) = getHermesApiSignature(PYTH_ETH_USD, ts1 + oracleMiddleware.getValidationDelay()); protocol.validateOpenPosition{ value: oracleMiddleware.validationCost(data, IUsdnProtocolTypes.ProtocolAction.ValidateOpenPosition) - }(payable(address(this)), data, EMPTY_PREVIOUS_DATA); + }( + payable(address(this)), data, EMPTY_PREVIOUS_DATA + ); protocol.initiateClosePosition{ value: _securityDeposit }( posId, OPEN_POSITION_AMOUNT, diff --git a/test/integration/utils/Fixtures.sol b/test/integration/utils/Fixtures.sol index b099f5b4..b3d5d50c 100644 --- a/test/integration/utils/Fixtures.sol +++ b/test/integration/utils/Fixtures.sol @@ -4,11 +4,13 @@ pragma solidity 0.8.26; import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { Wusdn } from "@smardex-usdn-contracts-1/src/Usdn/Wusdn.sol"; -import { UsdnProtocolUtilsLibrary as Utils } from - "@smardex-usdn-contracts-1/src/UsdnProtocol/libraries/UsdnProtocolUtilsLibrary.sol"; +import { + UsdnProtocolUtilsLibrary as Utils +} from "@smardex-usdn-contracts-1/src/UsdnProtocol/libraries/UsdnProtocolUtilsLibrary.sol"; import { IUsdnProtocol } from "@smardex-usdn-contracts-1/src/interfaces/UsdnProtocol/IUsdnProtocol.sol"; -import { UsdnProtocolBaseIntegrationFixture } from - "@smardex-usdn-contracts-1/test/integration/UsdnProtocol/utils/Fixtures.sol"; +import { + UsdnProtocolBaseIntegrationFixture +} from "@smardex-usdn-contracts-1/test/integration/UsdnProtocol/utils/Fixtures.sol"; import { DEPLOYER, WETH, WSTETH } from "@smardex-usdn-contracts-1/test/utils/Constants.sol"; import { IAllowanceTransfer } from "permit2/src/interfaces/IAllowanceTransfer.sol"; import { PermitSignature } from "permit2/test/utils/PermitSignature.sol"; diff --git a/test/integration/utils/SigUtils.sol b/test/integration/utils/SigUtils.sol index 087d8095..d9095933 100644 --- a/test/integration/utils/SigUtils.sol +++ b/test/integration/utils/SigUtils.sol @@ -4,8 +4,9 @@ pragma solidity 0.8.26; import { Test } from "forge-std/Test.sol"; import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import { UsdnProtocolConstantsLibrary as Constants } from - "@smardex-usdn-contracts-1/src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; +import { + UsdnProtocolConstantsLibrary as Constants +} from "@smardex-usdn-contracts-1/src/UsdnProtocol/libraries/UsdnProtocolConstantsLibrary.sol"; contract SigUtils is Test { /** From cd851c1f9eb842ebeb449e8ff84f8d859bd4aeda Mon Sep 17 00:00:00 2001 From: samooyo Date: Thu, 13 Nov 2025 14:11:54 +0100 Subject: [PATCH 2/9] refactor: rename command --- README.md | 3 +- script/01_Deploy.s.sol | 4 +-- src/UniversalRouter.sol | 2 +- src/base/Dispatcher.sol | 4 +-- src/base/RouterImmutables.sol | 4 +-- src/libraries/Commands.sol | 2 +- src/modules/Enso.sol | 50 ++++++++++++++--------------- test/integration/ExecuteOdos.t.sol | 8 ++--- test/integration/utils/Fixtures.sol | 2 +- test/units/EmptyWusdn.t.sol | 2 +- 10 files changed, 41 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index debcb19c..752301b4 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Each command is a `bytes1` containing the following 8 bits: ├──────┼───────────────────────────────┤ │ 0x06 │ PAY_PORTION │ |──────┼───────────────────────────────| - │ 0x07 │ ODOS │ + │ 0x07 │ ENSO │ ├──────┼───────────────────────────────┤ │ 0x08-│ ------- │ │ 0x0f │ │ @@ -197,6 +197,7 @@ Some commands explained : - `VALIDATE_WITHDRAWAL` : Validate a withdrawal in the protocol. - `VALIDATE_OPEN` : Validate an open in the protocol. - `VALIDATE_CLOSE` : Validate a close in the protocol. +- `ENSO` : Swap tokens through the Enso router. ## Installation diff --git a/script/01_Deploy.s.sol b/script/01_Deploy.s.sol index da5bba07..d79af3d9 100644 --- a/script/01_Deploy.s.sol +++ b/script/01_Deploy.s.sol @@ -19,7 +19,7 @@ contract Deploy is Script { address constant SMARDEX_FACTORY = 0xB878DC600550367e14220d4916Ff678fB284214F; bytes32 constant PAIR_INIT_HASH = 0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f; bytes32 constant POOL_INIT_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54; - address constant ODOS_SOR_ROUTER = 0xCf5540fFFCdC3d510B18bFcA6d2b9987b0772559; + address constant ENSO_V2_ROUTER = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf; function run(address wusdnTokenAddress, address usdnProtocolAddress) external @@ -39,7 +39,7 @@ contract Deploy is Script { wstEth: vm.envOr("WSTETH", WSTETH), wusdn: IWusdn(wusdnTokenAddress), smardexFactory: ISmardexFactory(vm.envOr("SMARDEX_FACTORY", SMARDEX_FACTORY)), - odosSorRouter: vm.envOr("ODOS_SOR_ROUTER", ODOS_SOR_ROUTER) + ensoV2Router: vm.envOr("ENSO_V2_ROUTER", ENSO_V2_ROUTER) }) ); diff --git a/src/UniversalRouter.sol b/src/UniversalRouter.sol index fbb5f84c..2dce395d 100644 --- a/src/UniversalRouter.sol +++ b/src/UniversalRouter.sol @@ -40,7 +40,7 @@ contract UniversalRouter is IUniversalRouter, Dispatcher { UsdnProtocolImmutables(UsdnProtocolParameters(params.usdnProtocol, params.wusdn)) LidoImmutables(params.wstEth) SmardexRouter(params.smardexFactory) - Enso(params.odosSorRouter) + Enso(params.ensoV2Router) { } /// @inheritdoc IUniversalRouter diff --git a/src/base/Dispatcher.sol b/src/base/Dispatcher.sol index 883a599c..21b4c38c 100644 --- a/src/base/Dispatcher.sol +++ b/src/base/Dispatcher.sol @@ -152,7 +152,7 @@ abstract contract Dispatcher is bips := calldataload(add(inputs.offset, 0x40)) } Payments.payPortion(token, map(recipient), bips); - } else if (command == Commands.ODOS) { + } else if (command == Commands.ENSO) { address tokenIn; uint256 ethAmount; assembly { @@ -160,7 +160,7 @@ abstract contract Dispatcher is ethAmount := calldataload(add(inputs.offset, 0x20)) } bytes calldata data = inputs.toBytes(2); - swapOdos(tokenIn, ethAmount, data); + swapViaEnso(tokenIn, ethAmount, data); } else { revert InvalidCommandType(command); } diff --git a/src/base/RouterImmutables.sol b/src/base/RouterImmutables.sol index 1d889e42..55ccecb3 100644 --- a/src/base/RouterImmutables.sol +++ b/src/base/RouterImmutables.sol @@ -19,7 +19,7 @@ import { ISmardexFactory } from "../interfaces/smardex/ISmardexFactory.sol"; * @param wusdn The wrapped USDN address. If the address is set to the zero address, * the wrap and unwrap commands are disabled * @param smardexFactory The smardex factory - * @param odosSorRouter The odos sor router address + * @param ensoV2Router The Enso v2 router address */ struct RouterParameters { address permit2; @@ -32,5 +32,5 @@ struct RouterParameters { address wstEth; IWusdn wusdn; ISmardexFactory smardexFactory; - address odosSorRouter; + address ensoV2Router; } diff --git a/src/libraries/Commands.sol b/src/libraries/Commands.sol index 8eaa8165..196d8a66 100644 --- a/src/libraries/Commands.sol +++ b/src/libraries/Commands.sol @@ -17,7 +17,7 @@ library Commands { uint256 constant SWEEP = 0x04; uint256 constant TRANSFER = 0x05; uint256 constant PAY_PORTION = 0x06; - uint256 constant ODOS = 0x07; + uint256 constant ENSO = 0x07; // COMMAND_PLACEHOLDER from 0x08 to 0x0f (all unused) // the commands are executed in nested if blocks to minimize gas consumption diff --git a/src/modules/Enso.sol b/src/modules/Enso.sol index bb0af1c4..43882afc 100644 --- a/src/modules/Enso.sol +++ b/src/modules/Enso.sol @@ -7,38 +7,38 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s abstract contract Enso { using SafeERC20 for IERC20; - /// @notice The address of the Odos SOR router. - address immutable ODOS_SOR_ROUTER; + /// @notice The address of the Enso v2 router. + address immutable ENSO_V2_ROUTER; - /// @notice Reverts when the swap via Odos fails. - error OdosSwapFailed(); + /// @notice Reverts when the swap via Enso fails. + error EnsoSwapFailed(); - /// @param odosSorRouter The address of the Odos SOR router. - constructor(address odosSorRouter) { - ODOS_SOR_ROUTER = odosSorRouter; + /// @param ensoV2Router The address of the Enso v2 router. + constructor(address ensoV2Router) { + ENSO_V2_ROUTER = ensoV2Router; } /** - * @notice Swaps tokens via the Odos SOR router. + * @notice Swaps tokens via the Enso router. * @param tokenIn The address of the input token. - * @param data The data to send to the Odos SOR router. + * @param data The data to send to the Enso router. */ - function swapOdos(address tokenIn, uint256 ethAmount, bytes memory data) internal { - if (ethAmount == 0) { - // forceApprove is not needed because the allowance is reset to 0 after each swap - IERC20(tokenIn).approve(ODOS_SOR_ROUTER, type(uint256).max); - - (bool success,) = ODOS_SOR_ROUTER.call(data); - if (!success) { - revert OdosSwapFailed(); - } - - IERC20(tokenIn).approve(ODOS_SOR_ROUTER, 0); - } else { - (bool success,) = ODOS_SOR_ROUTER.call{ value: ethAmount }(data); - if (!success) { - revert OdosSwapFailed(); - } + function swapViaEnso(address tokenIn, uint256 ethAmount, bytes memory data) internal { + // if (ethAmount == 0) { + // // forceApprove is not needed because the allowance is reset to 0 after each swap + // IERC20(tokenIn).approve(ENSO_V2_ROUTER, type(uint256).max); + + (bool success,) = ENSO_V2_ROUTER.call{ value: msg.value }(data); + if (!success) { + revert EnsoSwapFailed(); } + + // IERC20(tokenIn).approve(ENSO_V2_ROUTER, 0); + // } else { + // (bool success,) = ENSO_V2_ROUTER.call{ value: ethAmount }(data); + // if (!success) { + // revert OdosSwapFailed(); + // } + // } } } diff --git a/test/integration/ExecuteOdos.t.sol b/test/integration/ExecuteOdos.t.sol index c488aa67..8fc77e55 100644 --- a/test/integration/ExecuteOdos.t.sol +++ b/test/integration/ExecuteOdos.t.sol @@ -50,7 +50,7 @@ contract TestForkOdos is UniversalRouterBaseFixture { outputReceiver: address(router) }); - bytes memory commands = abi.encodePacked(uint8(Commands.ODOS)); + bytes memory commands = abi.encodePacked(uint8(Commands.ENSO)); bytes[] memory inputs = new bytes[](1); bytes memory swapData = abi.encodeWithSelector(IOdosRouterV2.swap.selector, params, SWAP_PATH, SWAP_EXECUTOR, SWAP_REFERRAL_CODE); @@ -87,7 +87,7 @@ contract TestForkOdos is UniversalRouterBaseFixture { outputReceiver: address(router) }); - bytes memory commands = abi.encodePacked(uint8(Commands.ODOS)); + bytes memory commands = abi.encodePacked(uint8(Commands.ENSO)); bytes[] memory inputs = new bytes[](1); bytes memory swapData = abi.encodeWithSelector(IOdosRouterV2.swap.selector, params, SWAP_PATH, SWAP_EXECUTOR, SWAP_REFERRAL_CODE); @@ -111,11 +111,11 @@ contract TestForkOdos is UniversalRouterBaseFixture { function test_Fork_RevertWhen_SwapFails() external { // function selector of the swap function of the Odos router with random data bytes memory data = hex"83bd37f900"; - bytes memory commands = abi.encodePacked(uint8(Commands.ODOS)); + bytes memory commands = abi.encodePacked(uint8(Commands.ENSO)); bytes[] memory inputs = new bytes[](1); inputs[0] = abi.encode(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, 0, data); - vm.expectRevert(Enso.OdosSwapFailed.selector); + vm.expectRevert(Enso.EnsoSwapFailed.selector); router.execute(commands, inputs); } } diff --git a/test/integration/utils/Fixtures.sol b/test/integration/utils/Fixtures.sol index b3d5d50c..970139d1 100644 --- a/test/integration/utils/Fixtures.sol +++ b/test/integration/utils/Fixtures.sol @@ -65,7 +65,7 @@ contract UniversalRouterBaseFixture is UsdnProtocolBaseIntegrationFixture, Permi wstEth: WSTETH, wusdn: wusdn, smardexFactory: ISmardexFactory(0xB878DC600550367e14220d4916Ff678fB284214F), - odosSorRouter: 0xCf5540fFFCdC3d510B18bFcA6d2b9987b0772559 + ensoV2Router: 0xCf5540fFFCdC3d510B18bFcA6d2b9987b0772559 }); vm.prank(DEPLOYER); diff --git a/test/units/EmptyWusdn.t.sol b/test/units/EmptyWusdn.t.sol index 9168a48c..52f6174b 100644 --- a/test/units/EmptyWusdn.t.sol +++ b/test/units/EmptyWusdn.t.sol @@ -34,7 +34,7 @@ contract TestEmptyWusdn is Test { wstEth: address(mockWstEth), wusdn: IWusdn(address(0)), smardexFactory: ISmardexFactory(address(0)), - odosSorRouter: address(0) + ensoV2Router: address(0) }); router = new UniversalRouter(params); From b149d5dcf027bd021518ca9d9d68dbb05cdd10b3 Mon Sep 17 00:00:00 2001 From: samooyo Date: Thu, 13 Nov 2025 15:41:41 +0100 Subject: [PATCH 3/9] test(enso): simple swap --- foundry.toml | 2 ++ soldeer.lock | 7 +++++ src/modules/Enso.sol | 4 +-- test/integration/ExecuteOdos.t.sol | 43 ++++++++++++++--------------- test/integration/utils/Fixtures.sol | 2 +- 5 files changed, 32 insertions(+), 26 deletions(-) diff --git a/foundry.toml b/foundry.toml index 993b0099..110f7523 100644 --- a/foundry.toml +++ b/foundry.toml @@ -24,6 +24,7 @@ remappings = [ "forge-std/=dependencies/forge-std-1.9.2/src/", "openzeppelin-contracts/=dependencies/openzeppelin-contracts-4.7.0/", "permit2/=dependencies/@uniswap-permit2-1.0.0/", + "solady-0.1/=dependencies/solady-0.1.26/", "solmate/=dependencies/solmate-6.7.0/", "usdn-contracts/=dependencies/@smardex-usdn-contracts-1.0.0/", ] @@ -133,6 +134,7 @@ forge-std = "1.9.2" solmate = "6.7.0" "@uniswap-v3-core" = "1.0.0" "openzeppelin-contracts" = { version = "4.7.0", url = "https://github.com/OpenZeppelin/openzeppelin-contracts/archive/refs/tags/v4.7.0.zip" } +solady = "0.1.26" [soldeer] remappings_location = "config" diff --git a/soldeer.lock b/soldeer.lock index a4161ab5..b2d1381f 100644 --- a/soldeer.lock +++ b/soldeer.lock @@ -60,6 +60,13 @@ url = "https://github.com/OpenZeppelin/openzeppelin-contracts/archive/refs/tags/ checksum = "7a88a52b70872f3e428e595a03aed77c3323b118338d7256ce57dfc99eeab9e9" integrity = "40bc5e663f19cf42c5ab0ddfc049ca44d58a9f1236cef417c370af79fa8ac3a7" +[[dependencies]] +name = "solady" +version = "0.1.26" +url = "https://soldeer-revisions.s3.amazonaws.com/solady/0_1_26_25-08-2025_15:30:06_solady.zip" +checksum = "9872ac7cfd32c1eba32800508a1325c49f4a4aa8c6f670454db91971a583e26b" +integrity = "5da4b5ca9cbad98812a4b75ad528ff34c72a0b84433204be6d1420c81de1d6ff" + [[dependencies]] name = "solmate" version = "6.7.0" diff --git a/src/modules/Enso.sol b/src/modules/Enso.sol index 43882afc..b7eeafc6 100644 --- a/src/modules/Enso.sol +++ b/src/modules/Enso.sol @@ -25,8 +25,8 @@ abstract contract Enso { */ function swapViaEnso(address tokenIn, uint256 ethAmount, bytes memory data) internal { // if (ethAmount == 0) { - // // forceApprove is not needed because the allowance is reset to 0 after each swap - // IERC20(tokenIn).approve(ENSO_V2_ROUTER, type(uint256).max); + // forceApprove is not needed because the allowance is reset to 0 after each swap + IERC20(tokenIn).approve(ENSO_V2_ROUTER, type(uint256).max); (bool success,) = ENSO_V2_ROUTER.call{ value: msg.value }(data); if (!success) { diff --git a/test/integration/ExecuteOdos.t.sol b/test/integration/ExecuteOdos.t.sol index 8fc77e55..dda5ba00 100644 --- a/test/integration/ExecuteOdos.t.sol +++ b/test/integration/ExecuteOdos.t.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.26; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { LibBytes } from "solady-0.1/src/utils/LibBytes.sol"; import { WETH } from "./utils/Constants.sol"; import { UniversalRouterBaseFixture } from "./utils/Fixtures.sol"; @@ -21,40 +22,35 @@ contract TestForkOdos is UniversalRouterBaseFixture { function setUp() external { SetUpParams memory params = DEFAULT_PARAMS; - params.forkBlock = 22_417_713; + params.forkBlock = 23_790_517; _setUp(params); deal(address(wstETH), address(this), SWAP_AMOUNT); } /** - * @notice Test the token swaps via Odos router - * @custom:given A data to send to Odos create via their API + * @notice Test the token swaps via Enso router + * @custom:given A data to send to Enso create via their API * @custom:when The `execute` function is called for `ODOS` command - * @custom:then The `ODOS` command should swap the tokens + * @custom:then The `ENSO` command should swap the tokens */ - function test_forkOdosSwapToken() external { + function test_forkEnsoSwapToken() external { wstETH.transfer(address(router), SWAP_AMOUNT); uint256 balanceWethBefore = IERC20(WETH).balanceOf(address(router)); // path to swap wstETH to WETH - bytes memory SWAP_PATH = - hex"010206006801010102030405ff00000000000000000000000000000000000000c4ce391d82d164c166df9c8336ddf84206b2f8127f39c581f595b53c5cb19bd0b3f8da6c935e2ca0c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2775f661b0bd1739349b9a2a3ef60be277c5d2d290fe906e030a44ef24ca8c7dc7b7c53a6c4f00ce9"; + bytes memory SWAP_DATA_1 = + hex"b94c3609000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca00000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000090495352c9fad7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a52b18b54dc360a293dcb846e18f523383d159e66f8bbd5ecc46961ccd6f5018f4000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000009095ea7b3010001ffffffffff7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0de0e9a3e0101ffffffffff017f39c581f595b53c5cb19bd0b3f8da6c935e2ca0095ea7b3010201ffffffffffae7ab96520de3a18e5e111b5eaab095312d7fe84346387bf4100000000000001bedfac7488dccaafdd66d1d7d56349780fe0477e0303040501060708898affffffffffffffffffffffffffffffffffffffffffffd0e30db00301ffffffffffffc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a9059cbb010b01ffffffffffc02aaa39b223fe8d0a0e5c4f27ead9083c756cc26e7a43a301010cffffffff017e7d64d987cab6eed08a191c4c2459daf2f8ed0b241c59120101ffffffffffff7e7d64d987cab6eed08a191c4c2459daf2f8ed0b000000000000000000000000000000000000000000000000000000000000000d00000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000000000000000000000000000000000000000000200000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000bedfac7488dccaafdd66d1d7d56349780fe0477e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000cf5540fffcdc3d510b18bfca6d2b9987b07725590000000000000000000000000000000000000000000000000000000000000020000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe840000000000000000000000000000000000000000000000000000000000000020000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000010e8b09a262d149100000000000000000000000000000000000000000000000000000000000000200000000000000000000000004fe93ebc4ce6ae4f81601cc7ce7139023919e0030000000000000000000000000000000000000000000000000000000000000020000000000000000000000000ab27db9e0105af3d9717b0ccef11e2cc65515609000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002043b635ce4000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe8400000000000000000000000000000000000000000000000010eb22053fadd33a000000000000000000000000365084b05fa7d5028346bd21d842ed0601bab5b8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010e8c2c7fbbbc3000000000000000000000000000000000000000000000000000f37e280c8f5c900000000000000000000000000bedfac7488dccaafdd66d1d7d56349780fe0477e0000000000000000000000000000000000000000000000000000000000000140000000000000000000000000365084b05fa7d5028346bd21d842ed0601bab5b800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084010206004c0101026800000102030405000601000201ff000000000000000000c4ce391d82d164c166df9c8336ddf84206b2f8127f39c581f595b53c5cb19bd0b3f8da6c935e2ca0c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2775f661b0bd1739349b9a2a3ef60be277c5d2d290fe906e030a44ef24ca8c7dc7b7c53a6c4f00ce900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000"; + bytes memory SWAP_DATA_2 = + hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000010bd6727f7b6de9900000000000000000000000000000000000000000000000000000000"; - IOdosRouterV2.swapTokenInfo memory params = IOdosRouterV2.swapTokenInfo({ - inputToken: address(wstETH), - inputAmount: SWAP_AMOUNT, - inputReceiver: SWAP_EXECUTOR, - outputToken: WETH, - outputQuote: SWAP_AMOUNT, - outputMin: SWAP_AMOUNT, - outputReceiver: address(router) - }); + // add the router address to the swap data + bytes memory SWAP_DATA = bytes.concat(SWAP_DATA_1, bytes20(uint160(address(router)))); + SWAP_DATA = bytes.concat(SWAP_DATA, SWAP_DATA_2); bytes memory commands = abi.encodePacked(uint8(Commands.ENSO)); bytes[] memory inputs = new bytes[](1); - bytes memory swapData = - abi.encodeWithSelector(IOdosRouterV2.swap.selector, params, SWAP_PATH, SWAP_EXECUTOR, SWAP_REFERRAL_CODE); - inputs[0] = abi.encode(address(wstETH), 0, swapData); + + inputs[0] = abi.encode(address(wstETH), 0, SWAP_DATA); router.execute(commands, inputs); @@ -65,8 +61,8 @@ contract TestForkOdos is UniversalRouterBaseFixture { } /** - * @notice Test the ETH swaps via Odos router - * @custom:given A data to send to Odos create via their API + * @notice Test the ETH swaps via Enso router + * @custom:given A data to send to Enso create via their API * @custom:when The `execute` function is called for `ODOS` command * @custom:then The `ODOS` command should swap the ether */ @@ -103,13 +99,13 @@ contract TestForkOdos is UniversalRouterBaseFixture { } /** - * @notice Test the swap via Odos router + * @notice Test the swap via Enso router * @custom:given A data with the swap function selector and random data * @custom:when The `execute` function is called for `ODOS` command * @custom:then The `ODOS` command should revert with the error `OdosSwapFailed` */ function test_Fork_RevertWhen_SwapFails() external { - // function selector of the swap function of the Odos router with random data + // function selector of the swap function of the Enso router with random data bytes memory data = hex"83bd37f900"; bytes memory commands = abi.encodePacked(uint8(Commands.ENSO)); bytes[] memory inputs = new bytes[](1); @@ -119,3 +115,4 @@ contract TestForkOdos is UniversalRouterBaseFixture { router.execute(commands, inputs); } } + diff --git a/test/integration/utils/Fixtures.sol b/test/integration/utils/Fixtures.sol index 970139d1..6813e19c 100644 --- a/test/integration/utils/Fixtures.sol +++ b/test/integration/utils/Fixtures.sol @@ -65,7 +65,7 @@ contract UniversalRouterBaseFixture is UsdnProtocolBaseIntegrationFixture, Permi wstEth: WSTETH, wusdn: wusdn, smardexFactory: ISmardexFactory(0xB878DC600550367e14220d4916Ff678fB284214F), - ensoV2Router: 0xCf5540fFFCdC3d510B18bFcA6d2b9987b0772559 + ensoV2Router: 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf }); vm.prank(DEPLOYER); From 3c9d40a281d53594908cd58037eae8dab013b4af Mon Sep 17 00:00:00 2001 From: samooyo Date: Thu, 13 Nov 2025 15:52:49 +0100 Subject: [PATCH 4/9] test(enso): eth swaps --- src/modules/Enso.sol | 30 +++++++++++------------ test/integration/ExecuteOdos.t.sol | 38 ++++++++++++------------------ 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/src/modules/Enso.sol b/src/modules/Enso.sol index b7eeafc6..2ae9a972 100644 --- a/src/modules/Enso.sol +++ b/src/modules/Enso.sol @@ -24,21 +24,21 @@ abstract contract Enso { * @param data The data to send to the Enso router. */ function swapViaEnso(address tokenIn, uint256 ethAmount, bytes memory data) internal { - // if (ethAmount == 0) { - // forceApprove is not needed because the allowance is reset to 0 after each swap - IERC20(tokenIn).approve(ENSO_V2_ROUTER, type(uint256).max); - - (bool success,) = ENSO_V2_ROUTER.call{ value: msg.value }(data); - if (!success) { - revert EnsoSwapFailed(); + if (ethAmount == 0) { + // forceApprove is not needed because the allowance is reset to 0 after each swap + IERC20(tokenIn).approve(ENSO_V2_ROUTER, type(uint256).max); + + (bool success,) = ENSO_V2_ROUTER.call{ value: msg.value }(data); + if (!success) { + revert EnsoSwapFailed(); + } + + IERC20(tokenIn).approve(ENSO_V2_ROUTER, 0); + } else { + (bool success,) = ENSO_V2_ROUTER.call{ value: ethAmount }(data); + if (!success) { + revert EnsoSwapFailed(); + } } - - // IERC20(tokenIn).approve(ENSO_V2_ROUTER, 0); - // } else { - // (bool success,) = ENSO_V2_ROUTER.call{ value: ethAmount }(data); - // if (!success) { - // revert OdosSwapFailed(); - // } - // } } } diff --git a/test/integration/ExecuteOdos.t.sol b/test/integration/ExecuteOdos.t.sol index dda5ba00..06eee09a 100644 --- a/test/integration/ExecuteOdos.t.sol +++ b/test/integration/ExecuteOdos.t.sol @@ -6,19 +6,16 @@ import { LibBytes } from "solady-0.1/src/utils/LibBytes.sol"; import { WETH } from "./utils/Constants.sol"; import { UniversalRouterBaseFixture } from "./utils/Fixtures.sol"; -import { IOdosRouterV2 } from "./utils/IOdosRouterV2.sol"; import { Commands } from "../../src/libraries/Commands.sol"; import { Enso } from "../../src/modules/Enso.sol"; /** - * @custom:feature Test the `ODOS` command - * @custom:background A initiated universal router + * @custom:feature Test the `ENSO` command + * @custom:background An initiated universal router */ -contract TestForkOdos is UniversalRouterBaseFixture { +contract TestForkEnso is UniversalRouterBaseFixture { uint256 internal constant SWAP_AMOUNT = 1 ether; - uint32 internal constant SWAP_REFERRAL_CODE = 0; - address internal constant SWAP_EXECUTOR = 0x76edF8C155A1e0D9B2aD11B04d9671CBC25fEE99; function setUp() external { SetUpParams memory params = DEFAULT_PARAMS; @@ -31,7 +28,7 @@ contract TestForkOdos is UniversalRouterBaseFixture { /** * @notice Test the token swaps via Enso router * @custom:given A data to send to Enso create via their API - * @custom:when The `execute` function is called for `ODOS` command + * @custom:when The `execute` function is called for `ENSO` command * @custom:then The `ENSO` command should swap the tokens */ function test_forkEnsoSwapToken() external { @@ -70,24 +67,19 @@ contract TestForkOdos is UniversalRouterBaseFixture { uint256 routerWstethBalanceBefore = wstETH.balanceOf(address(router)); uint256 thisEthBalanceBefore = address(this).balance; // path to swap ETH to wstETH - bytes memory SWAP_PATH = - hex"01020300690101010200ff0000000000000000000000000000000000000000000b1a513ee24972daef112bc777a5610d4325c9e70000000000000000000000000000000000000000"; - - IOdosRouterV2.swapTokenInfo memory params = IOdosRouterV2.swapTokenInfo({ - inputToken: address(0), - inputAmount: SWAP_AMOUNT, - inputReceiver: SWAP_EXECUTOR, - outputToken: address(wstETH), - outputQuote: SWAP_AMOUNT, - outputMin: 1, - outputReceiver: address(router) - }); + bytes memory SWAP_DATA_1 = + hex"b94c3609000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000072495352c9fad7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a56472712c43041694bfbcd8840182a3ac9a1c29186d93644b428eb45b80de77d8000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000004346387bf4300000000000008bedfac7488dccaafdd66d1d7d56349780fe0477e0001010203000405068788ffffffffffffffffffffffffffffffffffffffffff6e7a43a3010809ffffffff087e7d64d987cab6eed08a191c4c2459daf2f8ed0b241c59120108ffffffffffff7e7d64d987cab6eed08a191c4c2459daf2f8ed0b000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000056000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000cf5540fffcdc3d510b18bfca6d2b9987b07725590000000000000000000000000000000000000000000000000000000000000020000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000000200000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000b63bc8afd72d3800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000"; + bytes memory SWAP_DATA_2 = + hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000ab27db9e0105af3d9717b0ccef11e2cc65515609000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002043b635ce400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000365084b05fa7d5028346bd21d842ed0601bab5b80000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca00000000000000000000000000000000000000000000000000b63bc8afd72d3800000000000000000000000000000000000000000000000000b469441eb8dd680000000000000000000000000bedfac7488dccaafdd66d1d7d56349780fe0477e0000000000000000000000000000000000000000000000000000000000000140000000000000000000000000365084b05fa7d5028346bd21d842ed0601bab5b80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008401020600060101026800010102030405ff000000000000000000000000000000c4ce391d82d164c166df9c8336ddf84206b2f812c02aaa39b223fe8d0a0e5c4f27ead9083c756cc27f39c581f595b53c5cb19bd0b3f8da6c935e2ca00fe906e030a44ef24ca8c7dc7b7c53a6c4f00ce9775f661b0bd1739349b9a2a3ef60be277c5d2d2900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000b469441eb8dd68100000000000000000000000000000000000000000000000000000000"; + + // add the router address to the swap data + bytes memory SWAP_DATA = bytes.concat(SWAP_DATA_1, bytes20(uint160(address(router)))); + SWAP_DATA = bytes.concat(SWAP_DATA, SWAP_DATA_2); bytes memory commands = abi.encodePacked(uint8(Commands.ENSO)); bytes[] memory inputs = new bytes[](1); - bytes memory swapData = - abi.encodeWithSelector(IOdosRouterV2.swap.selector, params, SWAP_PATH, SWAP_EXECUTOR, SWAP_REFERRAL_CODE); - inputs[0] = abi.encode(address(0), SWAP_AMOUNT, swapData); + + inputs[0] = abi.encode(address(0), SWAP_AMOUNT, SWAP_DATA); router.execute{ value: SWAP_AMOUNT }(commands, inputs); @@ -106,7 +98,7 @@ contract TestForkOdos is UniversalRouterBaseFixture { */ function test_Fork_RevertWhen_SwapFails() external { // function selector of the swap function of the Enso router with random data - bytes memory data = hex"83bd37f900"; + bytes memory data = hex"b94c3609236423"; bytes memory commands = abi.encodePacked(uint8(Commands.ENSO)); bytes[] memory inputs = new bytes[](1); inputs[0] = abi.encode(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, 0, data); From 2e6f5fc7c79525f647ed5ec22eacaeda30f6a55a Mon Sep 17 00:00:00 2001 From: samooyo Date: Thu, 13 Nov 2025 15:53:28 +0100 Subject: [PATCH 5/9] style: odos to enso --- test/integration/ExecuteOdos.t.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/integration/ExecuteOdos.t.sol b/test/integration/ExecuteOdos.t.sol index 06eee09a..28da5f4c 100644 --- a/test/integration/ExecuteOdos.t.sol +++ b/test/integration/ExecuteOdos.t.sol @@ -60,10 +60,10 @@ contract TestForkEnso is UniversalRouterBaseFixture { /** * @notice Test the ETH swaps via Enso router * @custom:given A data to send to Enso create via their API - * @custom:when The `execute` function is called for `ODOS` command - * @custom:then The `ODOS` command should swap the ether + * @custom:when The `execute` function is called for `ENSO` command + * @custom:then The `ENSO` command should swap the ether */ - function test_forkOdosSwapEth() external { + function test_forkEnsoSwapEth() external { uint256 routerWstethBalanceBefore = wstETH.balanceOf(address(router)); uint256 thisEthBalanceBefore = address(this).balance; // path to swap ETH to wstETH @@ -93,8 +93,8 @@ contract TestForkEnso is UniversalRouterBaseFixture { /** * @notice Test the swap via Enso router * @custom:given A data with the swap function selector and random data - * @custom:when The `execute` function is called for `ODOS` command - * @custom:then The `ODOS` command should revert with the error `OdosSwapFailed` + * @custom:when The `execute` function is called for `ENSO` command + * @custom:then The `ENSO` command should revert with the error `EnsoSwapFailed` */ function test_Fork_RevertWhen_SwapFails() external { // function selector of the swap function of the Enso router with random data From 1baf4f9696936dabaaa9590ebafc9f9e1b5f5144 Mon Sep 17 00:00:00 2001 From: samooyo Date: Thu, 13 Nov 2025 16:17:13 +0100 Subject: [PATCH 6/9] chore: remove odos interface --- test/integration/utils/IOdosRouterV2.sol | 32 ------------------------ 1 file changed, 32 deletions(-) delete mode 100644 test/integration/utils/IOdosRouterV2.sol diff --git a/test/integration/utils/IOdosRouterV2.sol b/test/integration/utils/IOdosRouterV2.sol deleted file mode 100644 index cfbebd86..00000000 --- a/test/integration/utils/IOdosRouterV2.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -/** - * @title Odos token swapping functionality. - * @notice Functions for swapping tokens via Odos. - */ -interface IOdosRouterV2 { - /// @dev Contains all information needed to describe the input and output for a swap - struct swapTokenInfo { - address inputToken; - uint256 inputAmount; - address inputReceiver; - address outputToken; - uint256 outputQuote; - uint256 outputMin; - address outputReceiver; - } - - /** - * @notice Externally facing interface for swapping two tokens - * @param tokenInfo All information about the tokens being swapped - * @param pathDefinition Encoded path definition for executor - * @param executor Address of contract that will execute the path - * @param referralCode referral code to specify the source of the swap - * @return amountOut The amount of the output token received - */ - function swap(swapTokenInfo memory tokenInfo, bytes calldata pathDefinition, address executor, uint32 referralCode) - external - payable - returns (uint256 amountOut); -} From 9dcaf5ecdc50ebe96d80eb15475a2f61109418ae Mon Sep 17 00:00:00 2001 From: samooyo Date: Thu, 13 Nov 2025 16:24:55 +0100 Subject: [PATCH 7/9] chore: typo --- src/libraries/usdn/UsdnProtocolRouterLib.sol | 3 +-- src/modules/Enso.sol | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libraries/usdn/UsdnProtocolRouterLib.sol b/src/libraries/usdn/UsdnProtocolRouterLib.sol index cc4682d5..332e1498 100644 --- a/src/libraries/usdn/UsdnProtocolRouterLib.sol +++ b/src/libraries/usdn/UsdnProtocolRouterLib.sol @@ -60,8 +60,7 @@ library UsdnProtocolRouterLib { if (data.amount == Constants.CONTRACT_BALANCE) { data.amount = protocolAsset.balanceOf(address(this)); } - success_ = usdnProtocol.initiateDeposit{ value: data.ethAmount }( // cast is made here to allow the - // {CONTRACT_BALANCE} value + success_ = usdnProtocol.initiateDeposit{ value: data.ethAmount }( data.amount.toUint128(), data.sharesOutMin, data.to, diff --git a/src/modules/Enso.sol b/src/modules/Enso.sol index 2ae9a972..2dc7c45b 100644 --- a/src/modules/Enso.sol +++ b/src/modules/Enso.sol @@ -28,7 +28,7 @@ abstract contract Enso { // forceApprove is not needed because the allowance is reset to 0 after each swap IERC20(tokenIn).approve(ENSO_V2_ROUTER, type(uint256).max); - (bool success,) = ENSO_V2_ROUTER.call{ value: msg.value }(data); + (bool success,) = ENSO_V2_ROUTER.call(data); if (!success) { revert EnsoSwapFailed(); } From 769f9f911f5eb1481a1c04e87acd422b10dc6822 Mon Sep 17 00:00:00 2001 From: samooyo Date: Thu, 13 Nov 2025 16:30:46 +0100 Subject: [PATCH 8/9] chore: rename test file --- test/integration/{ExecuteOdos.t.sol => ExecuteEnso.t.sol} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/integration/{ExecuteOdos.t.sol => ExecuteEnso.t.sol} (100%) diff --git a/test/integration/ExecuteOdos.t.sol b/test/integration/ExecuteEnso.t.sol similarity index 100% rename from test/integration/ExecuteOdos.t.sol rename to test/integration/ExecuteEnso.t.sol From 236b1ab5768cd162260cf28739da09f3c3efcf85 Mon Sep 17 00:00:00 2001 From: samooyo Date: Thu, 13 Nov 2025 17:09:56 +0100 Subject: [PATCH 9/9] chore: typo --- test/integration/ExecuteEnso.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/ExecuteEnso.t.sol b/test/integration/ExecuteEnso.t.sol index 28da5f4c..f305e8be 100644 --- a/test/integration/ExecuteEnso.t.sol +++ b/test/integration/ExecuteEnso.t.sol @@ -27,7 +27,7 @@ contract TestForkEnso is UniversalRouterBaseFixture { /** * @notice Test the token swaps via Enso router - * @custom:given A data to send to Enso create via their API + * @custom:given Some data to send to Enso created via their API * @custom:when The `execute` function is called for `ENSO` command * @custom:then The `ENSO` command should swap the tokens */