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
}
}
}}
Submitted on: 2025-11-03 14:06:12
Comments
Log in to comment.
No comments yet.