Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"@openzeppelin/contracts/token/ERC20/ERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/
function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/
function _transfer(address from, address to, uint256 amount) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
// Overflow not possible: amount <= accountBalance <= totalSupply.
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}
"
},
"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
"
},
"@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
"
},
"@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/BatchConversionPayments.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.4;\r
\r
import "./interfaces/IERC20ConversionProxy.sol";\r
import "./interfaces/IEthConversionProxy.sol";\r
import "./BatchNoConversionPayments.sol";\r
\r
/**\r
* @title BatchConversionPayments\r
* @notice This contract makes multiple conversion payments with references, in one transaction:\r
* - on:\r
* - ERC20 tokens: using Erc20ConversionProxy and ERC20FeeProxy\r
* - Native tokens: (e.g. ETH) using EthConversionProxy and EthereumFeeProxy\r
* - to: multiple addresses\r
* - fees: conversion proxy fees and additional batch conversion fees are paid to the same address.\r
* batchPayments is the main function to batch all kinds of payments at once.\r
* If one transaction of the batch fails, all transactions are reverted.\r
* @dev batchPayments is the main function, but other batch payment functions are "public" in order to do\r
* gas optimization in some cases.\r
*/\r
contract BatchConversionPayments is BatchNoConversionPayments {\r
using SafeERC20 for IERC20;\r
\r
IERC20ConversionProxy public paymentErc20ConversionProxy;\r
IEthConversionProxy public paymentNativeConversionProxy;\r
\r
/** payerAuthorized is set to true to workaround the non-payable aspect in batch native conversion */\r
bool private payerAuthorized = false;\r
\r
/**\r
* @dev Used by the batchPayments to handle information for heterogeneous batches, grouped by payment network:\r
* - paymentNetworkId: from 0 to 4, cf. `batchPayments()` method\r
* - requestDetails all the data required for conversion and no conversion requests to be paid\r
*/\r
struct MetaDetail {\r
uint256 paymentNetworkId;\r
RequestDetail[] requestDetails;\r
}\r
\r
/**\r
* @param _paymentErc20Proxy The ERC20 payment proxy address to use.\r
* @param _paymentNativeProxy The native payment proxy address to use.\r
* @param _paymentErc20ConversionProxy The ERC20 Conversion payment proxy address to use.\r
* @param _paymentNativeConversionFeeProxy The native Conversion payment proxy address to use.\r
* @param _chainlinkConversionPath The address of the conversion path contract.\r
* @param _owner Owner of the contract.\r
*/\r
constructor(\r
address _paymentErc20Proxy,\r
address _paymentNativeProxy,\r
address _paymentErc20ConversionProxy,\r
address _paymentNativeConversionFeeProxy,\r
address _chainlinkConversionPath,\r
address _owner\r
)\r
BatchNoConversionPayments(\r
_paymentErc20Proxy,\r
_paymentNativeProxy,\r
_chainlinkConversionPath,\r
_owner\r
)\r
{\r
paymentErc20ConversionProxy = IERC20ConversionProxy(\r
_paymentErc20ConversionProxy\r
);\r
paymentNativeConversionProxy = IEthConversionProxy(\r
_paymentNativeConversionFeeProxy\r
);\r
}\r
\r
/**\r
* This contract is non-payable.\r
* Making a Native payment with conversion requires the contract to accept incoming Native tokens.\r
* @dev See the end of `paymentNativeConversionProxy.transferWithReferenceAndFee` where the leftover is given back.\r
*/\r
receive() external payable override {\r
require(payerAuthorized || msg.value == 0, "Non-payable");\r
}\r
\r
/**\r
* @notice Batch payments on different payment networks at once.\r
* @param metaDetails contains paymentNetworkId and requestDetails\r
* - batchMultiERC20ConversionPayments, paymentNetworkId=0\r
* - batchERC20Payments, paymentNetworkId=1\r
* - batchMultiERC20Payments, paymentNetworkId=2\r
* - batchNativePayments, paymentNetworkId=3\r
* - batchNativeConversionPayments, paymentNetworkId=4\r
* If metaDetails use paymentNetworkId = 4, it must be at the end of the list, or the transaction can be reverted.\r
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
* For batch native, mock an array of array to apply the limit, e.g: [[]]\r
* Without paths, there is not limitation, neither for the batch native functions.\r
* @param feeAddress The address where fees should be paid.\r
* @dev Use pathsToUSD only if you are pretty sure the batch fees will higher than the\r
* USD limit batchFeeAmountUSDLimit, because it increase gas consumption.\r
* batchPayments only reduces gas consumption when using more than a single payment network.\r
* For single payment network payments, it is more efficient to use the suited batch function.\r
*/\r
function batchPayments(\r
MetaDetail[] calldata metaDetails,\r
address[][] calldata pathsToUSD,\r
address feeAddress\r
) external payable {\r
require(metaDetails.length < 6, "more than 5 metaDetails");\r
\r
uint256 batchFeeAmountUSD = 0;\r
for (uint256 i = 0; i < metaDetails.length; i++) {\r
MetaDetail calldata metaDetail = metaDetails[i];\r
if (metaDetail.paymentNetworkId == 0) {\r
batchFeeAmountUSD += _batchMultiERC20ConversionPayments(\r
metaDetail.requestDetails,\r
batchFeeAmountUSD,\r
pathsToUSD,\r
feeAddress\r
);\r
} else if (metaDetail.paymentNetworkId == 1) {\r
batchFeeAmountUSD += _batchERC20Payments(\r
metaDetail.requestDetails,\r
pathsToUSD,\r
batchFeeAmountUSD,\r
payable(feeAddress)\r
);\r
} else if (metaDetail.paymentNetworkId == 2) {\r
batchFeeAmountUSD += _batchMultiERC20Payments(\r
metaDetail.requestDetails,\r
pathsToUSD,\r
batchFeeAmountUSD,\r
feeAddress\r
);\r
} else if (metaDetail.paymentNetworkId == 3) {\r
if (metaDetails[metaDetails.length - 1].paymentNetworkId == 4) {\r
// Set to false only if batchNativeConversionPayments is called after this function\r
transferBackRemainingNativeTokens = false;\r
}\r
batchFeeAmountUSD += _batchNativePayments(\r
metaDetail.requestDetails,\r
pathsToUSD.length == 0,\r
batchFeeAmountUSD,\r
payable(feeAddress)\r
);\r
if (metaDetails[metaDetails.length - 1].paymentNetworkId == 4) {\r
transferBackRemainingNativeTokens = true;\r
}\r
} else if (metaDetail.paymentNetworkId == 4) {\r
batchFeeAmountUSD += _batchNativeConversionPayments(\r
metaDetail.requestDetails,\r
pathsToUSD.length == 0,\r
batchFeeAmountUSD,\r
payable(feeAddress)\r
);\r
} else {\r
revert("Wrong paymentNetworkId");\r
}\r
}\r
}\r
\r
/**\r
* @notice Send a batch of ERC20 payments with amounts based on a request\r
* currency (e.g. fiat), with fees and paymentReferences to multiple accounts, with multiple tokens.\r
* @param requestDetails List of ERC20 requests denominated in fiat to pay.\r
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
* Without paths, there is not a fee limitation, and it consumes less gas.\r
* @param feeAddress The fee recipient.\r
*/\r
function batchMultiERC20ConversionPayments(\r
RequestDetail[] calldata requestDetails,\r
address[][] calldata pathsToUSD,\r
address feeAddress\r
) public returns (uint256) {\r
return\r
_batchMultiERC20ConversionPayments(\r
requestDetails,\r
0,\r
pathsToUSD,\r
feeAddress\r
);\r
}\r
\r
/**\r
* @notice Send a batch of Native conversion payments with fees and paymentReferences to multiple accounts.\r
* If one payment fails, the whole batch is reverted.\r
* @param requestDetails List of native requests denominated in fiat to pay.\r
* @param skipFeeUSDLimit Setting the value to true skips the USD fee limit, and reduces gas consumption.\r
* @param feeAddress The fee recipient.\r
* @dev It uses NativeConversionProxy (EthereumConversionProxy) to pay an invoice and fees.\r
* Please:\r
* Note that if there is not enough Native token attached to the function call,\r
* the following error is thrown: "revert paymentProxy transferExactEthWithReferenceAndFee failed"\r
*/\r
function batchNativeConversionPayments(\r
RequestDetail[] calldata requestDetails,\r
bool skipFeeUSDLimit,\r
address payable feeAddress\r
) public payable returns (uint256) {\r
return\r
_batchNativeConversionPayments(\r
requestDetails,\r
skipFeeUSDLimit,\r
0,\r
feeAddress\r
);\r
}\r
\r
/**\r
* @notice Send a batch of ERC20 payments with amounts based on a request\r
* currency (e.g. fiat), with fees and paymentReferences to multiple accounts, with multiple tokens.\r
* @param requestDetails List of ERC20 requests denominated in fiat to pay.\r
* @param batchFeeAmountUSD The batch fee amount in USD already paid.\r
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
* Without paths, there is not a fee limitation, and it consumes less gas.\r
* @param feeAddress The fee recipient.\r
*/\r
function _batchMultiERC20ConversionPayments(\r
RequestDetail[] calldata requestDetails,\r
uint256 batchFeeAmountUSD,\r
address[][] calldata pathsToUSD,\r
address feeAddress\r
) private returns (uint256) {\r
Token[] memory uTokens = getUTokens(requestDetails);\r
\r
IERC20 requestedToken;\r
// For each token: check allowance, transfer funds on the contract and approve the paymentProxy to spend if needed\r
for (\r
uint256 k = 0;\r
k < uTokens.length && uTokens[k].amountAndFee > 0;\r
k++\r
) {\r
uTokens[k].batchFeeAmount =\r
(uTokens[k].amountAndFee * batchFee) /\r
feeDenominator;\r
requestedToken = IERC20(uTokens[k].tokenAddress);\r
transferToContract(\r
requestedToken,\r
uTokens[k].amountAndFee,\r
uTokens[k].batchFeeAmount,\r
address(paymentErc20ConversionProxy)\r
);\r
}\r
\r
// Batch pays the requests using Erc20ConversionFeeProxy\r
for (uint256 i = 0; i < requestDetails.length; i++) {\r
RequestDetail calldata rD = requestDetails[i];\r
paymentErc20ConversionProxy.transferFromWithReferenceAndFee(\r
rD.recipient,\r
rD.requestAmount,\r
rD.path,\r
rD.paymentReference,\r
rD.feeAmount,\r
feeAddress,\r
rD.maxToSpend,\r
rD.maxRateTimespan\r
);\r
}\r
\r
// Batch sends back to the payer the tokens not spent and pays the batch fee\r
for (\r
uint256 k = 0;\r
k < uTokens.length && uTokens[k].amountAndFee > 0;\r
k++\r
) {\r
requestedToken = IERC20(uTokens[k].tokenAddress);\r
\r
// Batch sends back to the payer the tokens not spent = excessAmount\r
// excessAmount = maxToSpend - reallySpent, which is equal to the remaining tokens on the contract\r
uint256 excessAmount = requestedToken.balanceOf(address(this));\r
if (excessAmount > 0) {\r
requestedToken.safeTransfer(msg.sender, excessAmount);\r
}\r
\r
// Calculate batch fee to pay\r
uint256 batchFeeToPay = ((uTokens[k].amountAndFee - excessAmount) *\r
batchFee) / feeDenominator;\r
\r
(batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay(\r
batchFeeToPay,\r
uTokens[k].tokenAddress,\r
batchFeeAmountUSD,\r
pathsToUSD\r
);\r
\r
// Payer pays the exact batch fees amount\r
require(\r
safeTransferFrom(\r
uTokens[k].tokenAddress,\r
feeAddress,\r
batchFeeToPay\r
),\r
"Batch fee transferFrom() failed"\r
);\r
}\r
return batchFeeAmountUSD;\r
}\r
\r
/**\r
* @notice Send a batch of Native conversion payments with fees and paymentReferences to multiple accounts.\r
* If one payment fails, the whole batch is reverted.\r
* @param requestDetails List of native requests denominated in fiat to pay.\r
* @param skipFeeUSDLimit Setting the value to true skips the USD fee limit, and reduces gas consumption.\r
* @param batchFeeAmountUSD The batch fee amount in USD already paid.\r
* @param feeAddress The fee recipient.\r
* @dev It uses NativeConversionProxy (EthereumConversionProxy) to pay an invoice and fees.\r
* Please:\r
* Note that if there is not enough Native token attached to the function call,\r
* the following error is thrown: "revert paymentProxy transferExactEthWithReferenceAndFee failed"\r
*/\r
function _batchNativeConversionPayments(\r
RequestDetail[] calldata requestDetails,\r
bool skipFeeUSDLimit,\r
uint256 batchFeeAmountUSD,\r
address payable feeAddress\r
) private returns (uint256) {\r
uint256 contractBalance = address(this).balance;\r
payerAuthorized = true;\r
\r
// Batch contract pays the requests through nativeConversionProxy\r
for (uint256 i = 0; i < requestDetails.length; i++) {\r
RequestDetail calldata rD = requestDetails[i];\r
paymentNativeConversionProxy.transferWithReferenceAndFee{\r
value: address(this).balance\r
}(\r
payable(rD.recipient),\r
rD.requestAmount,\r
rD.path,\r
rD.paymentReference,\r
rD.feeAmount,\r
feeAddress,\r
rD.maxRateTimespan\r
);\r
}\r
\r
// Batch contract pays batch fee\r
uint256 batchFeeToPay = (((contractBalance - address(this).balance)) *\r
batchFee) / feeDenominator;\r
\r
if (skipFeeUSDLimit == false) {\r
(batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay(\r
batchFeeToPay,\r
pathsNativeToUSD[0][0],\r
batchFeeAmountUSD,\r
pathsNativeToUSD\r
);\r
}\r
\r
require(\r
address(this).balance >= batchFeeToPay,\r
"Not enough funds for batch conversion fees"\r
);\r
feeAddress.transfer(batchFeeToPay);\r
\r
// Batch contract transfers the remaining native tokens to the payer\r
(bool sendBackSuccess, ) = payable(msg.sender).call{\r
value: address(this).balance\r
}("");\r
require(sendBackSuccess, "Could not send remaining funds to the payer");\r
payerAuthorized = false;\r
\r
return batchFeeAmountUSD;\r
}\r
\r
/*\r
* Admin functions to edit the conversion proxies address and fees.\r
*/\r
\r
/**\r
* @param _paymentErc20ConversionProxy The address of the ERC20 Conversion payment proxy to use.\r
* Update cautiously, the proxy has to match the invoice proxy.\r
*/\r
function setPaymentErc20ConversionProxy(\r
address _paymentErc20ConversionProxy\r
) external onlyOwner {\r
paymentErc20ConversionProxy = IERC20ConversionProxy(\r
_paymentErc20ConversionProxy\r
);\r
}\r
\r
/**\r
* @param _paymentNativeConversionProxy The address of the native Conversion payment proxy to use.\r
* Update cautiously, the proxy has to match the invoice proxy.\r
*/\r
function setPaymentNativeConversionProxy(\r
address _paymentNativeConversionProxy\r
) external onlyOwner {\r
paymentNativeConversionProxy = IEthConversionProxy(\r
_paymentNativeConversionProxy\r
);\r
}\r
}\r
"
},
"contracts/BatchNoConversionPayments.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.4;\r
\r
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";\r
import "./lib/SafeERC20.sol";\r
import "@openzeppelin/contracts/access/Ownable.sol";\r
import "./interfaces/ERC20FeeProxy.sol";\r
import "./interfaces/EthereumFeeProxy.sol";\r
import "./ChainlinkConversionPath.sol";\r
\r
/**\r
* @title BatchNoConversionPayments\r
* @notice This contract makes multiple payments with references, in one transaction:\r
* - on: ERC20 Payment Proxy and Native (ETH) Payment Proxy of the Request Network protocol\r
* - to: multiple addresses\r
* - fees: ERC20 and Native (ETH) proxies fees are paid to the same address\r
* An additional batch fee is paid to the same address\r
* If one transaction of the batch fail, every transactions are reverted.\r
* @dev It is a clone of BatchPayment.sol, with three main modifications:\r
* - function "receive" has one other condition: payerAuthorized\r
* - fees are now divided by 10_000 instead of 1_000 in previous version\r
* - batch payment functions have new names and are now public, instead of external\r
*/\r
contract BatchNoConversionPayments is Ownable {\r
using SafeERC20 for IERC20;\r
\r
IERC20FeeProxy public paymentErc20Proxy;\r
IEthereumFeeProxy public paymentNativeProxy;\r
ChainlinkConversionPath public chainlinkConversionPath;\r
\r
/** Used to calculate batch fees: batchFee = 30 represent 0.30% of fee */\r
uint16 public batchFee = 0;\r
/** Used to calculate batch fees: divide batchFee by feeDenominator */\r
uint16 internal feeDenominator = 10000;\r
/** The amount of the batch fee cannot exceed a predefined amount in USD, e.g:\r
batchFeeAmountUSDLimit = 150 * 1e8 represents $150 */\r
uint64 public batchFeeAmountUSDLimit = 15000000000;\r
\r
/** transferBackRemainingNativeTokens is set to false only if the payer use batchPayments\r
and call both batchNativePayments and batchNativeConversionPayments */\r
bool internal transferBackRemainingNativeTokens = true;\r
\r
address public USDAddress = 0x775EB53d00DD0Acd3EC1696472105d579B9b386b;\r
address public NativeAddress = 0xF5AF88e117747e87fC5929F2ff87221B1447652E;\r
address[][] public pathsNativeToUSD;\r
\r
/** Contains the address of a token, the sum of the amount and fees paid with it, and the batch fee amount */\r
struct Token {\r
address tokenAddress;\r
uint256 amountAndFee;\r
uint256 batchFeeAmount;\r
}\r
\r
/**\r
* @dev All the information of a request, except the feeAddress\r
* recipient: Recipient address of the payment\r
* requestAmount: Request amount, in fiat for conversion payment\r
* path: Only for conversion payment: the conversion path\r
* paymentReference: Unique reference of the payment\r
* feeAmount: The fee amount, denominated in the first currency of `path` for conversion payment\r
* maxToSpend: Only for conversion payment:\r
* Maximum amount the payer wants to spend, denominated in the last currency of `path`:\r
* it includes fee proxy but NOT the batch fees to pay\r
* maxRateTimespan: Only for conversion payment:\r
* Max acceptable times span for conversion rates, ignored if zero\r
*/\r
struct RequestDetail {\r
address recipient;\r
uint256 requestAmount;\r
address[] path;\r
bytes paymentReference;\r
uint256 feeAmount;\r
uint256 maxToSpend;\r
uint256 maxRateTimespan;\r
}\r
\r
/**\r
* @param _paymentErc20Proxy The address to the ERC20 fee payment proxy to use.\r
* @param _paymentNativeProxy The address to the Native fee payment proxy to use.\r
* @param _chainlinkConversionPath The address of the conversion path contract.\r
* @param _owner Owner of the contract.\r
*/\r
constructor(\r
address _paymentErc20Proxy,\r
address _paymentNativeProxy,\r
address _chainlinkConversionPath,\r
address _owner\r
) {\r
paymentErc20Proxy = IERC20FeeProxy(_paymentErc20Proxy);\r
paymentNativeProxy = IEthereumFeeProxy(_paymentNativeProxy);\r
chainlinkConversionPath = ChainlinkConversionPath(\r
_chainlinkConversionPath\r
);\r
transferOwnership(_owner);\r
batchFee = 0;\r
}\r
\r
/**\r
* This contract is non-payable.\r
* @dev See the end of `paymentNativeProxy.transferWithReferenceAndFee` where the leftover is given back.\r
*/\r
receive() external payable virtual {\r
require(msg.value == 0, "Non-payable");\r
}\r
\r
/**\r
* @notice Gets batch fee\r
*/\r
function getBatchFee() external view returns (uint256 data) {\r
return batchFee;\r
}\r
\r
/**\r
* @notice Send a batch of Native token payments with fees and paymentReferences to multiple accounts.\r
* If one payment fails, the whole batch reverts.\r
* @param requestDetails List of Native tokens requests to pay.\r
* @param skipFeeUSDLimit Setting the value to true skips the USD fee limit, and reduces gas consumption.\r
* @param feeAddress The fee recipient.\r
* @dev It uses NativeFeeProxy (EthereumFeeProxy) to pay an invoice and fees with a payment reference.\r
* Make sure: msg.value >= sum(_amouts)+sum(_feeAmounts)+sumBatchFeeAmount\r
*/\r
function batchNativePayments(\r
RequestDetail[] calldata requestDetails,\r
bool skipFeeUSDLimit,\r
address payable feeAddress\r
) public payable returns (uint256) {\r
return\r
_batchNativePayments(\r
requestDetails,\r
skipFeeUSDLimit,\r
0,\r
payable(feeAddress)\r
);\r
}\r
\r
/**\r
* @notice Send a batch of ERC20 payments with fees and paymentReferences to multiple accounts.\r
* @param requestDetails List of ERC20 requests to pay, with only one ERC20 token.\r
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
* Without paths, there is not a fee limitation, and it consumes less gas.\r
* @param feeAddress The fee recipient.\r
* @dev Uses ERC20FeeProxy to pay an invoice and fees, with a payment reference.\r
* Make sure this contract has enough allowance to spend the payer's token.\r
* Make sure the payer has enough tokens to pay the amount, the fee, and the batch fee.\r
*/\r
function batchERC20Payments(\r
RequestDetail[] calldata requestDetails,\r
address[][] calldata pathsToUSD,\r
address feeAddress\r
) public returns (uint256) {\r
return _batchERC20Payments(requestDetails, pathsToUSD, 0, feeAddress);\r
}\r
\r
/**\r
* @notice Send a batch of ERC20 payments with fees and paymentReferences to multiple accounts, with multiple tokens.\r
* @param requestDetails List of ERC20 requests to pay.\r
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
* Without paths, there is not a fee limitation, and it consumes less gas.\r
* @param feeAddress The fee recipient.\r
* @dev It uses ERC20FeeProxy to pay an invoice and fees, with a payment reference.\r
* Make sure this contract has enough allowance to spend the payer's token.\r
* Make sure the payer has enough tokens to pay the amount, the fee, and the batch fee.\r
*/\r
function batchMultiERC20Payments(\r
RequestDetail[] calldata requestDetails,\r
address[][] calldata pathsToUSD,\r
address feeAddress\r
) public returns (uint256) {\r
return\r
_batchMultiERC20Payments(requestDetails, pathsToUSD, 0, feeAddress);\r
}\r
\r
/**\r
* @notice Send a batch of Native token payments with fees and paymentReferences to multiple accounts.\r
* If one payment fails, the whole batch reverts.\r
* @param requestDetails List of Native tokens requests to pay.\r
* @param skipFeeUSDLimit Setting the value to true skips the USD fee limit, and reduces gas consumption.\r
* @param batchFeeAmountUSD The batch fee amount in USD already paid.\r
* @param feeAddress The fee recipient.\r
* @dev It uses NativeFeeProxy (EthereumFeeProxy) to pay an invoice and fees with a payment reference.\r
* Make sure: msg.value >= sum(_amouts)+sum(_feeAmounts)+sumBatchFeeAmount\r
*/\r
function _batchNativePayments(\r
RequestDetail[] calldata requestDetails,\r
bool skipFeeUSDLimit,\r
uint256 batchFeeAmountUSD,\r
address payable feeAddress\r
) internal returns (uint256) {\r
// amount is used to get the total amount and then used as batch fee amount\r
uint256 amount = 0;\r
\r
// Batch contract pays the requests thourgh NativeFeeProxy (EthFeeProxy)\r
for (uint256 i = 0; i < requestDetails.length; i++) {\r
RequestDetail calldata rD = requestDetails[i];\r
require(\r
address(this).balance >= rD.requestAmount + rD.feeAmount,\r
"Not enough funds"\r
);\r
amount += rD.requestAmount;\r
\r
paymentNativeProxy.transferWithReferenceAndFee{\r
value: rD.requestAmount + rD.feeAmount\r
}(\r
payable(rD.recipient),\r
rD.paymentReference,\r
rD.feeAmount,\r
payable(feeAddress)\r
);\r
}\r
\r
// amount is updated into batch fee amount\r
amount = (amount * batchFee) / feeDenominator;\r
if (skipFeeUSDLimit == false) {\r
(amount, batchFeeAmountUSD) = calculateBatchFeeToPay(\r
amount,\r
pathsNativeToUSD[0][0],\r
batchFeeAmountUSD,\r
pathsNativeToUSD\r
);\r
}\r
// Check that batch contract has enough funds to pay batch fee\r
require(\r
address(this).balance >= amount,\r
"Not enough funds for batch fee"\r
);\r
// Batch pays batch fee\r
feeAddress.transfer(amount);\r
\r
// Batch contract transfers the remaining Native tokens to the payer\r
if (transferBackRemainingNativeTokens && address(this).balance > 0) {\r
(bool sendBackSuccess, ) = payable(msg.sender).call{\r
value: address(this).balance\r
}("");\r
require(\r
sendBackSuccess,\r
"Could not send remaining funds to the payer"\r
);\r
}\r
return batchFeeAmountUSD;\r
}\r
\r
/**\r
* @notice Send a batch of ERC20 payments with fees and paymentReferences to multiple accounts.\r
* @param requestDetails List of ERC20 requests to pay, with only one ERC20 token.\r
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
* Without paths, there is not a fee limitation, and it consumes less gas.\r
* @param batchFeeAmountUSD The batch fee amount in USD already paid.\r
* @param feeAddress The fee recipient.\r
* @dev Uses ERC20FeeProxy to pay an invoice and fees, with a payment reference.\r
* Make sure this contract has enough allowance to spend the payer's token.\r
* Make sure the payer has enough tokens to pay the amount, the fee, and the batch fee.\r
*/\r
function _batchERC20Payments(\r
RequestDetail[] calldata requestDetails,\r
address[][] calldata pathsToUSD,\r
uint256 batchFeeAmountUSD,\r
address feeAddress\r
) internal returns (uint256) {\r
uint256 amountAndFee = 0;\r
uint256 batchFeeAmount = 0;\r
for (uint256 i = 0; i < requestDetails.length; i++) {\r
amountAndFee +=\r
requestDetails[i].requestAmount +\r
requestDetails[i].feeAmount;\r
batchFeeAmount += requestDetails[i].requestAmount;\r
}\r
batchFeeAmount = (batchFeeAmount * batchFee) / feeDenominator;\r
\r
// batchFeeToPay and batchFeeAmountUSD are updated if needed\r
(batchFeeAmount, batchFeeAmountUSD) = calculateBatchFeeToPay(\r
batchFeeAmount,\r
requestDetails[0].path[0],\r
batchFeeAmountUSD,\r
pathsToUSD\r
);\r
\r
IERC20 requestedToken = IERC20(requestDetails[0].path[0]);\r
\r
transferToContract(\r
requestedToken,\r
amountAndFee,\r
batchFeeAmount,\r
address(paymentErc20Proxy)\r
);\r
\r
// Payer pays batch fee amount\r
require(\r
safeTransferFrom(\r
requestDetails[0].path[0],\r
feeAddress,\r
batchFeeAmount\r
),\r
"Batch fee transferFrom() failed"\r
);\r
\r
// Batch contract pays the requests using Erc20FeeProxy\r
for (uint256 i = 0; i < requestDetails.length; i++) {\r
RequestDetail calldata rD = requestDetails[i];\r
paymentErc20Proxy.transferFromWithReferenceAndFee(\r
rD.path[0],\r
rD.recipient,\r
rD.requestAmount,\r
rD.paymentReference,\r
rD.feeAmount,\r
feeAddress\r
);\r
}\r
\r
return batchFeeAmountUSD;\r
}\r
\r
/**\r
* @notice Send a batch of ERC20 payments with fees and paymentReferences to multiple accounts, with multiple tokens.\r
* @param requestDetails List of ERC20 requests to pay.\r
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
* Without paths, there is not a fee limitation, and it consumes less gas.\r
* @param batchFeeAmountUSD The batch fee amount in USD already paid.\r
* @param feeAddress The fee recipient.\r
* @dev It uses ERC20FeeProxy to pay an invoice and fees, with a payment reference.\r
* Make sure this contract has enough allowance to spend the payer's token.\r
* Make sure the payer has enough tokens to pay the amount, the fee, and the batch fee.\r
*/\r
function _batchMultiERC20Payments(\r
RequestDetail[] calldata requestDetails,\r
address[][] calldata pathsToUSD,\r
uint256 batchFeeAmountUSD,\r
address feeAddress\r
) internal returns (uint256) {\r
Token[] memory uTokens = getUTokens(requestDetails);\r
\r
// The payer transfers tokens to the batch contract and pays batch fee\r
for (\r
uint256 i = 0;\r
i < uTokens.length && uTokens[i].amountAndFee > 0;\r
i++\r
) {\r
uTokens[i].batchFeeAmount =\r
(uTokens[i].batchFeeAmount * batchFee) /\r
feeDenominator;\r
IERC20 requestedToken = IERC20(uTokens[i].tokenAddress);\r
transferToContract(\r
requestedToken,\r
uTokens[i].amountAndFee,\r
uTokens[i].batchFeeAmount,\r
address(paymentErc20Proxy)\r
);\r
\r
// Payer pays batch fee amount\r
\r
uint256 batchFeeToPay = uTokens[i].batchFeeAmount;\r
\r
(batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay(\r
batchFeeToPay,\r
uTokens[i].tokenAddress,\r
batchFeeAmountUSD,\r
pathsToUSD\r
);\r
\r
require(\r
safeTransferFrom(\r
uTokens[i].tokenAddress,\r
feeAddress,\r
batchFeeToPay\r
),\r
"Batch fee transferFrom() failed"\r
);\r
}\r
\r
// Batch contract pays the requests using Erc20FeeProxy\r
for (uint256 i = 0; i < requestDetails.length; i++) {\r
RequestDetail calldata rD = requestDetails[i];\r
paymentErc20Proxy.transferFromWithReferenceAndFee(\r
rD.path[0],\r
rD.recipient,\r
rD.requestAmount,\r
rD.paymentReference,\r
rD.feeAmount,\r
feeAddress\r
);\r
}\r
return batchFeeAmountUSD;\r
}\r
\r
/*\r
* Helper functions\r
*/\r
\r
/**\r
* Top up the contract with enough `requestedToken` to pay `amountAndFee`.\r
* The contract is NOT topped-up for `batchFeeAmount`.\r
*\r
* It also performs a few checks:\r
* - checks that the batch contract has enough allowance from the payer\r
* - checks that the payer has enough funds, including batch fees\r
* - increases the allowance of the contract to use the payment proxy if needed\r
*\r
* @param requestedToken The token to pay\r
* @param amountAndFee The amount and the fee for a token to pay\r
* @param batchFeeAmount The batch fee amount for a token to pay\r
* @param paymentProxyAddress The payment proxy address used to pay\r
*/\r
function transferToContract(\r
IERC20 requestedToken,\r
uint256 amountAndFee,\r
uint256 batchFeeAmount,\r
address paymentProxyAddress\r
) internal {\r
// Check proxy's allowance from user\r
require(\r
requestedToken.allowance(msg.sender, address(this)) >= amountAndFee,\r
"Insufficient allowance for batch to pay"\r
);\r
// Check user's funds to pay amounts, it is an approximation for conversion payment\r
require(\r
requestedToken.balanceOf(msg.sender) >=\r
amountAndFee + batchFeeAmount,\r
"Not enough funds, including fees"\r
);\r
\r
// Transfer the amount and fees (no batch fees) required for the token on the batch contract\r
require(\r
safeTransferFrom(\r
address(requestedToken),\r
address(this),\r
amountAndFee\r
),\r
"payment transferFrom() failed"\r
);\r
\r
// Batch contract approves Erc20ConversionProxy to spend the token\r
if (\r
requestedToken.allowance(address(this), paymentProxyAddress) <\r
amountAndFee\r
) {\r
approvePaymentProxyToSpend(\r
address(requestedToken),\r
paymentProxyAddress\r
);\r
}\r
}\r
\r
/**\r
* It create a list of unique tokens used and the amounts associated.\r
* It only considers tokens having: requestAmount + feeAmount > 0.\r
* Regarding ERC20 no conversion payments:\r
* batchFeeAmount is the sum of requestAmount and feeAmount.\r
* Out of the function, batch fee rate is applied\r
* @param requestDetails List of requests to pay.\r
*/\r
function getUTokens(\r
RequestDetail[] calldata requestDetails\r
) internal pure returns (Token[] memory uTokens) {\r
// A list of unique tokens, with the sum of maxToSpend by token\r
uTokens = new Token[](requestDetails.length);\r
for (uint256 i = 0; i < requestDetails.length; i++) {\r
for (uint256 k = 0; k < requestDetails.length; k++) {\r
RequestDetail calldata rD = requestDetails[i];\r
// If the token is already in the existing uTokens list\r
if (uTokens[k].tokenAddress == rD.path[rD.path.length - 1]) {\r
if (rD.path.length > 1) {\r
uTokens[k].amountAndFee += rD.maxToSpend;\r
} else {\r
// It is not a conversion payment\r
uTokens[k].amountAndFee +=\r
rD.requestAmount +\r
rD.feeAmount;\r
uTokens[k].batchFeeAmount += rD.requestAmount;\r
}\r
break;\r
}\r
// If the token is not in the list (amountAndFee = 0)\r
else if (\r
uTokens[k].amountAndFee == 0 &&\r
(rD.maxToSpend > 0 || rD.requestAmount + rD.feeAmount > 0)\r
) {\r
uTokens[k].tokenAddress = rD.path[rD.path.length - 1];\r
\r
if (rD.path.length > 1) {\r
// amountAndFee is used to store _maxToSpend, useful to send enough tokens to this contract\r
uTokens[k].amountAndFee = rD.maxToSpend;\r
} else {\r
// It is not a conversion payment\r
uTokens[k].amountAndFee =\r
rD.requestAmount +\r
rD.feeAmount;\r
uTokens[k].batchFeeAmount = rD.requestAmount;\r
}\r
break;\r
}\r
}\r
}\r
}\r
\r
/**\r
* Calculate the batch fee amount to pay, using the USD fee limitation.\r
* Without pathsToUSD or a wrong one, the fee limitation is not applied.\r
* @param batchFeeToPay The amount of batch fee to pay\r
* @param tokenAddress The address of the token\r
* @param batchFeeAmountUSD The batch fee amount in USD already paid.\r
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
* Without paths, there is not a fee limitation, and it consumes less gas.\r
*/\r
function calculateBatchFeeToPay(\r
uint256 batchFeeToPay,\r
address tokenAddress,\r
uint256 batchFeeAmountUSD,\r
address[][] memory pathsToUSD\r
) internal view returns (uint256, uint256) {\r
// Fees are not limited if there is no pathsToUSD\r
// Excepted if batchFeeAmountUSD is already >= batchFeeAmountUSDLimit\r
if (\r
pathsToUSD.length == 0 && batchFeeAmountUSD < batchFeeAmountUSDLimit\r
) {\r
return (batchFeeToPay, batchFeeAmountUSD);\r
}\r
\r
// Apply the fee limit and calculate if needed batchFeeToPay\r
if (batchFeeAmountUSD < batchFeeAmountUSDLimit) {\r
for (uint256 i = 0; i < pathsToUSD.length; i++) {\r
// Check if the pathToUSD is right\r
if (\r
pathsToUSD[i][0] == tokenAddress &&\r
pathsToUSD[i][pathsToUSD[i].length - 1] == USDAddress\r
) {\r
(uint256 conversionUSD, ) = chainlinkConversionPath\r
.getConversion(batchFeeToPay, pathsToUSD[i]);\r
// Calculate the batch fee to pay, taking care of the batchFeeAmountUSDLimit\r
uint256 conversionToPayUSD = conversionUSD;\r
if (\r
batchFeeAmountUSD + conversionToPayUSD >\r
batchFeeAmountUSDLimit\r
) {\r
conversionToPayUSD =\r
batchFeeAmountUSDLimit -\r
batchFeeAmountUSD;\r
batchFeeToPay =\r
(batchFeeToPay * conversionToPayUSD) /\r
conversionUSD;\r
}\r
batchFeeAmountUSD += conversionToPayUSD;\r
// Add only once the fees\r
break;\r
}\r
}\r
} else {\r
batchFeeToPay = 0;\r
}\r
return (batchFeeToPay, batchFeeAmountUSD);\r
}\r
\r
/**\r
* @notice Authorizes the proxy to spend a new request currency (ERC20).\r
* @param _erc20Address Address of an ERC20 used as the request currency.\r
* @param _paymentErc20Proxy Address of the proxy.\r
*/\r
function approvePaymentProxyToSpend(\r
address _erc20Address,\r
address _paymentErc20Proxy\r
) internal {\r
IERC20 erc20 = IERC20(_erc20Address);\r
uint256 max = 2 ** 256 - 1;\r
erc20.safeApprove(address(_paymentErc20Proxy), max);\r
}\r
\r
/**\r
* @notice Call transferFrom ERC20 function and validates the return data of a ERC20 contract call.\r
* @dev This is necessary because of non-standard ERC20 tokens that don't have a return value.\r
* @return result The return value of the ERC20 call, returning true for non-standard tokens\r
*/\r
function safeTransferFrom(\r
address _tokenAddress,\r
address _to,\r
uint256 _amount\r
) internal returns (bool result) {\r
/* solium-disable security/no-inline-assembly */\r
// check if the address is a contract\r
assembly {\r
if iszero(extcodesize(_tokenAddress)) {\r
revert(0, 0)\r
}\r
}\r
\r
// solium-disable-next-line security/no-low-level-calls\r
(bool success, ) = _tokenAddress.call(\r
abi.encodeWithSignature(\r
"transferFrom(address,address,uint256)",\r
msg.sender,\r
_to,\r
_amount\r
)\r
);\r
\r
assembly {\r
switch returndatasize()\r
case 0 {\r
// Not a standard erc20\r
result := 1\r
}\r
case 32 {\r
// Standard erc20\r
returndatacopy(0, 0, 32)\r
result := mload(0)\r
}\r
default {\r
// Anything else, should revert for safety\r
revert(0, 0)\r
}\r
}\r
\r
require(success, "transferFrom() has been reverted");\r
\r
/* solium-enable security/no-inline-assembly */\r
return result;\r
}\r
\r
/*\r
* Admin functions to edit the proxies address and fees\r
*/\r
\r
/**\r
* @notice Fees added when using Erc20/Native batch function
Submitted on: 2025-11-03 14:06:35
Comments
Log in to comment.
No comments yet.