@@ -15,8 +15,7 @@ import './BatchNoConversionPayments.sol';
15
15
* - fees: conversion proxy fees and additional batch conversion fees are paid to the same address.
16
16
* batchRouter is the main function to batch all kinds of payments at once.
17
17
* If one transaction of the batch fails, all transactions are reverted.
18
- * @dev Note that fees have 4 decimals (instead of 3 in a previous version)
19
- * batchRouter is the main function, but other batch payment functions are "public" in order to do
18
+ * @dev batchRouter is the main function, but other batch payment functions are "public" in order to do
20
19
* gas optimization in some cases.
21
20
*/
22
21
contract BatchConversionPayments is BatchNoConversionPayments {
@@ -25,18 +24,14 @@ contract BatchConversionPayments is BatchNoConversionPayments {
25
24
IERC20ConversionProxy public paymentErc20ConversionProxy;
26
25
IEthConversionProxy public paymentEthConversionProxy;
27
26
28
- uint256 public batchConversionFee;
29
-
30
27
/**
31
28
* @dev Used by the batchRouter to handle information for heterogeneous batches, grouped by payment network.
32
29
* - paymentNetworkId: from 0 to 4, cf. `batchRouter()` method.
33
- * - conversionDetails all the data required for conversion requests to be paid, for paymentNetworkId = 0 or 4
34
- * - cryptoDetails all the data required to pay requests without conversion, for paymentNetworkId = 1, 2, or 3
30
+ * - conversionDetails all the data required for conversion and no conversion requests to be paid
35
31
*/
36
32
struct MetaDetail {
37
33
uint256 paymentNetworkId;
38
34
ConversionDetail[] conversionDetails;
39
- CryptoDetails cryptoDetails;
40
35
}
41
36
42
37
/**
@@ -64,31 +59,33 @@ contract BatchConversionPayments is BatchNoConversionPayments {
64
59
{
65
60
paymentErc20ConversionProxy = IERC20ConversionProxy (_paymentErc20ConversionProxy);
66
61
paymentEthConversionProxy = IEthConversionProxy (_paymentEthConversionFeeProxy);
67
- batchConversionFee = 0 ;
68
62
}
69
63
70
64
/**
71
65
* @notice Batch payments on different payment networks at once.
72
- * @param metaDetails contains paymentNetworkId, conversionDetails, and cryptoDetails
66
+ * @param metaDetails contains paymentNetworkId and conversionDetails
73
67
* - batchMultiERC20ConversionPayments, paymentNetworkId=0
74
68
* - batchERC20Payments, paymentNetworkId=1
75
69
* - batchMultiERC20Payments, paymentNetworkId=2
76
70
* - batchEthPayments, paymentNetworkId=3
77
71
* - batchEthConversionPayments, paymentNetworkId=4
78
- * If metaDetails use paymentNetworkId = 4, it must be at the end of the list, or the transaction can be reverted
79
- * @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees, caution,
80
- Caution, the calculation of batchFeeAmountUSD which allows to limit the batch fees takes only
81
- into consideration these paths. Without paths, there is not limitation.
82
- * @param _feeAddress The address where fees should be paid
72
+ * If metaDetails use paymentNetworkId = 4, it must be at the end of the list, or the transaction can be reverted.
73
+ * @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.
74
+ * Without paths, there is not limitation.
75
+ * @param feeAddress The address where fees should be paid
83
76
* @dev batchRouter only reduces gas consumption when using more than a single payment network.
84
77
* For single payment network payments, it is more efficient to use the suited batch function.
85
78
*/
86
79
function batchRouter (
87
80
MetaDetail[] calldata metaDetails ,
88
81
address [][] calldata pathsToUSD ,
89
- address _feeAddress
82
+ address feeAddress
90
83
) external payable {
91
84
require (metaDetails.length < 6 , 'more than 5 metaDetails ' );
85
+ if (pathsToUSD.length > 0 ) {
86
+ // Set to true to limit the batch fee to pay
87
+ batchPaymentOrigin = true ;
88
+ }
92
89
uint256 batchFeeAmountUSD = 0 ;
93
90
for (uint256 i = 0 ; i < metaDetails.length ; i++ ) {
94
91
MetaDetail calldata metaConversionDetail = metaDetails[i];
@@ -97,74 +94,84 @@ contract BatchConversionPayments is BatchNoConversionPayments {
97
94
metaConversionDetail.conversionDetails,
98
95
batchFeeAmountUSD,
99
96
pathsToUSD,
100
- _feeAddress
97
+ feeAddress
101
98
);
102
99
} else if (metaConversionDetail.paymentNetworkId == 1 ) {
103
- batchERC20Payments (
100
+ batchFeeAmountUSD += batchERC20Payments (
104
101
metaConversionDetail.conversionDetails,
105
102
pathsToUSD,
106
103
batchFeeAmountUSD,
107
- _feeAddress
104
+ feeAddress
108
105
);
109
106
} else if (metaConversionDetail.paymentNetworkId == 2 ) {
110
107
batchFeeAmountUSD += batchMultiERC20Payments (
111
108
metaConversionDetail.conversionDetails,
112
109
pathsToUSD,
113
110
batchFeeAmountUSD,
114
- _feeAddress
111
+ feeAddress
115
112
);
116
113
} else if (metaConversionDetail.paymentNetworkId == 3 ) {
117
114
if (metaDetails[metaDetails.length - 1 ].paymentNetworkId == 4 ) {
118
115
// Set to false only if batchEthConversionPayments is called after this function
119
116
transferBackRemainingEth = false ;
120
117
}
121
- batchEthPayments (
122
- metaConversionDetail.cryptoDetails.recipients,
123
- metaConversionDetail.cryptoDetails.amounts,
124
- metaConversionDetail.cryptoDetails.paymentReferences,
125
- metaConversionDetail.cryptoDetails.feeAmounts,
126
- payable (_feeAddress)
118
+ batchFeeAmountUSD += batchEthPayments (
119
+ metaConversionDetail.conversionDetails,
120
+ batchFeeAmountUSD,
121
+ payable (feeAddress)
127
122
);
128
123
if (metaDetails[metaDetails.length - 1 ].paymentNetworkId == 4 ) {
129
124
transferBackRemainingEth = true ;
130
125
}
131
126
} else if (metaConversionDetail.paymentNetworkId == 4 ) {
132
- batchEthConversionPayments (metaConversionDetail.conversionDetails, payable (_feeAddress));
127
+ batchFeeAmountUSD += batchEthConversionPayments (
128
+ metaConversionDetail.conversionDetails,
129
+ batchFeeAmountUSD,
130
+ payable (feeAddress)
131
+ );
133
132
} else {
134
- revert ('wrong paymentNetworkId ' );
133
+ revert ('Wrong paymentNetworkId ' );
135
134
}
136
135
}
136
+ if (pathsToUSD.length > 0 ) {
137
+ batchPaymentOrigin = false ;
138
+ }
137
139
}
138
140
139
141
/**
140
142
* @notice Send a batch of ERC20 payments with amounts based on a request
141
143
* currency (e.g. fiat), with fees and paymentReferences to multiple accounts, with multiple tokens.
142
- * @param conversionDetails list of requestInfo, each one containing all the information of a request
143
- * @param batchFeeAmountUSD The batchFeeAmountUSD already paid
144
- * @param pathsToUSD The list of paths into USD for every token
145
- * @param _feeAddress The fee recipient
144
+ * @param conversionDetails List of ERC20 requests denominated in fiat to pay.
145
+ * @param batchFeeAmountUSD The batch fee amount in USD already paid.
146
+ * @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.
147
+ * Without paths, there is not limitation.
148
+ * @param feeAddress The fee recipient
146
149
*/
147
150
function batchMultiERC20ConversionPayments (
148
151
ConversionDetail[] calldata conversionDetails ,
149
152
uint256 batchFeeAmountUSD ,
150
153
address [][] calldata pathsToUSD ,
151
- address _feeAddress
154
+ address feeAddress
152
155
) public returns (uint256 ) {
156
+ // Avoid the possibility to manually put high value to batchFeeAmountUSD
157
+ if (batchPaymentOrigin != true ) {
158
+ batchFeeAmountUSD = 0 ;
159
+ }
153
160
Token[] memory uTokens = getUTokens (conversionDetails);
154
161
155
162
IERC20 requestedToken;
156
163
// For each token: check allowance, transfer funds on the contract and approve the paymentProxy to spend if needed
157
164
for (uint256 k = 0 ; k < uTokens.length && uTokens[k].amountAndFee > 0 ; k++ ) {
158
165
requestedToken = IERC20 (uTokens[k].tokenAddress);
159
- uTokens[k].batchFeeAmount = (uTokens[k].amountAndFee * batchConversionFee ) / tenThousand ;
166
+ uTokens[k].batchFeeAmount = (uTokens[k].amountAndFee * batchFee ) / feeDenominator ;
160
167
// Check proxy's allowance from user, and user's funds to pay approximated amounts.
161
168
require (
162
169
requestedToken.allowance (msg .sender , address (this )) >= uTokens[k].amountAndFee,
163
170
'Insufficient allowance for batch to pay '
164
171
);
165
172
require (
166
173
requestedToken.balanceOf (msg .sender ) >= uTokens[k].amountAndFee + uTokens[k].batchFeeAmount,
167
- 'not enough funds, including fees '
174
+ 'Not enough funds, including fees '
168
175
);
169
176
170
177
// Transfer the amount and fee required for the token on the batch conversion contract
@@ -191,7 +198,7 @@ contract BatchConversionPayments is BatchNoConversionPayments {
191
198
rI.path,
192
199
rI.paymentReference,
193
200
rI.feeAmount,
194
- _feeAddress ,
201
+ feeAddress ,
195
202
rI.maxToSpend,
196
203
rI.maxRateTimespan
197
204
);
@@ -208,8 +215,9 @@ contract BatchConversionPayments is BatchNoConversionPayments {
208
215
requestedToken.safeTransfer (msg .sender , excessAmount);
209
216
}
210
217
211
- uint256 batchFeeToPay = ((uTokens[k].amountAndFee - excessAmount) * batchConversionFee) /
212
- tenThousand;
218
+ // Calculate batch fee to pay
219
+ uint256 batchFeeToPay = ((uTokens[k].amountAndFee - excessAmount) * batchFee) /
220
+ feeDenominator;
213
221
214
222
(batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay (
215
223
batchFeeToPay,
@@ -220,8 +228,8 @@ contract BatchConversionPayments is BatchNoConversionPayments {
220
228
221
229
// Payer pays the exact batch fees amount
222
230
require (
223
- safeTransferFrom (uTokens[k].tokenAddress, _feeAddress , batchFeeToPay),
224
- 'batch fee transferFrom() failed '
231
+ safeTransferFrom (uTokens[k].tokenAddress, feeAddress , batchFeeToPay),
232
+ 'Batch fee transferFrom() failed '
225
233
);
226
234
}
227
235
return batchFeeAmountUSD;
@@ -230,9 +238,9 @@ contract BatchConversionPayments is BatchNoConversionPayments {
230
238
/**
231
239
* @notice Send a batch of ETH conversion payments with fees and paymentReferences to multiple accounts.
232
240
* If one payment fails, the whole batch is reverted.
233
- * @param conversionDetails List of requestInfos, each one containing all the information of a request .
234
- * _maxToSpend is not used in this function .
235
- * @param _feeAddress The fee recipient.
241
+ * @param conversionDetails List of ETH requests denominated in fiat to pay .
242
+ * @param batchFeeAmountUSD The batch fee amount in USD already paid .
243
+ * @param feeAddress The fee recipient.
236
244
* @dev It uses EthereumConversionProxy to pay an invoice and fees.
237
245
* Please:
238
246
* Note that if there is not enough ether attached to the function call,
@@ -241,50 +249,55 @@ contract BatchConversionPayments is BatchNoConversionPayments {
241
249
*/
242
250
function batchEthConversionPayments (
243
251
ConversionDetail[] calldata conversionDetails ,
244
- address payable _feeAddress
245
- ) public payable {
252
+ uint256 batchFeeAmountUSD ,
253
+ address payable feeAddress
254
+ ) public payable returns (uint256 ) {
255
+ // Avoid the possibility to manually put high value to batchFeeAmountUSD
256
+ if (batchPaymentOrigin != true ) {
257
+ batchFeeAmountUSD = 0 ;
258
+ }
246
259
uint256 contractBalance = address (this ).balance;
247
260
payerAuthorized = true ;
248
261
249
262
// Batch contract pays the requests through EthConversionProxy
250
263
for (uint256 i = 0 ; i < conversionDetails.length ; i++ ) {
264
+ ConversionDetail memory cD = conversionDetails[i];
251
265
paymentEthConversionProxy.transferWithReferenceAndFee {value: address (this ).balance}(
252
- payable (conversionDetails[i] .recipient),
253
- conversionDetails[i] .requestAmount,
254
- conversionDetails[i] .path,
255
- conversionDetails[i] .paymentReference,
256
- conversionDetails[i] .feeAmount,
257
- _feeAddress ,
258
- conversionDetails[i] .maxRateTimespan
266
+ payable (cD .recipient),
267
+ cD .requestAmount,
268
+ cD .path,
269
+ cD .paymentReference,
270
+ cD .feeAmount,
271
+ feeAddress ,
272
+ cD .maxRateTimespan
259
273
);
260
274
}
261
275
262
- // Check that batch contract has enough funds to pay batch conversion fees
263
- uint256 amountBatchFees = (((contractBalance - address (this ).balance)) * batchConversionFee) /
264
- tenThousand;
265
- require (address (this ).balance >= amountBatchFees, 'not enough funds for batch conversion fees ' );
266
-
267
276
// Batch contract pays batch fee
268
- _feeAddress.transfer (amountBatchFees);
277
+ uint256 batchFeeToPay = (((contractBalance - address (this ).balance)) * batchFee) /
278
+ feeDenominator;
279
+
280
+ (batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay (
281
+ batchFeeToPay,
282
+ pathsEthToUSD[0 ][0 ],
283
+ batchFeeAmountUSD,
284
+ pathsEthToUSD
285
+ );
286
+ require (address (this ).balance >= batchFeeToPay, 'Not enough funds for batch conversion fees ' );
287
+ feeAddress.transfer (batchFeeToPay);
269
288
270
289
// Batch contract transfers the remaining ethers to the payer
271
290
(bool sendBackSuccess , ) = payable (msg .sender ).call {value: address (this ).balance}('' );
272
291
require (sendBackSuccess, 'Could not send remaining funds to the payer ' );
273
292
payerAuthorized = false ;
293
+
294
+ return batchFeeAmountUSD;
274
295
}
275
296
276
297
/*
277
- * Admin functions to edit the conversion proxies address and fees
298
+ * Admin functions to edit the conversion proxies address and fees.
278
299
*/
279
300
280
- /**
281
- * @notice fees added when using Erc20/Eth conversion batch functions
282
- * @param _batchConversionFee between 0 and 10000, i.e: batchFee = 50 represent 0.50% of fees
283
- */
284
- function setBatchConversionFee (uint256 _batchConversionFee ) external onlyOwner {
285
- batchConversionFee = _batchConversionFee;
286
- }
287
-
288
301
/**
289
302
* @param _paymentErc20ConversionProxy The address of the ERC20 Conversion payment proxy to use.
290
303
* Update cautiously, the proxy has to match the invoice proxy.
@@ -302,8 +315,8 @@ contract BatchConversionPayments is BatchNoConversionPayments {
302
315
}
303
316
304
317
/**
305
- * @notice Update the conversion path contract used to fetch conversions
306
- * @param _chainlinkConversionPathAddress address of the conversion path contract
318
+ * @notice Update the conversion path contract used to fetch conversions.
319
+ * @param _chainlinkConversionPathAddress The address of the conversion path contract.
307
320
*/
308
321
function setConversionPathAddress (address _chainlinkConversionPathAddress ) external onlyOwner {
309
322
chainlinkConversionPath = ChainlinkConversionPath (_chainlinkConversionPathAddress);
0 commit comments