Skip to content

Commit 53e75c8

Browse files
committed
functions and test working
1 parent 59a1485 commit 53e75c8

File tree

8 files changed

+734
-508
lines changed

8 files changed

+734
-508
lines changed

packages/smart-contracts/scripts/test-deploy-batch-conversion-deployment.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,12 @@ export async function deployBatchConversionPayment(
7676
// Initialize batch conversion fee, useful to others packages.
7777
const batchConversion = batchConversionPaymentsArtifact.connect(hre.network.name, owner);
7878
await batchConversion.connect(owner).setBatchFee(30);
79-
await batchConversion.connect(owner).setBatchConversionFee(30);
80-
await batchConversion.connect(owner).setUSDAddress(currencyManager.fromSymbol('USD')!.hash);
79+
await batchConversion
80+
.connect(owner)
81+
.setETHAndUSDAddress(
82+
currencyManager.fromSymbol('ETH')!.hash,
83+
currencyManager.fromSymbol('USD')!.hash,
84+
);
8185
await batchConversion.connect(owner).setBatchFeeAmountUSDLimit(300); // * 1_000_000_000_000_000_000);
8286

8387
// ----------------------------------

packages/smart-contracts/src/contracts/BatchConversionPayments.sol

Lines changed: 81 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ import './BatchNoConversionPayments.sol';
1515
* - fees: conversion proxy fees and additional batch conversion fees are paid to the same address.
1616
* batchRouter is the main function to batch all kinds of payments at once.
1717
* 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
2019
* gas optimization in some cases.
2120
*/
2221
contract BatchConversionPayments is BatchNoConversionPayments {
@@ -25,18 +24,14 @@ contract BatchConversionPayments is BatchNoConversionPayments {
2524
IERC20ConversionProxy public paymentErc20ConversionProxy;
2625
IEthConversionProxy public paymentEthConversionProxy;
2726

28-
uint256 public batchConversionFee;
29-
3027
/**
3128
* @dev Used by the batchRouter to handle information for heterogeneous batches, grouped by payment network.
3229
* - 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
3531
*/
3632
struct MetaDetail {
3733
uint256 paymentNetworkId;
3834
ConversionDetail[] conversionDetails;
39-
CryptoDetails cryptoDetails;
4035
}
4136

4237
/**
@@ -64,31 +59,33 @@ contract BatchConversionPayments is BatchNoConversionPayments {
6459
{
6560
paymentErc20ConversionProxy = IERC20ConversionProxy(_paymentErc20ConversionProxy);
6661
paymentEthConversionProxy = IEthConversionProxy(_paymentEthConversionFeeProxy);
67-
batchConversionFee = 0;
6862
}
6963

7064
/**
7165
* @notice Batch payments on different payment networks at once.
72-
* @param metaDetails contains paymentNetworkId, conversionDetails, and cryptoDetails
66+
* @param metaDetails contains paymentNetworkId and conversionDetails
7367
* - batchMultiERC20ConversionPayments, paymentNetworkId=0
7468
* - batchERC20Payments, paymentNetworkId=1
7569
* - batchMultiERC20Payments, paymentNetworkId=2
7670
* - batchEthPayments, paymentNetworkId=3
7771
* - 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
8376
* @dev batchRouter only reduces gas consumption when using more than a single payment network.
8477
* For single payment network payments, it is more efficient to use the suited batch function.
8578
*/
8679
function batchRouter(
8780
MetaDetail[] calldata metaDetails,
8881
address[][] calldata pathsToUSD,
89-
address _feeAddress
82+
address feeAddress
9083
) external payable {
9184
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+
}
9289
uint256 batchFeeAmountUSD = 0;
9390
for (uint256 i = 0; i < metaDetails.length; i++) {
9491
MetaDetail calldata metaConversionDetail = metaDetails[i];
@@ -97,74 +94,84 @@ contract BatchConversionPayments is BatchNoConversionPayments {
9794
metaConversionDetail.conversionDetails,
9895
batchFeeAmountUSD,
9996
pathsToUSD,
100-
_feeAddress
97+
feeAddress
10198
);
10299
} else if (metaConversionDetail.paymentNetworkId == 1) {
103-
batchERC20Payments(
100+
batchFeeAmountUSD += batchERC20Payments(
104101
metaConversionDetail.conversionDetails,
105102
pathsToUSD,
106103
batchFeeAmountUSD,
107-
_feeAddress
104+
feeAddress
108105
);
109106
} else if (metaConversionDetail.paymentNetworkId == 2) {
110107
batchFeeAmountUSD += batchMultiERC20Payments(
111108
metaConversionDetail.conversionDetails,
112109
pathsToUSD,
113110
batchFeeAmountUSD,
114-
_feeAddress
111+
feeAddress
115112
);
116113
} else if (metaConversionDetail.paymentNetworkId == 3) {
117114
if (metaDetails[metaDetails.length - 1].paymentNetworkId == 4) {
118115
// Set to false only if batchEthConversionPayments is called after this function
119116
transferBackRemainingEth = false;
120117
}
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)
127122
);
128123
if (metaDetails[metaDetails.length - 1].paymentNetworkId == 4) {
129124
transferBackRemainingEth = true;
130125
}
131126
} else if (metaConversionDetail.paymentNetworkId == 4) {
132-
batchEthConversionPayments(metaConversionDetail.conversionDetails, payable(_feeAddress));
127+
batchFeeAmountUSD += batchEthConversionPayments(
128+
metaConversionDetail.conversionDetails,
129+
batchFeeAmountUSD,
130+
payable(feeAddress)
131+
);
133132
} else {
134-
revert('wrong paymentNetworkId');
133+
revert('Wrong paymentNetworkId');
135134
}
136135
}
136+
if (pathsToUSD.length > 0) {
137+
batchPaymentOrigin = false;
138+
}
137139
}
138140

139141
/**
140142
* @notice Send a batch of ERC20 payments with amounts based on a request
141143
* 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
146149
*/
147150
function batchMultiERC20ConversionPayments(
148151
ConversionDetail[] calldata conversionDetails,
149152
uint256 batchFeeAmountUSD,
150153
address[][] calldata pathsToUSD,
151-
address _feeAddress
154+
address feeAddress
152155
) public returns (uint256) {
156+
// Avoid the possibility to manually put high value to batchFeeAmountUSD
157+
if (batchPaymentOrigin != true) {
158+
batchFeeAmountUSD = 0;
159+
}
153160
Token[] memory uTokens = getUTokens(conversionDetails);
154161

155162
IERC20 requestedToken;
156163
// For each token: check allowance, transfer funds on the contract and approve the paymentProxy to spend if needed
157164
for (uint256 k = 0; k < uTokens.length && uTokens[k].amountAndFee > 0; k++) {
158165
requestedToken = IERC20(uTokens[k].tokenAddress);
159-
uTokens[k].batchFeeAmount = (uTokens[k].amountAndFee * batchConversionFee) / tenThousand;
166+
uTokens[k].batchFeeAmount = (uTokens[k].amountAndFee * batchFee) / feeDenominator;
160167
// Check proxy's allowance from user, and user's funds to pay approximated amounts.
161168
require(
162169
requestedToken.allowance(msg.sender, address(this)) >= uTokens[k].amountAndFee,
163170
'Insufficient allowance for batch to pay'
164171
);
165172
require(
166173
requestedToken.balanceOf(msg.sender) >= uTokens[k].amountAndFee + uTokens[k].batchFeeAmount,
167-
'not enough funds, including fees'
174+
'Not enough funds, including fees'
168175
);
169176

170177
// Transfer the amount and fee required for the token on the batch conversion contract
@@ -191,7 +198,7 @@ contract BatchConversionPayments is BatchNoConversionPayments {
191198
rI.path,
192199
rI.paymentReference,
193200
rI.feeAmount,
194-
_feeAddress,
201+
feeAddress,
195202
rI.maxToSpend,
196203
rI.maxRateTimespan
197204
);
@@ -208,8 +215,9 @@ contract BatchConversionPayments is BatchNoConversionPayments {
208215
requestedToken.safeTransfer(msg.sender, excessAmount);
209216
}
210217

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;
213221

214222
(batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay(
215223
batchFeeToPay,
@@ -220,8 +228,8 @@ contract BatchConversionPayments is BatchNoConversionPayments {
220228

221229
// Payer pays the exact batch fees amount
222230
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'
225233
);
226234
}
227235
return batchFeeAmountUSD;
@@ -230,9 +238,9 @@ contract BatchConversionPayments is BatchNoConversionPayments {
230238
/**
231239
* @notice Send a batch of ETH conversion payments with fees and paymentReferences to multiple accounts.
232240
* 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.
236244
* @dev It uses EthereumConversionProxy to pay an invoice and fees.
237245
* Please:
238246
* Note that if there is not enough ether attached to the function call,
@@ -241,50 +249,55 @@ contract BatchConversionPayments is BatchNoConversionPayments {
241249
*/
242250
function batchEthConversionPayments(
243251
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+
}
246259
uint256 contractBalance = address(this).balance;
247260
payerAuthorized = true;
248261

249262
// Batch contract pays the requests through EthConversionProxy
250263
for (uint256 i = 0; i < conversionDetails.length; i++) {
264+
ConversionDetail memory cD = conversionDetails[i];
251265
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
259273
);
260274
}
261275

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-
267276
// 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);
269288

270289
// Batch contract transfers the remaining ethers to the payer
271290
(bool sendBackSuccess, ) = payable(msg.sender).call{value: address(this).balance}('');
272291
require(sendBackSuccess, 'Could not send remaining funds to the payer');
273292
payerAuthorized = false;
293+
294+
return batchFeeAmountUSD;
274295
}
275296

276297
/*
277-
* Admin functions to edit the conversion proxies address and fees
298+
* Admin functions to edit the conversion proxies address and fees.
278299
*/
279300

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-
288301
/**
289302
* @param _paymentErc20ConversionProxy The address of the ERC20 Conversion payment proxy to use.
290303
* Update cautiously, the proxy has to match the invoice proxy.
@@ -302,8 +315,8 @@ contract BatchConversionPayments is BatchNoConversionPayments {
302315
}
303316

304317
/**
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.
307320
*/
308321
function setConversionPathAddress(address _chainlinkConversionPathAddress) external onlyOwner {
309322
chainlinkConversionPath = ChainlinkConversionPath(_chainlinkConversionPathAddress);

0 commit comments

Comments
 (0)