EthConversionProxy

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:

{{
  "language": "Solidity",
  "sources": {
    "@openzeppelin/contracts/security/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }
}
"
    },
    "@openzeppelin/contracts/utils/Context.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
"
    },
    "contracts/ChainlinkConversionPath.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.0;\r
\r
import "./legacy_openzeppelin/contracts/access/roles/WhitelistAdminRole.sol";\r
\r
interface ERC20fraction {\r
    function decimals() external view returns (uint8);\r
}\r
\r
interface AggregatorFraction {\r
    function decimals() external view returns (uint8);\r
\r
    function latestAnswer() external view returns (int256);\r
\r
    function latestTimestamp() external view returns (uint256);\r
}\r
\r
/**\r
 * @title ChainlinkConversionPath\r
 *\r
 * @notice ChainlinkConversionPath is a contract computing currency conversion rates based on Chainlink aggretators\r
 */\r
contract ChainlinkConversionPath is WhitelistAdminRole {\r
    uint256 constant PRECISION = 1e18;\r
    uint256 constant NATIVE_TOKEN_DECIMALS = 18;\r
    uint256 constant FIAT_DECIMALS = 8;\r
    address public nativeTokenHash;\r
\r
    /**\r
     * @param _nativeTokenHash hash of the native token\r
     */\r
    constructor(address _nativeTokenHash) {\r
        nativeTokenHash = _nativeTokenHash;\r
    }\r
\r
    // Mapping of Chainlink aggregators (input currency => output currency => contract address)\r
    // input & output currencies are the addresses of the ERC20 contracts OR the sha3("currency code")\r
    mapping(address => mapping(address => address)) public allAggregators;\r
\r
    // declare a new aggregator\r
    event AggregatorUpdated(\r
        address _input,\r
        address _output,\r
        address _aggregator\r
    );\r
\r
    /**\r
     * @notice Gets native token hash\r
     */\r
    function getNativeTokenHashAddress() external view returns (address data) {\r
        return nativeTokenHash;\r
    }\r
\r
    /**\r
     * @notice Update an aggregator\r
     * @param _input address representing the input currency\r
     * @param _output address representing the output currency\r
     * @param _aggregator address of the aggregator contract\r
     */\r
    function updateAggregator(\r
        address _input,\r
        address _output,\r
        address _aggregator\r
    ) external onlyWhitelistAdmin {\r
        allAggregators[_input][_output] = _aggregator;\r
        emit AggregatorUpdated(_input, _output, _aggregator);\r
    }\r
\r
    /**\r
     * @notice Update a list of aggregators\r
     * @param _inputs list of addresses representing the input currencies\r
     * @param _outputs list of addresses representing the output currencies\r
     * @param _aggregators list of addresses of the aggregator contracts\r
     */\r
    function updateAggregatorsList(\r
        address[] calldata _inputs,\r
        address[] calldata _outputs,\r
        address[] calldata _aggregators\r
    ) external onlyWhitelistAdmin {\r
        require(\r
            _inputs.length == _outputs.length,\r
            "arrays must have the same length"\r
        );\r
        require(\r
            _inputs.length == _aggregators.length,\r
            "arrays must have the same length"\r
        );\r
\r
        // For every conversions of the path\r
        for (uint256 i; i < _inputs.length; i++) {\r
            allAggregators[_inputs[i]][_outputs[i]] = _aggregators[i];\r
            emit AggregatorUpdated(_inputs[i], _outputs[i], _aggregators[i]);\r
        }\r
    }\r
\r
    /**\r
     * @notice Computes the conversion of an amount through a list of intermediate conversions\r
     * @param _amountIn Amount to convert\r
     * @param _path List of addresses representing the currencies for the intermediate conversions\r
     * @return result The result after all the conversions\r
     * @return oldestRateTimestamp The oldest timestamp of the path\r
     */\r
    function getConversion(\r
        uint256 _amountIn,\r
        address[] calldata _path\r
    ) external view returns (uint256 result, uint256 oldestRateTimestamp) {\r
        (uint256 rate, uint256 timestamp, uint256 decimals) = getRate(_path);\r
\r
        // initialize the result\r
        result = (_amountIn * rate) / decimals;\r
\r
        oldestRateTimestamp = timestamp;\r
    }\r
\r
    /**\r
     * @notice Computes the conversion rate from a list of currencies\r
     * @param _path List of addresses representing the currencies for the conversions\r
     * @return rate The rate\r
     * @return oldestRateTimestamp The oldest timestamp of the path\r
     * @return decimals of the conversion rate\r
     */\r
    function getRate(\r
        address[] memory _path\r
    )\r
        public\r
        view\r
        returns (uint256 rate, uint256 oldestRateTimestamp, uint256 decimals)\r
    {\r
        // initialize the result with 18 decimals (for more precision)\r
        rate = PRECISION;\r
        decimals = PRECISION;\r
        oldestRateTimestamp = block.timestamp;\r
\r
        // For every conversion of the path\r
        for (uint256 i; i < _path.length - 1; i++) {\r
            (\r
                AggregatorFraction aggregator,\r
                bool reverseAggregator,\r
                uint256 decimalsInput,\r
                uint256 decimalsOutput\r
            ) = getAggregatorAndDecimals(_path[i], _path[i + 1]);\r
\r
            // store the latest timestamp of the path\r
            uint256 currentTimestamp = aggregator.latestTimestamp();\r
            if (currentTimestamp < oldestRateTimestamp) {\r
                oldestRateTimestamp = currentTimestamp;\r
            }\r
\r
            // get the rate of the current step\r
            uint256 currentRate = uint256(aggregator.latestAnswer());\r
            // get the number of decimals of the current rate\r
            uint256 decimalsAggregator = uint256(aggregator.decimals());\r
\r
            // mul with the difference of decimals before the current rate computation (for more precision)\r
            if (decimalsAggregator > decimalsInput) {\r
                rate = rate * (10 ** (decimalsAggregator - decimalsInput));\r
            }\r
            if (decimalsAggregator < decimalsOutput) {\r
                rate = rate * (10 ** (decimalsOutput - decimalsAggregator));\r
            }\r
\r
            // Apply the current rate (if path uses an aggregator in the reverse way, div instead of mul)\r
            if (reverseAggregator) {\r
                rate = (rate * (10 ** decimalsAggregator)) / currentRate;\r
            } else {\r
                rate = (rate * currentRate) / (10 ** decimalsAggregator);\r
            }\r
\r
            // div with the difference of decimals AFTER the current rate computation (for more precision)\r
            if (decimalsAggregator < decimalsInput) {\r
                rate = rate / (10 ** (decimalsInput - decimalsAggregator));\r
            }\r
            if (decimalsAggregator > decimalsOutput) {\r
                rate = rate / (10 ** (decimalsAggregator - decimalsOutput));\r
            }\r
        }\r
    }\r
\r
    /**\r
     * @notice Gets aggregators and decimals of two currencies\r
     * @param _input input Address\r
     * @param _output output Address\r
     * @return aggregator to get the rate between the two currencies\r
     * @return reverseAggregator true if the aggregator returned give the rate from _output to _input\r
     * @return decimalsInput decimals of _input\r
     * @return decimalsOutput decimals of _output\r
     */\r
    function getAggregatorAndDecimals(\r
        address _input,\r
        address _output\r
    )\r
        private\r
        view\r
        returns (\r
            AggregatorFraction aggregator,\r
            bool reverseAggregator,\r
            uint256 decimalsInput,\r
            uint256 decimalsOutput\r
        )\r
    {\r
        // Try to get the right aggregator for the conversion\r
        aggregator = AggregatorFraction(allAggregators[_input][_output]);\r
        reverseAggregator = false;\r
\r
        // if no aggregator found we try to find an aggregator in the reverse way\r
        if (address(aggregator) == address(0x00)) {\r
            aggregator = AggregatorFraction(allAggregators[_output][_input]);\r
            reverseAggregator = true;\r
        }\r
\r
        require(address(aggregator) != address(0x00), "No aggregator found");\r
\r
        // get the decimals for the two currencies\r
        decimalsInput = getDecimals(_input);\r
        decimalsOutput = getDecimals(_output);\r
    }\r
\r
    /**\r
     * @notice Gets decimals from an address currency\r
     * @param _addr address to check\r
     * @return decimals number of decimals\r
     */\r
    function getDecimals(\r
        address _addr\r
    ) private view returns (uint256 decimals) {\r
        // by default we assume it is fiat\r
        decimals = FIAT_DECIMALS;\r
        // if address is the hash of the ETH currency\r
        if (_addr == nativeTokenHash) {\r
            decimals = NATIVE_TOKEN_DECIMALS;\r
        } else if (isContract(_addr)) {\r
            // otherwise, we get the decimals from the erc20 directly\r
            decimals = ERC20fraction(_addr).decimals();\r
        }\r
    }\r
\r
    /**\r
     * @notice Checks if an address is a contract\r
     * @param _addr Address to check\r
     * @return true if the address hosts a contract, false otherwise\r
     */\r
    function isContract(address _addr) private view returns (bool) {\r
        uint32 size;\r
        // solium-disable security/no-inline-assembly\r
        assembly {\r
            size := extcodesize(_addr)\r
        }\r
        return (size > 0);\r
    }\r
}\r
"
    },
    "contracts/EthConversionProxy.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.0;\r
\r
import "./ChainlinkConversionPath.sol";\r
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";\r
import "./legacy_openzeppelin/contracts/access/roles/WhitelistAdminRole.sol";\r
\r
/**\r
 * @title EthConversionProxy\r
 * @notice This contract converts from chainlink then swaps ETH (or native token)\r
 *         before paying a request thanks to a conversion payment proxy.\r
 *         The inheritance from ReentrancyGuard is required to perform\r
 *         "transferExactEthWithReferenceAndFee" on the eth-fee-proxy contract\r
 */\r
contract EthConversionProxy is ReentrancyGuard, WhitelistAdminRole {\r
    address public paymentProxy;\r
    ChainlinkConversionPath public chainlinkConversionPath;\r
    address public nativeTokenHash;\r
\r
    constructor(\r
        address _paymentProxyAddress,\r
        address _chainlinkConversionPathAddress,\r
        address _nativeTokenHash,\r
        address _owner\r
    ) {\r
        paymentProxy = _paymentProxyAddress;\r
        chainlinkConversionPath = ChainlinkConversionPath(\r
            _chainlinkConversionPathAddress\r
        );\r
        nativeTokenHash = _nativeTokenHash;\r
        renounceWhitelistAdmin();\r
        _addWhitelistAdmin(_owner);\r
    }\r
\r
    // Event to declare a conversion with a reference\r
    event TransferWithConversionAndReference(\r
        uint256 amount,\r
        address currency,\r
        bytes indexed paymentReference,\r
        uint256 feeAmount,\r
        uint256 maxRateTimespan\r
    );\r
\r
    // Event to declare a transfer with a reference\r
    // This event is emitted by this contract from a delegate call of the payment-proxy\r
    event TransferWithReferenceAndFee(\r
        address to,\r
        uint256 amount,\r
        bytes indexed paymentReference,\r
        uint256 feeAmount,\r
        address feeAddress\r
    );\r
\r
    /**\r
     * @notice Gets native token hash\r
     */\r
    function getNativeTokenHashAddress() external view returns (address data) {\r
        return nativeTokenHash;\r
    }\r
\r
    /**\r
     * @notice Performs an ETH transfer with a reference computing the payment amount based on the request amount\r
     * @param _to Transfer recipient of the payement\r
     * @param _requestAmount Request amount\r
     * @param _path Conversion path\r
     * @param _paymentReference Reference of the payment related\r
     * @param _feeAmount The amount of the payment fee\r
     * @param _feeAddress The fee recipient\r
     * @param _maxRateTimespan Max time span with the oldestrate, ignored if zero\r
     */\r
    function transferWithReferenceAndFee(\r
        address _to,\r
        uint256 _requestAmount,\r
        address[] calldata _path,\r
        bytes calldata _paymentReference,\r
        uint256 _feeAmount,\r
        address _feeAddress,\r
        uint256 _maxRateTimespan\r
    ) external payable {\r
        require(\r
            _path[_path.length - 1] == nativeTokenHash,\r
            "payment currency must be the native token"\r
        );\r
\r
        (uint256 amountToPay, uint256 amountToPayInFees) = getConversions(\r
            _path,\r
            _requestAmount,\r
            _feeAmount,\r
            _maxRateTimespan\r
        );\r
\r
        // Pay the request and fees\r
        (bool status, ) = paymentProxy.delegatecall(\r
            abi.encodeWithSignature(\r
                "transferExactEthWithReferenceAndFee(address,uint256,bytes,uint256,address)",\r
                _to,\r
                amountToPay,\r
                _paymentReference,\r
                amountToPayInFees,\r
                _feeAddress\r
            )\r
        );\r
\r
        require(\r
            status,\r
            "paymentProxy transferExactEthWithReferenceAndFee failed"\r
        );\r
\r
        // Event to declare a transfer with a reference\r
        emit TransferWithConversionAndReference(\r
            _requestAmount,\r
            // request currency\r
            _path[0],\r
            _paymentReference,\r
            _feeAmount,\r
            _maxRateTimespan\r
        );\r
    }\r
\r
    function getConversions(\r
        address[] memory _path,\r
        uint256 _requestAmount,\r
        uint256 _feeAmount,\r
        uint256 _maxRateTimespan\r
    ) internal view returns (uint256 amountToPay, uint256 amountToPayInFees) {\r
        (\r
            uint256 rate,\r
            uint256 oldestTimestampRate,\r
            uint256 decimals\r
        ) = chainlinkConversionPath.getRate(_path);\r
\r
        // Check rate timespan\r
        require(\r
            _maxRateTimespan == 0 ||\r
                block.timestamp - oldestTimestampRate <= _maxRateTimespan,\r
            "aggregator rate is outdated"\r
        );\r
\r
        // Get the amount to pay in the native token\r
        amountToPay = (_requestAmount * rate) / decimals;\r
        amountToPayInFees = (_feeAmount * rate) / decimals;\r
    }\r
\r
    /**\r
     * @notice Update the conversion path contract used to fetch conversions\r
     * @param _chainlinkConversionPathAddress address of the conversion path contract\r
     */\r
    function updateConversionPathAddress(\r
        address _chainlinkConversionPathAddress\r
    ) external onlyWhitelistAdmin {\r
        chainlinkConversionPath = ChainlinkConversionPath(\r
            _chainlinkConversionPathAddress\r
        );\r
    }\r
\r
    /**\r
     * @notice Update the conversion proxy used to process the payment\r
     * @param _paymentProxyAddress address of the ETH conversion proxy\r
     */\r
    function updateConversionProxyAddress(\r
        address _paymentProxyAddress\r
    ) external onlyWhitelistAdmin {\r
        paymentProxy = _paymentProxyAddress;\r
    }\r
\r
    /**\r
     * @notice Update the native token hash\r
     * @param _nativeTokenHash hash of the native token represented as an eth address\r
     */\r
    function updateNativeTokenHash(\r
        address _nativeTokenHash\r
    ) external onlyWhitelistAdmin {\r
        nativeTokenHash = _nativeTokenHash;\r
    }\r
}\r
"
    },
    "contracts/legacy_openzeppelin/contracts/access/Roles.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.0;\r
\r
/**\r
 * @title Roles\r
 * @dev Library for managing addresses assigned to a Role.\r
 */\r
library Roles {\r
  struct Role {\r
    mapping(address => bool) bearer;\r
  }\r
\r
  /**\r
   * @dev Give an account access to this role.\r
   */\r
  function add(Role storage role, address account) internal {\r
    require(!has(role, account), 'Roles: account already has role');\r
    role.bearer[account] = true;\r
  }\r
\r
  /**\r
   * @dev Remove an account's access to this role.\r
   */\r
  function remove(Role storage role, address account) internal {\r
    require(has(role, account), 'Roles: account does not have role');\r
    role.bearer[account] = false;\r
  }\r
\r
  /**\r
   * @dev Check if an account has this role.\r
   * @return bool\r
   */\r
  function has(Role storage role, address account) internal view returns (bool) {\r
    require(account != address(0), 'Roles: account is the zero address');\r
    return role.bearer[account];\r
  }\r
}\r
"
    },
    "contracts/legacy_openzeppelin/contracts/access/roles/WhitelistAdminRole.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.0;\r
\r
import '@openzeppelin/contracts/utils/Context.sol';\r
import '../Roles.sol';\r
\r
/**\r
 * @title WhitelistAdminRole\r
 * @dev WhitelistAdmins are responsible for assigning and removing Whitelisted accounts.\r
 */\r
abstract contract WhitelistAdminRole is Context {\r
  using Roles for Roles.Role;\r
\r
  event WhitelistAdminAdded(address indexed account);\r
  event WhitelistAdminRemoved(address indexed account);\r
\r
  Roles.Role private _whitelistAdmins;\r
\r
  constructor() {\r
    _addWhitelistAdmin(_msgSender());\r
  }\r
\r
  modifier onlyWhitelistAdmin() {\r
    require(\r
      isWhitelistAdmin(_msgSender()),\r
      'WhitelistAdminRole: caller does not have the WhitelistAdmin role'\r
    );\r
    _;\r
  }\r
\r
  function isWhitelistAdmin(address account) public view returns (bool) {\r
    return _whitelistAdmins.has(account);\r
  }\r
\r
  function addWhitelistAdmin(address account) public onlyWhitelistAdmin {\r
    _addWhitelistAdmin(account);\r
  }\r
\r
  function renounceWhitelistAdmin() public {\r
    _removeWhitelistAdmin(_msgSender());\r
  }\r
\r
  function _addWhitelistAdmin(address account) internal {\r
    _whitelistAdmins.add(account);\r
    emit WhitelistAdminAdded(account);\r
  }\r
\r
  function _removeWhitelistAdmin(address account) internal {\r
    _whitelistAdmins.remove(account);\r
    emit WhitelistAdminRemoved(account);\r
  }\r
}\r
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "metadata": {
      "useLiteralContent": true
    }
  }
}}

Tags:
Proxy, Upgradeable, Factory, Oracle|addr:0x78a5b440dec22fa49e71909e0c8830a27a26e7bf|verified:true|block:23718689|tx:0x698f397706b7879d1702bd3d4aea7fb070364f404079160b4311fe30f9b4839b|first_check:1762175171

Submitted on: 2025-11-03 14:06:12

Comments

Log in to comment.

No comments yet.