Skip to content

Commit

Permalink
feat: modify batcher contract to support erc20 sendMany
Browse files Browse the repository at this point in the history
Ticket: COIN-2782
  • Loading branch information
kamleshmugdiya committed Jan 30, 2025
1 parent d64bb5a commit 92bc37b
Show file tree
Hide file tree
Showing 2 changed files with 365 additions and 2 deletions.
57 changes: 56 additions & 1 deletion contracts/Batcher.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
pragma solidity 0.8.20;
import '@openzeppelin/contracts/access/Ownable2Step.sol';
import './TransferHelper.sol';

interface IERC20 {
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
}

error EmptyRecipientsList();
error UnequalRecipientsAndValues();
error TooManyRecipients(uint256 provided, uint256 limit);
error TokenTransferFailed(address token, address from, address to, uint256 amount);

// SPDX-License-Identifier: Apache-2.0

Expand All @@ -23,14 +33,21 @@ contract Batcher is Ownable2Step {
uint256 prevTransferGasLimit,
uint256 newTransferGasLimit
);
event BatchTransferLimitChange(
uint256 prevBatchTransferLimit,
uint256 newBatchTransferLimit
);

uint256 public lockCounter;
uint256 public transferGasLimit;
uint256 public batchTransferLimit;

constructor(uint256 _transferGasLimit) Ownable(msg.sender) {
constructor(uint256 _transferGasLimit, uint256 _batchTransferLimit) Ownable(msg.sender) {
lockCounter = 1;
transferGasLimit = _transferGasLimit;
batchTransferLimit = _batchTransferLimit;
emit TransferGasLimitChange(0, transferGasLimit);
emit BatchTransferLimitChange(0, batchTransferLimit);
}

modifier lockCall() {
Expand Down Expand Up @@ -78,6 +95,35 @@ contract Batcher is Ownable2Step {
require(totalSent == msg.value, 'Total sent out must equal total received');
}

/**
* @dev Batch transferFrom for an ERC20 token.
* @param token The address of the ERC20 token contract.
* @param recipients The array of recipient addresses.
* @param amounts The array of amounts to transfer to each recipient.
* Requirements:
* - `recipients` and `amounts` must have the same length.
* - The caller must have approved the contract to spend the tokens being transferred.
*/
function batchTransferFrom(
address token,
address[] calldata recipients,
uint256[] calldata amounts
) external lockCall {
if (recipients.length == 0) revert EmptyRecipientsList();
if (recipients.length != amounts.length) revert UnequalRecipientsAndValues();
if (recipients.length > batchTransferLimit) revert TooManyRecipients(recipients.length, batchTransferLimit);

for (uint16 i = 0; i < recipients.length; i++) {
(bool success, bytes memory returndata) = token.call(
abi.encodeWithSelector(0x23b872dd, msg.sender, recipients[i], amounts[i])
);

if (!success || (returndata.length > 0 && !abi.decode(returndata, (bool)))) {
revert TokenTransferFailed(token, msg.sender, recipients[i], amounts[i]);
}
}
}

/**
* Recovery function for the contract owner to recover any ERC20 tokens or ETH that may get lost in the control of this contract.
* @param to The recipient to send to
Expand Down Expand Up @@ -109,6 +155,15 @@ contract Batcher is Ownable2Step {
transferGasLimit = newTransferGasLimit;
}

function changeBatchTransferLimit(uint256 newBatchTransferLimit)
external
onlyOwner
{
require(newBatchTransferLimit > 0, 'Batch transfer limit too low');
emit BatchTransferLimitChange(batchTransferLimit, newBatchTransferLimit);
batchTransferLimit = newBatchTransferLimit;
}

fallback() external payable {
revert('Invalid fallback');
}
Expand Down
Loading

0 comments on commit 92bc37b

Please sign in to comment.