Skip to content

Commit 256f5d6

Browse files
committed
add fee batch amount limit to batchMultiERC20ConversionPayments
1 parent 44dc374 commit 256f5d6

File tree

4 files changed

+252
-21
lines changed

4 files changed

+252
-21
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export async function deployBatchConversionPayment(
2828
const _EthereumFeeProxyAddress = ethereumFeeProxyArtifact.getAddress('private');
2929
const _paymentErc20ConversionFeeProxy = erc20ConversionProxy.getAddress('private');
3030
const _paymentEthConversionFeeProxy = ethConversionArtifact.getAddress('private');
31+
const _chainlinkConversionPath = '0x4e71920b7330515faf5EA0c690f1aD06a85fB60c';
3132

3233
// Deploy BatchConversionPayments contract
3334
const { address: BatchConversionPaymentsAddress } = await deployOne(
@@ -40,6 +41,7 @@ export async function deployBatchConversionPayment(
4041
_EthereumFeeProxyAddress,
4142
_paymentErc20ConversionFeeProxy,
4243
_paymentEthConversionFeeProxy,
44+
_chainlinkConversionPath,
4345
await (await hre.ethers.getSigners())[0].getAddress(),
4446
],
4547
},
@@ -75,6 +77,8 @@ export async function deployBatchConversionPayment(
7577
const batchConversion = batchConversionPaymentsArtifact.connect(hre.network.name, owner);
7678
await batchConversion.connect(owner).setBatchFee(30);
7779
await batchConversion.connect(owner).setBatchConversionFee(30);
80+
await batchConversion.connect(owner).setUSDAddress(currencyManager.fromSymbol('USD')!.hash);
81+
await batchConversion.connect(owner).setBatchFeeAmountUSDLimit(300); // * 1_000_000_000_000_000_000);
7882

7983
// ----------------------------------
8084
console.log('Contracts deployed');

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

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pragma solidity ^0.8.4;
33

44
import './interfaces/IERC20ConversionProxy.sol';
55
import './interfaces/IEthConversionProxy.sol';
6+
import './ChainlinkConversionPath.sol';
67
import './BatchNoConversionPayments.sol';
78

89
/**
@@ -24,9 +25,13 @@ contract BatchConversionPayments is BatchNoConversionPayments {
2425

2526
IERC20ConversionProxy public paymentErc20ConversionProxy;
2627
IEthConversionProxy public paymentEthConversionProxy;
28+
ChainlinkConversionPath public chainlinkConversionPath;
2729

2830
uint256 public batchConversionFee;
2931

32+
uint256 public batchFeeAmountUSDLimit; // maybe not necessary
33+
address public USDAddress;
34+
3035
/**
3136
* @dev All the information of a request, except the feeAddress
3237
* _recipient Recipient address of the payment
@@ -57,6 +62,7 @@ contract BatchConversionPayments is BatchNoConversionPayments {
5762
uint256[] amounts;
5863
bytes[] paymentReferences;
5964
uint256[] feeAmounts;
65+
//address[][] pathsToUSD; // TODO is there another solution ?
6066
}
6167

6268
/**
@@ -76,17 +82,20 @@ contract BatchConversionPayments is BatchNoConversionPayments {
7682
* @param _paymentEthProxy The ETH payment proxy address to use.
7783
* @param _paymentErc20ConversionProxy The ERC20 Conversion payment proxy address to use.
7884
* @param _paymentEthConversionFeeProxy The ETH Conversion payment proxy address to use.
85+
* @param _chainlinkConversionPathAddress The address of the conversion path contract
7986
* @param _owner Owner of the contract.
8087
*/
8188
constructor(
8289
address _paymentErc20Proxy,
8390
address _paymentEthProxy,
8491
address _paymentErc20ConversionProxy,
8592
address _paymentEthConversionFeeProxy,
93+
address _chainlinkConversionPathAddress,
8694
address _owner
8795
) BatchNoConversionPayments(_paymentErc20Proxy, _paymentEthProxy, _owner) {
8896
paymentErc20ConversionProxy = IERC20ConversionProxy(_paymentErc20ConversionProxy);
8997
paymentEthConversionProxy = IEthConversionProxy(_paymentEthConversionFeeProxy);
98+
chainlinkConversionPath = ChainlinkConversionPath(_chainlinkConversionPathAddress);
9099
batchConversionFee = 0;
91100
}
92101

@@ -99,16 +108,29 @@ contract BatchConversionPayments is BatchNoConversionPayments {
99108
* - batchEthPayments, paymentNetworkId=3
100109
* - batchEthConversionPayments, paymentNetworkId=4
101110
* If metaDetails use paymentNetworkId = 4, it must be at the end of the list, or the transaction can be reverted
111+
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees, caution,
112+
Caution, the calculation of batchFeeAmountUSD which allows to limit the batch fees takes only
113+
into consideration these paths. Without paths, there is not limitation.
102114
* @param _feeAddress The address where fees should be paid
103115
* @dev batchRouter only reduces gas consumption when using more than a single payment network.
104116
* For single payment network payments, it is more efficient to use the suited batch function.
105117
*/
106-
function batchRouter(MetaDetail[] calldata metaDetails, address _feeAddress) external payable {
118+
function batchRouter(
119+
MetaDetail[] calldata metaDetails,
120+
address[][] calldata pathsToUSD,
121+
address _feeAddress
122+
) external payable {
107123
require(metaDetails.length < 6, 'more than 5 metaDetails');
124+
uint256 batchFeeAmountUSD = 0;
108125
for (uint256 i = 0; i < metaDetails.length; i++) {
109126
MetaDetail calldata metaConversionDetail = metaDetails[i];
110127
if (metaConversionDetail.paymentNetworkId == 0) {
111-
batchMultiERC20ConversionPayments(metaConversionDetail.conversionDetails, _feeAddress);
128+
batchFeeAmountUSD = batchMultiERC20ConversionPayments(
129+
metaConversionDetail.conversionDetails,
130+
batchFeeAmountUSD,
131+
pathsToUSD,
132+
_feeAddress
133+
);
112134
} else if (metaConversionDetail.paymentNetworkId == 1) {
113135
batchERC20Payments(
114136
metaConversionDetail.cryptoDetails.tokenAddresses[0],
@@ -154,12 +176,15 @@ contract BatchConversionPayments is BatchNoConversionPayments {
154176
* @notice Send a batch of ERC20 payments with amounts based on a request
155177
* currency (e.g. fiat), with fees and paymentReferences to multiple accounts, with multiple tokens.
156178
* @param conversionDetails list of requestInfo, each one containing all the information of a request
179+
* @param pathsToUSD The list of paths into USD for every token
157180
* @param _feeAddress The fee recipient
158181
*/
159182
function batchMultiERC20ConversionPayments(
160183
ConversionDetail[] calldata conversionDetails,
184+
uint256 batchFeeAmountUSD,
185+
address[][] calldata pathsToUSD,
161186
address _feeAddress
162-
) public {
187+
) public returns (uint256) {
163188
// a list of unique tokens, with the sum of maxToSpend by token
164189
Token[] memory uTokens = new Token[](conversionDetails.length);
165190
for (uint256 i = 0; i < conversionDetails.length; i++) {
@@ -237,16 +262,23 @@ contract BatchConversionPayments is BatchNoConversionPayments {
237262
requestedToken.safeTransfer(msg.sender, excessAmount);
238263
}
239264

265+
uint256 batchFeeToPay = ((uTokens[k].amountAndFee - excessAmount) * batchConversionFee) /
266+
tenThousand;
267+
268+
(batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay(
269+
batchFeeToPay,
270+
uTokens[k].tokenAddress,
271+
batchFeeAmountUSD,
272+
pathsToUSD
273+
);
274+
240275
// Payer pays the exact batch fees amount
241276
require(
242-
safeTransferFrom(
243-
uTokens[k].tokenAddress,
244-
_feeAddress,
245-
((uTokens[k].amountAndFee - excessAmount) * batchConversionFee) / tenThousand
246-
),
277+
safeTransferFrom(uTokens[k].tokenAddress, _feeAddress, batchFeeToPay),
247278
'batch fee transferFrom() failed'
248279
);
249280
}
281+
return batchFeeAmountUSD;
250282
}
251283

252284
/**
@@ -295,6 +327,34 @@ contract BatchConversionPayments is BatchNoConversionPayments {
295327
payerAuthorized = false;
296328
}
297329

330+
function calculateBatchFeeToPay(
331+
uint256 batchFeeToPay,
332+
address tokenAddress,
333+
uint256 batchFeeAmountUSD,
334+
address[][] calldata pathsToUSD
335+
) internal view returns (uint256, uint256) {
336+
if (pathsToUSD.length > 0) {
337+
for (uint256 i = 0; i < pathsToUSD.length; i++) {
338+
if (
339+
pathsToUSD[i][0] == tokenAddress && pathsToUSD[i][pathsToUSD[i].length - 1] == USDAddress
340+
) {
341+
uint256 conversionUSD = 0;
342+
(conversionUSD, ) = chainlinkConversionPath.getConversion(batchFeeToPay, pathsToUSD[i]);
343+
// calculate the batch fee to pay, taking care of the batchFeeAmountUSDLimit
344+
uint256 conversionToPayUSD = conversionUSD;
345+
if (batchFeeAmountUSD + conversionToPayUSD > batchFeeAmountUSDLimit) {
346+
conversionToPayUSD = batchFeeAmountUSDLimit - batchFeeAmountUSD;
347+
batchFeeToPay = (batchFeeToPay * conversionToPayUSD) / conversionUSD;
348+
}
349+
batchFeeAmountUSD += conversionToPayUSD;
350+
// add only once the fees
351+
break;
352+
}
353+
}
354+
}
355+
return (batchFeeToPay, batchFeeAmountUSD);
356+
}
357+
298358
/*
299359
* Admin functions to edit the conversion proxies address and fees
300360
*/
@@ -322,4 +382,20 @@ contract BatchConversionPayments is BatchNoConversionPayments {
322382
function setPaymentEthConversionProxy(address _paymentEthConversionProxy) external onlyOwner {
323383
paymentEthConversionProxy = IEthConversionProxy(_paymentEthConversionProxy);
324384
}
385+
386+
/**
387+
* @notice Update the conversion path contract used to fetch conversions
388+
* @param _chainlinkConversionPathAddress address of the conversion path contract
389+
*/
390+
function setConversionPathAddress(address _chainlinkConversionPathAddress) external onlyOwner {
391+
chainlinkConversionPath = ChainlinkConversionPath(_chainlinkConversionPathAddress);
392+
}
393+
394+
function setUSDAddress(address _USDAddress) external onlyOwner {
395+
USDAddress = _USDAddress;
396+
}
397+
398+
function setBatchFeeAmountUSDLimit(uint256 _batchFeeAmountUSDLimit) external onlyOwner {
399+
batchFeeAmountUSDLimit = _batchFeeAmountUSDLimit;
400+
}
325401
}

packages/smart-contracts/src/lib/artifacts/BatchConversionPayments/0.1.0.json

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
"name": "_paymentEthConversionFeeProxy",
2323
"type": "address"
2424
},
25+
{
26+
"internalType": "address",
27+
"name": "_chainlinkConversionPathAddress",
28+
"type": "address"
29+
},
2530
{
2631
"internalType": "address",
2732
"name": "_owner",
@@ -50,6 +55,19 @@
5055
"name": "OwnershipTransferred",
5156
"type": "event"
5257
},
58+
{
59+
"inputs": [],
60+
"name": "USDAddress",
61+
"outputs": [
62+
{
63+
"internalType": "address",
64+
"name": "",
65+
"type": "address"
66+
}
67+
],
68+
"stateMutability": "view",
69+
"type": "function"
70+
},
5371
{
5472
"inputs": [],
5573
"name": "batchConversionFee",
@@ -202,6 +220,19 @@
202220
"stateMutability": "view",
203221
"type": "function"
204222
},
223+
{
224+
"inputs": [],
225+
"name": "batchFeeAmountUSDLimit",
226+
"outputs": [
227+
{
228+
"internalType": "uint256",
229+
"name": "",
230+
"type": "uint256"
231+
}
232+
],
233+
"stateMutability": "view",
234+
"type": "function"
235+
},
205236
{
206237
"inputs": [
207238
{
@@ -246,6 +277,11 @@
246277
"name": "conversionDetails",
247278
"type": "tuple[]"
248279
},
280+
{
281+
"internalType": "address[][]",
282+
"name": "pathsToUSD",
283+
"type": "address[][]"
284+
},
249285
{
250286
"internalType": "address",
251287
"name": "_feeAddress",
@@ -383,6 +419,11 @@
383419
"name": "metaDetails",
384420
"type": "tuple[]"
385421
},
422+
{
423+
"internalType": "address[][]",
424+
"name": "pathsToUSD",
425+
"type": "address[][]"
426+
},
386427
{
387428
"internalType": "address",
388429
"name": "_feeAddress",
@@ -394,6 +435,19 @@
394435
"stateMutability": "payable",
395436
"type": "function"
396437
},
438+
{
439+
"inputs": [],
440+
"name": "chainlinkConversionPath",
441+
"outputs": [
442+
{
443+
"internalType": "contract ChainlinkConversionPath",
444+
"name": "",
445+
"type": "address"
446+
}
447+
],
448+
"stateMutability": "view",
449+
"type": "function"
450+
},
397451
{
398452
"inputs": [],
399453
"name": "owner",
@@ -492,6 +546,32 @@
492546
"stateMutability": "nonpayable",
493547
"type": "function"
494548
},
549+
{
550+
"inputs": [
551+
{
552+
"internalType": "uint256",
553+
"name": "_batchFeeAmountUSDLimit",
554+
"type": "uint256"
555+
}
556+
],
557+
"name": "setBatchFeeAmountUSDLimit",
558+
"outputs": [],
559+
"stateMutability": "nonpayable",
560+
"type": "function"
561+
},
562+
{
563+
"inputs": [
564+
{
565+
"internalType": "address",
566+
"name": "_chainlinkConversionPathAddress",
567+
"type": "address"
568+
}
569+
],
570+
"name": "setConversionPathAddress",
571+
"outputs": [],
572+
"stateMutability": "nonpayable",
573+
"type": "function"
574+
},
495575
{
496576
"inputs": [
497577
{
@@ -544,6 +624,19 @@
544624
"stateMutability": "nonpayable",
545625
"type": "function"
546626
},
627+
{
628+
"inputs": [
629+
{
630+
"internalType": "address",
631+
"name": "_USDAddress",
632+
"type": "address"
633+
}
634+
],
635+
"name": "setUSDAddress",
636+
"outputs": [],
637+
"stateMutability": "nonpayable",
638+
"type": "function"
639+
},
547640
{
548641
"inputs": [
549642
{

0 commit comments

Comments
 (0)