Skip to content

Commit 69d76a7

Browse files
committed
refacto smart contract and optimize gas
1 parent 53e75c8 commit 69d76a7

File tree

3 files changed

+83
-88
lines changed

3 files changed

+83
-88
lines changed

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

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,13 @@ contract BatchConversionPayments is BatchNoConversionPayments {
8282
address feeAddress
8383
) external payable {
8484
require(metaDetails.length < 6, 'more than 5 metaDetails');
85-
if (pathsToUSD.length > 0) {
85+
86+
// Check that there are paths to USD, and more than one paymentNetworkId
87+
if (pathsToUSD.length > 0 && metaDetails.length > 1) {
8688
// Set to true to limit the batch fee to pay
8789
batchPaymentOrigin = true;
8890
}
91+
8992
uint256 batchFeeAmountUSD = 0;
9093
for (uint256 i = 0; i < metaDetails.length; i++) {
9194
MetaDetail calldata metaConversionDetail = metaDetails[i];
@@ -133,7 +136,7 @@ contract BatchConversionPayments is BatchNoConversionPayments {
133136
revert('Wrong paymentNetworkId');
134137
}
135138
}
136-
if (pathsToUSD.length > 0) {
139+
if (pathsToUSD.length > 0 && metaDetails.length > 1) {
137140
batchPaymentOrigin = false;
138141
}
139142
}
@@ -162,31 +165,14 @@ contract BatchConversionPayments is BatchNoConversionPayments {
162165
IERC20 requestedToken;
163166
// For each token: check allowance, transfer funds on the contract and approve the paymentProxy to spend if needed
164167
for (uint256 k = 0; k < uTokens.length && uTokens[k].amountAndFee > 0; k++) {
165-
requestedToken = IERC20(uTokens[k].tokenAddress);
166168
uTokens[k].batchFeeAmount = (uTokens[k].amountAndFee * batchFee) / feeDenominator;
167-
// Check proxy's allowance from user, and user's funds to pay approximated amounts.
168-
require(
169-
requestedToken.allowance(msg.sender, address(this)) >= uTokens[k].amountAndFee,
170-
'Insufficient allowance for batch to pay'
171-
);
172-
require(
173-
requestedToken.balanceOf(msg.sender) >= uTokens[k].amountAndFee + uTokens[k].batchFeeAmount,
174-
'Not enough funds, including fees'
175-
);
176-
177-
// Transfer the amount and fee required for the token on the batch conversion contract
178-
require(
179-
safeTransferFrom(uTokens[k].tokenAddress, address(this), uTokens[k].amountAndFee),
180-
'payment transferFrom() failed'
169+
requestedToken = IERC20(uTokens[k].tokenAddress);
170+
contractAllowanceApprovalTransfer(
171+
requestedToken,
172+
uTokens[k].amountAndFee,
173+
uTokens[k].batchFeeAmount,
174+
address(paymentErc20ConversionProxy)
181175
);
182-
183-
// Batch contract approves Erc20ConversionProxy to spend the token
184-
if (
185-
requestedToken.allowance(address(this), address(paymentErc20ConversionProxy)) <
186-
uTokens[k].amountAndFee
187-
) {
188-
approvePaymentProxyToSpend(uTokens[k].tokenAddress, address(paymentErc20ConversionProxy));
189-
}
190176
}
191177

192178
// Batch pays the requests using Erc20ConversionFeeProxy

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

Lines changed: 70 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ contract BatchNoConversionPayments is Ownable {
3333
uint256 internal feeDenominator = 10000;
3434

3535
/** payerAuthorized is set to true only when needed for batch Eth conversion */
36-
bool internal payerAuthorized;
36+
bool internal payerAuthorized = false;
3737
/** batchPayment function is the caller */
38-
bool internal batchPaymentOrigin;
38+
bool internal batchPaymentOrigin = false;
3939

4040
/** transferBackRemainingEth is set to false only if the payer use batchRouter
4141
and call both batchEthPayments and batchConversionEthPaymentsWithReference */
@@ -63,7 +63,7 @@ contract BatchNoConversionPayments is Ownable {
6363
* feeAmount: The fee amount, denominated in the first currency of `path` for conversion payment
6464
* maxToSpend: Only for conversion payment:
6565
* Maximum amount the payer wants to spend, denominated in the last currency of `path`:
66-
* it includes fee proxy but NOT the batchFee
66+
* it includes fee proxy but NOT the batch fees to pay
6767
* maxRateTimespan: Only for conversion payment:
6868
* Max acceptable times span for conversion rates, ignored if zero
6969
*/
@@ -181,31 +181,37 @@ contract BatchNoConversionPayments is Ownable {
181181
if (batchPaymentOrigin != true) {
182182
batchFeeAmountUSD = 0;
183183
}
184-
// amount is used to get the total amount, and then used as batch fee amount
185-
uint256 amount = 0;
186184
uint256 amountAndFee = 0;
185+
uint256 batchFeeAmount = 0;
187186
for (uint256 i = 0; i < conversionDetails.length; i++) {
188-
amount += conversionDetails[i].requestAmount;
189187
amountAndFee += conversionDetails[i].requestAmount + conversionDetails[i].feeAmount;
188+
batchFeeAmount += conversionDetails[i].requestAmount;
190189
}
190+
batchFeeAmount = (batchFeeAmount * batchFee) / feeDenominator;
191+
192+
// batchFeeToPay and batchFeeAmountUSD are updated if needed
193+
(batchFeeAmount, batchFeeAmountUSD) = calculateBatchFeeToPay(
194+
batchFeeAmount,
195+
conversionDetails[0].path[0],
196+
batchFeeAmountUSD,
197+
pathsToUSD
198+
);
191199

192-
// Transfer the amount and fee from the payer to the batch contract
193200
IERC20 requestedToken = IERC20(conversionDetails[0].path[0]);
194-
require(
195-
requestedToken.allowance(msg.sender, address(this)) >= amountAndFee,
196-
'Insufficient allowance for batch to pay'
201+
202+
contractAllowanceApprovalTransfer(
203+
requestedToken,
204+
amountAndFee,
205+
batchFeeAmount,
206+
address(paymentErc20Proxy)
197207
);
198-
require(requestedToken.balanceOf(msg.sender) >= amountAndFee, 'Not enough funds');
208+
209+
// Payer pays batch fee amount
199210
require(
200-
safeTransferFrom(conversionDetails[0].path[0], address(this), amountAndFee),
201-
'payment transferFrom() failed'
211+
safeTransferFrom(conversionDetails[0].path[0], feeAddress, batchFeeAmount),
212+
'Batch fee transferFrom() failed'
202213
);
203214

204-
// Batch contract approve Erc20FeeProxy to spend the token
205-
if (requestedToken.allowance(address(this), address(paymentErc20Proxy)) < amountAndFee) {
206-
approvePaymentProxyToSpend(address(requestedToken), address(paymentErc20Proxy));
207-
}
208-
209215
// Batch contract pays the requests using Erc20FeeProxy
210216
for (uint256 i = 0; i < conversionDetails.length; i++) {
211217
ConversionDetail memory cD = conversionDetails[i];
@@ -219,24 +225,6 @@ contract BatchNoConversionPayments is Ownable {
219225
);
220226
}
221227

222-
// amount is updated into batch fee amount
223-
amount = (amount * batchFee) / feeDenominator;
224-
// Check if the payer has enough funds to pay batch fee
225-
require(requestedToken.balanceOf(msg.sender) >= amount, 'Not enough funds for the batch fee');
226-
227-
// Payer pays batch fee amount
228-
// amount that represents batchFeeToPay updated if needed
229-
(amount, batchFeeAmountUSD) = calculateBatchFeeToPay(
230-
amount,
231-
conversionDetails[0].path[0],
232-
batchFeeAmountUSD,
233-
pathsToUSD
234-
);
235-
require(
236-
safeTransferFrom(conversionDetails[0].path[0], feeAddress, amount),
237-
'Batch fee transferFrom() failed'
238-
);
239-
240228
return batchFeeAmountUSD;
241229
}
242230

@@ -265,34 +253,15 @@ contract BatchNoConversionPayments is Ownable {
265253

266254
// The payer transfers tokens to the batch contract and pays batch fee
267255
for (uint256 i = 0; i < uTokens.length && uTokens[i].amountAndFee > 0; i++) {
268-
IERC20 requestedToken = IERC20(uTokens[i].tokenAddress);
269256
uTokens[i].batchFeeAmount = (uTokens[i].batchFeeAmount * batchFee) / feeDenominator;
270-
271-
require(
272-
requestedToken.allowance(msg.sender, address(this)) >=
273-
uTokens[i].amountAndFee + uTokens[i].batchFeeAmount,
274-
'Insufficient allowance for batch to pay'
275-
);
276-
// Check if the payer can pay the amount, the fee, and the batchFee
277-
require(
278-
requestedToken.balanceOf(msg.sender) >= uTokens[i].amountAndFee + uTokens[i].batchFeeAmount,
279-
'Not enough funds'
280-
);
281-
282-
// Transfer only the amount and fee required for the token on the batch contract
283-
require(
284-
safeTransferFrom(uTokens[i].tokenAddress, address(this), uTokens[i].amountAndFee),
285-
'payment transferFrom() failed'
257+
IERC20 requestedToken = IERC20(uTokens[i].tokenAddress);
258+
contractAllowanceApprovalTransfer(
259+
requestedToken,
260+
uTokens[i].amountAndFee,
261+
uTokens[i].batchFeeAmount,
262+
address(paymentErc20Proxy)
286263
);
287264

288-
// Batch contract approves Erc20FeeProxy to spend the token
289-
if (
290-
requestedToken.allowance(address(this), address(paymentErc20Proxy)) <
291-
uTokens[i].amountAndFee
292-
) {
293-
approvePaymentProxyToSpend(address(requestedToken), address(paymentErc20Proxy));
294-
}
295-
296265
// Payer pays batch fee amount
297266

298267
uint256 batchFeeToPay = uTokens[i].batchFeeAmount;
@@ -329,6 +298,46 @@ contract BatchNoConversionPayments is Ownable {
329298
* Helper functions
330299
*/
331300

301+
/**
302+
* It:
303+
* - checks that the batch contract has enough allowance from the payer
304+
* - checks that the payer has enough fund, including batch fees
305+
* - does the transfer of token from the payer to the batch contract
306+
* - increases the allowance of the contract to use the payment proxy if needed
307+
* @param requestedToken The token to pay
308+
* @param amountAndFee The amount and the fee for a token to pay
309+
* @param batchFeeAmount The batch fee amount for a token to pay
310+
* @param paymentProxyAddress The payment proxy address used to pay
311+
*/
312+
function contractAllowanceApprovalTransfer(
313+
IERC20 requestedToken,
314+
uint256 amountAndFee,
315+
uint256 batchFeeAmount,
316+
address paymentProxyAddress
317+
) internal {
318+
// Check proxy's allowance from user
319+
require(
320+
requestedToken.allowance(msg.sender, address(this)) >= amountAndFee,
321+
'Insufficient allowance for batch to pay'
322+
);
323+
// Check user's funds to pay amounts, it is an approximation for conversion payment
324+
require(
325+
requestedToken.balanceOf(msg.sender) >= amountAndFee + batchFeeAmount,
326+
'Not enough funds, including fees'
327+
);
328+
329+
// Transfer the amount and fee required for the token on the batch contract
330+
require(
331+
safeTransferFrom(address(requestedToken), address(this), amountAndFee),
332+
'payment transferFrom() failed'
333+
);
334+
335+
// Batch contract approves Erc20ConversionProxy to spend the token
336+
if (requestedToken.allowance(address(this), paymentProxyAddress) < amountAndFee) {
337+
approvePaymentProxyToSpend(address(requestedToken), paymentProxyAddress);
338+
}
339+
}
340+
332341
/**
333342
* It create a list of unique tokens used and the amounts associated.
334343
* It only considers tokens having: requestAmount + feeAmount > 0.

packages/smart-contracts/test/contracts/BatchNoConversionErc20Payments.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ describe('contract: batchNoConversionPayments: ERC20', () => {
590590
batch
591591
.connect(spender3)
592592
.batchERC20Payments(conversionDetails, [[token1Address, USD_hash]], 0, feeAddress),
593-
).revertedWith('Not enough funds');
593+
).revertedWith('Not enough funds, including fees');
594594
});
595595

596596
it('Should revert batch if not enough funds to pay the batch fee', async () => {
@@ -608,7 +608,7 @@ describe('contract: batchNoConversionPayments: ERC20', () => {
608608
0,
609609
feeAddress,
610610
),
611-
).revertedWith('Not enough funds for the batch fee');
611+
).revertedWith('Not enough funds, including fees');
612612
});
613613

614614
it('Should revert batch without approval', async () => {

0 commit comments

Comments
 (0)