Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Each command is a `bytes1` containing the following 8 bits:
├──────┼───────────────────────────────┤
│ 0x06 │ PAY_PORTION │
|──────┼───────────────────────────────|
│ 0x07 │ ODOS │
│ 0x07 │ ENSO │
├──────┼───────────────────────────────┤
│ 0x08-│ ------- │
│ 0x0f │ │
Expand Down Expand Up @@ -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

Expand Down
30 changes: 15 additions & 15 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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/",
]
Expand Down Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions script/01_Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
})
);

Expand Down
7 changes: 7 additions & 0 deletions soldeer.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
13 changes: 7 additions & 6 deletions src/UniversalRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
pragma solidity 0.8.26;

import {
PaymentsImmutables, PaymentsParameters
PaymentsImmutables,
PaymentsParameters
} from "@uniswap/universal-router/contracts/modules/PaymentsImmutables.sol";
import {
UniswapImmutables,
Expand All @@ -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";
Expand All @@ -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.ensoV2Router)
{ }

/// @inheritdoc IUniversalRouter
Expand Down
41 changes: 24 additions & 17 deletions src/base/Dispatcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -36,7 +36,7 @@ abstract contract Dispatcher is
LockAndMap,
UsdnProtocolRouter,
LidoImmutables,
Odos
Enso
{
using BytesLib for bytes;

Expand Down Expand Up @@ -152,15 +152,15 @@ 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 {
tokenIn := calldataload(inputs.offset)
ethAmount := calldataload(add(inputs.offset, 0x20))
}
bytes calldata data = inputs.toBytes(2);
swapOdos(tokenIn, ethAmount, data);
swapViaEnso(tokenIn, ethAmount, data);
} else {
revert InvalidCommandType(command);
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
4 changes: 2 additions & 2 deletions src/base/RouterImmutables.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,5 +32,5 @@ struct RouterParameters {
address wstEth;
IWusdn wusdn;
ISmardexFactory smardexFactory;
address odosSorRouter;
address ensoV2Router;
}
4 changes: 1 addition & 3 deletions src/interfaces/lido/IStETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/Commands.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 4 additions & 6 deletions src/libraries/smardex/SmardexRouterLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}

Expand Down
9 changes: 5 additions & 4 deletions src/libraries/usdn/UsdnProtocolRouterLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -60,7 +61,6 @@ library UsdnProtocolRouterLib {
data.amount = protocolAsset.balanceOf(address(this));
}
success_ = usdnProtocol.initiateDeposit{ value: data.ethAmount }(
// cast is made here to allow the {CONTRACT_BALANCE} value
data.amount.toUint128(),
data.sharesOutMin,
data.to,
Expand Down Expand Up @@ -88,8 +88,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
);
}

/**
Expand Down
44 changes: 44 additions & 0 deletions src/modules/Enso.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-3.0-or-later
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 Enso {
using SafeERC20 for IERC20;

/// @notice The address of the Enso v2 router.
address immutable ENSO_V2_ROUTER;

/// @notice Reverts when the swap via Enso fails.
error EnsoSwapFailed();

/// @param ensoV2Router The address of the Enso v2 router.
constructor(address ensoV2Router) {
ENSO_V2_ROUTER = ensoV2Router;
}

/**
* @notice Swaps tokens via the Enso router.
* @param tokenIn The address of the input token.
* @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(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();
}
}
}
}
Loading