文件 1 的 6:ChainlinkConversionPath.sol
pragma solidity ^0.8.0;
import './legacy_openzeppelin/contracts/access/roles/WhitelistAdminRole.sol';
interface ERC20fraction {
function decimals() external view returns (uint8);
}
interface AggregatorFraction {
function decimals() external view returns (uint8);
function latestAnswer() external view returns (int256);
function latestTimestamp() external view returns (uint256);
}
contract ChainlinkConversionPath is WhitelistAdminRole {
uint256 constant PRECISION = 1e18;
uint256 constant NATIVE_TOKEN_DECIMALS = 18;
uint256 constant FIAT_DECIMALS = 8;
address public nativeTokenHash;
constructor(address _nativeTokenHash) {
nativeTokenHash = _nativeTokenHash;
}
mapping(address => mapping(address => address)) public allAggregators;
event AggregatorUpdated(address _input, address _output, address _aggregator);
function updateAggregator(
address _input,
address _output,
address _aggregator
) external onlyWhitelistAdmin {
allAggregators[_input][_output] = _aggregator;
emit AggregatorUpdated(_input, _output, _aggregator);
}
function updateAggregatorsList(
address[] calldata _inputs,
address[] calldata _outputs,
address[] calldata _aggregators
) external onlyWhitelistAdmin {
require(_inputs.length == _outputs.length, 'arrays must have the same length');
require(_inputs.length == _aggregators.length, 'arrays must have the same length');
for (uint256 i; i < _inputs.length; i++) {
allAggregators[_inputs[i]][_outputs[i]] = _aggregators[i];
emit AggregatorUpdated(_inputs[i], _outputs[i], _aggregators[i]);
}
}
function getConversion(uint256 _amountIn, address[] calldata _path)
external
view
returns (uint256 result, uint256 oldestRateTimestamp)
{
(uint256 rate, uint256 timestamp, uint256 decimals) = getRate(_path);
result = (_amountIn * rate) / decimals;
oldestRateTimestamp = timestamp;
}
function getRate(address[] memory _path)
public
view
returns (
uint256 rate,
uint256 oldestRateTimestamp,
uint256 decimals
)
{
rate = PRECISION;
decimals = PRECISION;
oldestRateTimestamp = block.timestamp;
for (uint256 i; i < _path.length - 1; i++) {
(
AggregatorFraction aggregator,
bool reverseAggregator,
uint256 decimalsInput,
uint256 decimalsOutput
) = getAggregatorAndDecimals(_path[i], _path[i + 1]);
uint256 currentTimestamp = aggregator.latestTimestamp();
if (currentTimestamp < oldestRateTimestamp) {
oldestRateTimestamp = currentTimestamp;
}
uint256 currentRate = uint256(aggregator.latestAnswer());
uint256 decimalsAggregator = uint256(aggregator.decimals());
if (decimalsAggregator > decimalsInput) {
rate = rate * (10**(decimalsAggregator - decimalsInput));
}
if (decimalsAggregator < decimalsOutput) {
rate = rate * (10**(decimalsOutput - decimalsAggregator));
}
if (reverseAggregator) {
rate = (rate * (10**decimalsAggregator)) / currentRate;
} else {
rate = (rate * currentRate) / (10**decimalsAggregator);
}
if (decimalsAggregator < decimalsInput) {
rate = rate / (10**(decimalsInput - decimalsAggregator));
}
if (decimalsAggregator > decimalsOutput) {
rate = rate / (10**(decimalsAggregator - decimalsOutput));
}
}
}
function getAggregatorAndDecimals(address _input, address _output)
private
view
returns (
AggregatorFraction aggregator,
bool reverseAggregator,
uint256 decimalsInput,
uint256 decimalsOutput
)
{
aggregator = AggregatorFraction(allAggregators[_input][_output]);
reverseAggregator = false;
if (address(aggregator) == address(0x00)) {
aggregator = AggregatorFraction(allAggregators[_output][_input]);
reverseAggregator = true;
}
require(address(aggregator) != address(0x00), 'No aggregator found');
decimalsInput = getDecimals(_input);
decimalsOutput = getDecimals(_output);
}
function getDecimals(address _addr) private view returns (uint256 decimals) {
decimals = FIAT_DECIMALS;
if (_addr == nativeTokenHash) {
decimals = NATIVE_TOKEN_DECIMALS;
} else if (isContract(_addr)) {
decimals = ERC20fraction(_addr).decimals();
}
}
function isContract(address _addr) private view returns (bool) {
uint32 size;
assembly {
size := extcodesize(_addr)
}
return (size > 0);
}
}
文件 2 的 6:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
文件 3 的 6:EthConversionProxy.sol
pragma solidity ^0.8.0;
import './ChainlinkConversionPath.sol';
import '@openzeppelin/contracts/security/ReentrancyGuard.sol';
import './legacy_openzeppelin/contracts/access/roles/WhitelistAdminRole.sol';
contract EthConversionProxy is ReentrancyGuard, WhitelistAdminRole {
address public paymentProxy;
ChainlinkConversionPath public chainlinkConversionPath;
address public nativeTokenHash;
constructor(
address _paymentProxyAddress,
address _chainlinkConversionPathAddress,
address _nativeTokenHash
) {
paymentProxy = _paymentProxyAddress;
chainlinkConversionPath = ChainlinkConversionPath(_chainlinkConversionPathAddress);
nativeTokenHash = _nativeTokenHash;
}
event TransferWithConversionAndReference(
uint256 amount,
address currency,
bytes indexed paymentReference,
uint256 feeAmount,
uint256 maxRateTimespan
);
event TransferWithReferenceAndFee(
address to,
uint256 amount,
bytes indexed paymentReference,
uint256 feeAmount,
address feeAddress
);
function transferWithReferenceAndFee(
address _to,
uint256 _requestAmount,
address[] calldata _path,
bytes calldata _paymentReference,
uint256 _feeAmount,
address _feeAddress,
uint256 _maxRateTimespan
) external payable {
require(
_path[_path.length - 1] == nativeTokenHash,
'payment currency must be the native token'
);
(uint256 amountToPay, uint256 amountToPayInFees) = getConversions(
_path,
_requestAmount,
_feeAmount,
_maxRateTimespan
);
(bool status, ) = paymentProxy.delegatecall(
abi.encodeWithSignature(
'transferExactEthWithReferenceAndFee(address,uint256,bytes,uint256,address)',
_to,
amountToPay,
_paymentReference,
amountToPayInFees,
_feeAddress
)
);
require(status, 'paymentProxy transferExactEthWithReferenceAndFee failed');
emit TransferWithConversionAndReference(
_requestAmount,
_path[0],
_paymentReference,
_feeAmount,
_maxRateTimespan
);
}
function getConversions(
address[] memory _path,
uint256 _requestAmount,
uint256 _feeAmount,
uint256 _maxRateTimespan
) internal view returns (uint256 amountToPay, uint256 amountToPayInFees) {
(uint256 rate, uint256 oldestTimestampRate, uint256 decimals) = chainlinkConversionPath
.getRate(_path);
require(
_maxRateTimespan == 0 || block.timestamp - oldestTimestampRate <= _maxRateTimespan,
'aggregator rate is outdated'
);
amountToPay = (_requestAmount * rate) / decimals;
amountToPayInFees = (_feeAmount * rate) / decimals;
}
function updateConversionPathAddress(address _chainlinkConversionPathAddress)
external
onlyWhitelistAdmin
{
chainlinkConversionPath = ChainlinkConversionPath(_chainlinkConversionPathAddress);
}
function updateConversionProxyAddress(address _paymentProxyAddress)
external
onlyWhitelistAdmin
{
paymentProxy = _paymentProxyAddress;
}
}
文件 4 的 6:ReentrancyGuard.sol
pragma solidity ^0.8.0;
abstract contract ReentrancyGuard {
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
modifier nonReentrant() {
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
_status = _ENTERED;
_;
_status = _NOT_ENTERED;
}
}
文件 5 的 6:Roles.sol
pragma solidity ^0.8.0;
library Roles {
struct Role {
mapping (address => bool) bearer;
}
function add(Role storage role, address account) internal {
require(!has(role, account), "Roles: account already has role");
role.bearer[account] = true;
}
function remove(Role storage role, address account) internal {
require(has(role, account), "Roles: account does not have role");
role.bearer[account] = false;
}
function has(Role storage role, address account) internal view returns (bool) {
require(account != address(0), "Roles: account is the zero address");
return role.bearer[account];
}
}
文件 6 的 6:WhitelistAdminRole.sol
pragma solidity ^0.8.0;
import '@openzeppelin/contracts/utils/Context.sol';
import "../Roles.sol";
abstract contract WhitelistAdminRole is Context {
using Roles for Roles.Role;
event WhitelistAdminAdded(address indexed account);
event WhitelistAdminRemoved(address indexed account);
Roles.Role private _whitelistAdmins;
constructor () {
_addWhitelistAdmin(_msgSender());
}
modifier onlyWhitelistAdmin() {
require(isWhitelistAdmin(_msgSender()), "WhitelistAdminRole: caller does not have the WhitelistAdmin role");
_;
}
function isWhitelistAdmin(address account) public view returns (bool) {
return _whitelistAdmins.has(account);
}
function addWhitelistAdmin(address account) public onlyWhitelistAdmin {
_addWhitelistAdmin(account);
}
function renounceWhitelistAdmin() public {
_removeWhitelistAdmin(_msgSender());
}
function _addWhitelistAdmin(address account) internal {
_whitelistAdmins.add(account);
emit WhitelistAdminAdded(account);
}
function _removeWhitelistAdmin(address account) internal {
_whitelistAdmins.remove(account);
emit WhitelistAdminRemoved(account);
}
}
{
"compilationTarget": {
"src/contracts/EthConversionProxy.sol": "EthConversionProxy"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_paymentProxyAddress","type":"address"},{"internalType":"address","name":"_chainlinkConversionPathAddress","type":"address"},{"internalType":"address","name":"_nativeTokenHash","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"currency","type":"address"},{"indexed":true,"internalType":"bytes","name":"paymentReference","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"feeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"maxRateTimespan","type":"uint256"}],"name":"TransferWithConversionAndReference","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":true,"internalType":"bytes","name":"paymentReference","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"feeAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"feeAddress","type":"address"}],"name":"TransferWithReferenceAndFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"WhitelistAdminAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"WhitelistAdminRemoved","type":"event"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"addWhitelistAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"chainlinkConversionPath","outputs":[{"internalType":"contract ChainlinkConversionPath","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isWhitelistAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nativeTokenHash","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paymentProxy","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceWhitelistAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_requestAmount","type":"uint256"},{"internalType":"address[]","name":"_path","type":"address[]"},{"internalType":"bytes","name":"_paymentReference","type":"bytes"},{"internalType":"uint256","name":"_feeAmount","type":"uint256"},{"internalType":"address","name":"_feeAddress","type":"address"},{"internalType":"uint256","name":"_maxRateTimespan","type":"uint256"}],"name":"transferWithReferenceAndFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_chainlinkConversionPathAddress","type":"address"}],"name":"updateConversionPathAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_paymentProxyAddress","type":"address"}],"name":"updateConversionProxyAddress","outputs":[],"stateMutability":"nonpayable","type":"function"}]