Skip to content

Comments

feat: implement token swap functionality#175

Draft
parsaaba wants to merge 16 commits intodevfrom
feat/tokenswap
Draft

feat: implement token swap functionality#175
parsaaba wants to merge 16 commits intodevfrom
feat/tokenswap

Conversation

@parsaaba
Copy link
Collaborator

@parsaaba parsaaba commented Jun 3, 2025

No description provided.

// ============================================================================

modifier onlyAuthorizedCaller() {
if (!authorizedCallers[msg.sender] && msg.sender != owner()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we restrict this to admin and LTM? So our role permissions setup is similar to other LAT contracts. Do you foresee that that we would have other authorized callers?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can introudcue kinda role management style

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's really depends on how we gonna use that far contract.
Do you see any other use case beside that ltm usage?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the original comment is stale -- I do think it would be good to keep the option open for other contracts to call. WM may use it when settling user withdrawals (undecided as of now)

The role management style makes sense, as its in-line with the pattern of other contracts

But if we do the lib + storage method, we wouldn't need any auth on the swap functions (in lib) right? Only the management functions that control the FAR state (in storage contract) would need something like DEFAULT_ADMIN_ROLE?

@gowthamsundaresan
Copy link
Collaborator

gowthamsundaresan commented Jul 10, 2025

Hey, looks great overall!!

The one consideration that I have structurally is that future contracts like WM and potentially others would have to maintain separate (but synced) state of FAR. So I wonder if it's a good idea to split the FAR into:

  • AutoRoutingLib: All swap functions
  • AutoRoutingStorage : All state (including custom DEX Registry) and all their management functions. We add this to deploy process

The implications of this change would then be --

  • S1: I believe that none of the swap functions change any state right? So we can forgo all auth-related state variables, functions and modifers and related to authorizedCallers and routeManager in FAR. We can just keep DEFAULT_ADMIN_ROLE in AutoRoutingStorage taken during init (similar to other contracts) to control all management and emergency functions

  • S2: We can remove all changes to LTM init and in the future, any other contract can easily integrate the token swap functionality with
    using AutoRoutingLib for AutoRoutingStorage

  • S3: We can remove farSupportedTokens and instead,

    • just keep tokenDecimals mapping (if 0, token not valid)
    • impose the check of token support using LTM’s supportedTokens mapping

Overall, looking to reduce complexity + improve deployment/dev experience. Lemme know your thoughts!

parsaaba and others added 9 commits July 22, 2025 17:12
- Improve ETH transfer safety in emergencyWithdraw with proper error handling
- Clean up comments and imports in FinalAutoRouting
- Update LiquidTokenManager with latest implementation
- Remove deprecated TokenSwap test directory and related test files
if (poolAddresses.length != curveInterfaces.length)
revert InvalidParameter("interfaceArrays");

// Configure supported tokens
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use the same supported tokens from LTM? Since there can only be swaps between those tokens (we remain agnostic to intermediate tokens).

Because maintaining another registry here can create a new point of human error

require(msg.sender == address(this), "Internal only");

// Check quote freshness
if (block.timestamp - quote.timestamp > QUOTE_MAX_AGE)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the manager need to specifically watch out for this revert and adjust its calls accordingly (like splitting a heavy call into multiple smaller attempts, etc.)?

uint256 minAmountOut
) private returns (uint256 amountOut) {
if (params.tokenIn == address(WETH) && params.tokenOut == OSETH) {
return _performWETHToOsETHSwap(params, minAmountOut);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to specifically handle WETH -> osETH here instead of having the user do a multi-step swap? In case we don’t handle WETH -> osETH here, then can we remove the entire category of Protocol.MultiHop right? Since any other multi-hop is handled with (Protocol.Uniswap && route.isMultiHop)

Perhaps we can instead recommend WETH -> rETH -> osETH to the manager in its config for when it sets up RouteConfigs for AutoSwaps and put responsibility on the caller if they choose to use swapAssets for WETH <> osETH

* @param params Swap parameters
* @return amountOut Amount of output tokens received by caller
*/
function swapAssets(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the params.minAmountOut provided by the user is not actually used right?

  • Quoter: checks quote -> adds buffer and calculates new minAmountOut from quote -> attempts swap -> success if outputAmount >= new minAmountOut
  • Fallback: Directly calculates minAmountOut from SlippageConfig

If so, then I feel that we can give the caller the autonomy to choose their slippage without configs/fallbacks:

  • Quoter: checks quote -> if quote >= params.minAmountOut -> adds buffer and calculates new minAmountOut from quote -> attempts swap -> success if outputAmount >= new minAmountOut
  • Fallback: None, revert because caller has been too strict with their slippage expectations

Note 1: If this suggestion is taken, we can consider removing SlippageConfig altogether and instead add slippage uint256 slippageBps to RouteConfig.

Note 2: If both suggestions from L530 and Note 1 are taken, we can consider removing AssetType altogether. The bridgeAsset can be taken in the initialization params directly OR we can add unitOfAccount as an immutable constant on the LiquidToken contract and use that (this actually helps in a few ways for the backend/manager too)

);
}

if (amountOut < params.minAmountOut) revert InsufficientOutput();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are your thoughts on not using params.minAmountOut at all and relying on the pre-existing config. So this function can v simple for the manager to call and is truly “auto”

return amountOut;
}

// 3) Bridge route
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, this is smart!

* @param password password for safety
* @return amountOut Amount of tokens received
*/
function executeBackendSwap(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add minAmountOut here and check amountOut >= minAmountOut?

IERC20(tokenIn).safeApprove(dex, 0);
}

emit BackendSwapExecuted(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we include gasUsed like we did in AssetSwapped? Would be nice for backend to evaluate a given DEX over time and judge when to use it

// ============================================================================

modifier onlyAuthorizedCaller() {
if (!authorizedCallers[msg.sender] && msg.sender != owner()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the original comment is stale -- I do think it would be good to keep the option open for other contracts to call. WM may use it when settling user withdrawals (undecided as of now)

The role management style makes sense, as its in-line with the pattern of other contracts

But if we do the lib + storage method, we wouldn't need any auth on the swap functions (in lib) right? Only the management functions that control the FAR state (in storage contract) would need something like DEFAULT_ADMIN_ROLE?


// Swap using FAR - for every tokenIn swap to corresponding tokenOut
for (uint256 i = 0; i < assetsLength; i++) {
address tokenIn = address(assetsToSwap[i]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that tokenIn must be a supported token and have a corresponding supported strategy

@gowthamsundaresan gowthamsundaresan marked this pull request as draft July 28, 2025 14:52
@gowthamsundaresan
Copy link
Collaborator

Hey putting this PR into draft as we are splitting it up into LTM integration and a new repo. Will keep it open for reference and close it entirely after merge of v2-core to dev

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants