// File: src/TSLedgerAdapter.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import { SafeTransferLib } from "../lib/SafeTransferLib.sol";
import { ReentrancyGuard } from "../lib/ReentrancyGuard.sol";
import { Owners } from "./Owners.sol";

contract TSLedgerAdapter is Owners, ReentrancyGuard {
    
    struct AggregatorConfig {
        address aggregator;
        uint16 interfaceId;
    }

    mapping(uint16 => AggregatorConfig) public aggregatorConfigs;

    constructor() {
        _setOwner(msg.sender, true);
    }

    function addOwner(address owner) external isOwner {
        _setOwner(owner, true);
    }

    function removeOwner(address owner) external isOwner {
        _setOwner(owner, false);
    }

    function revokeOwnership() external isOwner {
        _setOwner(msg.sender, false);
    }

    // aggregator configs should be restriced to allowed contracts
    // after initial setup to copy current whitelist
    // ownership will be transfered to a multisig controlled by multiple parties
    function setAggregatorConfig(uint16 id, address aggregator, uint16 interfaceId) external isOwner {
        require(interfaceId > 0 && interfaceId < 6, "Invalid interfaceId");
        aggregatorConfigs[id] = AggregatorConfig(aggregator, interfaceId);
    }

    // to help maintainers of this contract
    function getConfig(uint16 id) public view returns (address aggregator, uint16 interfaceId) {
        AggregatorConfig memory config = aggregatorConfigs[id];
        return (config.aggregator, config.interfaceId);
    }

    // in case users send tokens to this contract rather than calling it
    function emergencyWithdrawToken(address payable to, address token, uint256 amount) external isOwner {
        SafeTransferLib.safeTransfer(token, to, amount);
    }

    // in case users send gas assets to this contract rather than calling it
    function emergencyWithdrawGasAsset(address payable to, uint256 amount) external isOwner {
        SafeTransferLib.safeTransferETH(to, amount);
    }

    function _depositWithExpiry(address target, bytes calldata params) private returns (bool success) {
        (address payable vault, address asset, uint amount, string memory memo, uint expiration) = abi.decode(params, (address, address, uint, string, uint));
        (success,) = target.call{value: msg.value}(abi.encodeWithSignature("depositWithExpiry(address,address,uint256,string,uint256)", vault, asset, amount, memo, expiration));
        return success;
    }

    function _tcGenericSwapIn(address target, bytes calldata params) private returns (bool success) {
        (address tcRouter, address tcVault, string memory tcMemo, address token, uint amount, address router, bytes memory data, uint deadline) = abi.decode(params, (address, address, string, address, uint, address, bytes, uint));
        (success,) = target.call{value: msg.value}(abi.encodeWithSignature("swapIn(address,address,string,address,uint256,address,bytes,uint256)", tcRouter, tcVault, tcMemo, token, amount, router, data, deadline));
        return success;
    }

    function _tcDexSwapIn(address target, bytes calldata params) private returns (bool success) {
        (address tcRouter, address tcVault, string memory tcMemo, address token, uint amount, uint amountOutMin, uint deadline) = abi.decode(params, (address, address, string, address, uint, uint, uint));
        (success,) = target.call{value: msg.value}(abi.encodeWithSignature("swapIn(address,address,string,address,uint256,uint256,uint256)", tcRouter, tcVault, tcMemo, token, amount, amountOutMin, deadline));
        return success;
    }

    function _dexSwapIn(address target, bytes calldata params) private returns (bool success) {
        (address router, address vault, string memory memo, address token, uint amount, uint amountOutMin, uint deadline) = abi.decode(params, (address, address, string, address, uint, uint, uint));
        (success,) = target.call{value: msg.value}(abi.encodeWithSignature("swapIn(address,address,string,address,uint256,uint256,uint256)", router, vault, memo, token, amount, amountOutMin, deadline));
        return success;
    }

    function _swap(address target, bytes calldata params) private returns (bool success) {
        (address router, address vault, string memory memo, address tokenIn, address tokenOut, uint amountIn, uint amountOutMin, uint deadline) = abi.decode(params, (address, address, string, address, address, uint, uint, uint));
        (success,) = target.call{value: msg.value}(abi.encodeWithSignature("swap(address,address,string,address,address,uint256,uint256,uint256)", router, vault, memo, tokenIn, tokenOut, amountIn, amountOutMin, deadline));
        return success;
    }

    function ledgerCall(
        uint16 aggregatorConfig,
        bytes calldata params,
        string calldata memo,
        string calldata sellAsset,
        uint256 sellAmount,
        string calldata buyAsset,
        uint256 buyAmountExpected,
        uint256 buyAmountMin
    ) external payable nonReentrant {
        AggregatorConfig memory config = aggregatorConfigs[aggregatorConfig];
        require(config.aggregator != address(0), "Aggregator must be set.");
        bool success;

        if(config.interfaceId == 1) {
            success = _depositWithExpiry(config.aggregator, params);
        } else if(config.interfaceId == 2) {
            success = _tcGenericSwapIn(config.aggregator, params);
        } else if(config.interfaceId == 3) {
            success = _tcDexSwapIn(config.aggregator, params);
        } else if(config.interfaceId == 4) {
            success = _dexSwapIn(config.aggregator, params);
        } else if(config.interfaceId == 5) {
            success = _swap(config.aggregator, params);
        } else {
            revert("Invalid interfaceId");
        }

        require(success, "External call failed.");
    }
}
// File: lib/SafeTransferLib.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Gnosis (https://github.com/gnosis/gp-v2-contracts/blob/main/src/contracts/libraries/GPv2SafeERC20.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
library SafeTransferLib {
    /*///////////////////////////////////////////////////////////////
                            ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool callStatus;

        assembly {
            // Transfer the ETH and store if it succeeded or not.
            callStatus := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(callStatus, "ETH_TRANSFER_FAILED");
    }

    /*///////////////////////////////////////////////////////////////
                           ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool callStatus;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata to memory piece by piece:
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) // Begin with the function selector.
            mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "from" argument.
            mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.

            // Call the token and store if it succeeded or not.
            // We use 100 because the calldata length is 4 + 32 * 3.
            callStatus := call(gas(), token, 0, freeMemoryPointer, 100, 0, 0)
        }

        require(didLastOptionalReturnCallSucceed(callStatus), "TRANSFER_FROM_FAILED");
    }

    function safeTransfer(
        address token,
        address to,
        uint256 amount
    ) internal {
        bool callStatus;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata to memory piece by piece:
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) // Begin with the function selector.
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.

            // Call the token and store if it succeeded or not.
            // We use 68 because the calldata length is 4 + 32 * 2.
            callStatus := call(gas(), token, 0, freeMemoryPointer, 68, 0, 0)
        }

        require(didLastOptionalReturnCallSucceed(callStatus), "TRANSFER_FAILED");
    }

    function safeApprove(
        address token,
        address to,
        uint256 amount
    ) internal {
        bool callStatus;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata to memory piece by piece:
            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) // Begin with the function selector.
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Mask and append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.

            // Call the token and store if it succeeded or not.
            // We use 68 because the calldata length is 4 + 32 * 2.
            callStatus := call(gas(), token, 0, freeMemoryPointer, 68, 0, 0)
        }

        require(didLastOptionalReturnCallSucceed(callStatus), "APPROVE_FAILED");
    }

    /*///////////////////////////////////////////////////////////////
                         INTERNAL HELPER LOGIC
    //////////////////////////////////////////////////////////////*/

    function didLastOptionalReturnCallSucceed(bool callStatus) private pure returns (bool success) {
        assembly {
            // Get how many bytes the call returned.
            let returnDataSize := returndatasize()

            // If the call reverted:
            if iszero(callStatus) {
                // Copy the revert message into memory.
                returndatacopy(0, 0, returnDataSize)

                // Revert with the same message.
                revert(0, returnDataSize)
            }

            switch returnDataSize
            case 32 {
                // Copy the return data into memory.
                returndatacopy(0, 0, returnDataSize)

                // Set success to whether it returned true.
                success := iszero(iszero(mload(0)))
            }
            case 0 {
                // There was no return data.
                success := 1
            }
            default {
                // It returned some malformed input.
                success := 0
            }
        }
    }
}

// File: lib/ReentrancyGuard.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
    uint256 private locked = 1;

    modifier nonReentrant() {
        require(locked == 1, "REENTRANCY");

        locked = 2;

        _;

        locked = 1;
    }
}

// File: src/Owners.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

abstract contract Owners {
    event OwnerSet(address indexed owner, bool active);

    mapping(address => bool) public owners;

    modifier isOwner() {
        require(owners[msg.sender], "Unauthorized");
        _;
    }

    function _setOwner(address owner, bool active) internal virtual {
      owners[owner] = active;
      emit OwnerSet(owner, active);
    }

    function setOwner(address owner, bool active) external virtual isOwner {
      _setOwner(owner, active);
    }
}