SwapForUsdcModule

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

interface IGnosisSafe {
    enum Operation {
        Call,
        DelegateCall
    }

    function execTransactionFromModule(
        address to,
        uint256 value,
        bytes calldata data,
        Operation operation
    ) external returns (bool success);

    function enableModule(address module) external;
}

interface IERC20 {
    function balanceOf(address account) external view returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function decimals() external view returns (uint8);
}

interface IOdos {
    struct swapTokenInfo {
        address inputToken;
        uint256 inputAmount;
        address inputReceiver;
        address outputToken;
        uint256 outputQuote;
        uint256 outputMin;
        address outputReceiver;
    }

    function swap(
        swapTokenInfo memory tokenInfo,
        bytes calldata pathDefinition,
        address executor,
        uint32 referralCode
    ) external;
}

contract SwapForUsdcModule {
    address public immutable safe;
    address public immutable operator;

    address public immutable odosSwapRouter;
    address public immutable inputToken;
    address public constant usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    uint256 public immutable minSwapRate; // e.g., 0.995e18 for 99.5%

    constructor(
        address safe_,
        address operator_,
        address odosSwapRouter_,
        address inputToken_,
        uint256 minSwapRate_ // e.g., 0.995e18 for 99.5%
    ) {
        require(safe_ != address(0), "Invalid Safe address");
        require(operator_ != address(0), "Invalid operator address");
        require(odosSwapRouter_ != address(0), "Invalid odosSwapRouter address");
        require(inputToken_ != address(0), "Invalid input token");
        require(inputToken_ != usdc, "Input token cannot be USDC");

        safe = safe_;
        operator = operator_;
        odosSwapRouter = odosSwapRouter_;
        inputToken = inputToken_;
        minSwapRate = minSwapRate_;
    }

    modifier onlyOperator() {
        require(msg.sender == operator, "Not authorized operator");
        _;
    }

    function swap(uint256 inputAmount, bytes calldata swapData) external onlyOperator returns (uint256) {
        require(inputAmount > 0, "Invalid input amount");
        (bytes4 selector, bytes memory dataWithoutSelector) = _splitCallData(swapData);
        require(selector == IOdos.swap.selector, "Invalid swap.selector");
        (IOdos.swapTokenInfo memory tokenInfo, , , ) = abi.decode(
            dataWithoutSelector,
            (IOdos.swapTokenInfo, bytes, address, uint32)
        );
        require(tokenInfo.inputToken == inputToken, "Invalid swap.input token");
        require(tokenInfo.outputToken == usdc, "Invalid swap.output token");
        require(tokenInfo.inputAmount == inputAmount, "Invalid swap.input amount");
        require(tokenInfo.outputReceiver == safe, "Invalid swap.output receiver");
        uint256 usdcBefore = IERC20(usdc).balanceOf(safe);
        _approve(inputToken, odosSwapRouter, inputAmount);
        bool success = IGnosisSafe(safe).execTransactionFromModule(
            odosSwapRouter,
            0,
            swapData,
            IGnosisSafe.Operation.Call
        );
        require(success, "Swap failed");
        _approve(inputToken, odosSwapRouter, 0);
        uint256 usdcAfter = IERC20(usdc).balanceOf(safe);
        require(usdcAfter > usdcBefore, "No USDC received");
        uint256 actualUsdcReceived = usdcAfter - usdcBefore;
        _validateSwapSlippage(inputAmount, actualUsdcReceived);
        return actualUsdcReceived;
    }

    function _approve(address token, address spender, uint256 amount) internal {
        bytes memory approveData = abi.encodeWithSelector(IERC20.approve.selector, spender, amount);
        bool success = IGnosisSafe(safe).execTransactionFromModule(token, 0, approveData, IGnosisSafe.Operation.Call);
        require(success, "Approval failed");
    }

    function _splitCallData(
        bytes calldata data
    ) internal pure returns (bytes4 selector, bytes memory dataWithoutSelector) {
        selector = bytes4(data[:4]);
        dataWithoutSelector = data[4:];
    }

    function _validateSwapSlippage(uint256 inputAmount, uint256 actualUsdcReceived) internal view {
        uint8 inputDecimals = IERC20(inputToken).decimals();
        uint256 minExpectedUsdc;
        if (inputDecimals >= 6) {
            uint256 scale = 10 ** (inputDecimals - 6);
            minExpectedUsdc = (inputAmount * minSwapRate) / (1e18 * scale);
        } else {
            uint256 scale = 10 ** (6 - inputDecimals);
            minExpectedUsdc = (inputAmount * minSwapRate * scale) / 1e18;
        }
        require(actualUsdcReceived >= minExpectedUsdc, "Swap rate too low");
    }
}

Tags:
Proxy, Upgradeable|addr:0xbc583092e0f5abfb940210fdcc5ff14b7cb488c5|verified:true|block:23544366|tx:0xd60ac6dabf2a7491344ea3e3ba24c6fa6886eb5536a802c9cff57d43b586903b|first_check:1760083170

Submitted on: 2025-10-10 09:59:30

Comments

Log in to comment.

No comments yet.