// File: src/TSLedgerAdapter.sol



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

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 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 _executeDelegateCall(
        address target,
        bytes memory data
    ) private returns (bool success, bytes memory result) {
        (success, result) = target.delegatecall(data);
    }

    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.");
        bytes memory data;

        if (config.interfaceId == 1) {
            data = abi.encodeWithSignature(
                "depositWithExpiry(address,address,uint256,string,uint256)",
                params
            );
        } else if (config.interfaceId == 2) {
            data = abi.encodeWithSignature(
                "swapIn(address,address,string,address,uint256,address,bytes,uint256)",
                params
            );
        } else if (config.interfaceId == 3) {
            data = abi.encodeWithSignature(
                "swapIn(address,address,string,address,uint256,uint256,uint256)",
                params
            );
        } else if (config.interfaceId == 4) {
            data = abi.encodeWithSignature(
                "swapIn(address,address,string,address,uint256,uint256,uint256)",
                params
            );
        } else if (config.interfaceId == 5) {
            data = abi.encodeWithSignature(
                "swap(address,address,string,address,address,uint256,uint256,uint256)",
                params
            );
        } else {
            revert("Invalid interfaceId");
        }

        (bool success, bytes memory result) = _executeDelegateCall(
            config.aggregator,
            data
        );

        require(success, string(result)); // target contract error will bubble up
    }
}

// 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.10;

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);
    }
}