Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions contracts/utils/MultiSendERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MultiSendERC20 is Ownable {
uint8 public arrayLimit;
ERC20 public token;

event Multisended(uint256 total, address token);

constructor(address _owner, address _token) Ownable(_owner) {
arrayLimit = 200;
token = ERC20(_token);
}

function setArrayLimit(uint8 _arrayLimit) external onlyOwner {
arrayLimit = _arrayLimit;
}

function multisendToken(address[] calldata _recipients, uint256[] calldata _amounts) public {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you want this to be called only by the owner?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need, since the tokens are sent from the message owner we don't need that control. The only thing I want to control is the limit of how many can be sent at a time

uint256 total = 0;
require(_recipients.length <= arrayLimit, "Array length exceeds limit");
uint8 i = 0;
for (i; i < _recipients.length; i++) {
require(token.transferFrom(msg.sender, _recipients[i], _amounts[i]), "Transfer failed");
total += _amounts[i];
}

emit Multisended(total, address(token));
}
}
65 changes: 65 additions & 0 deletions test/contracts/utils/MultiSendERC20.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import chai from "chai";
import { ethers, waffle } from "hardhat";
import { solidity } from "ethereum-waffle";

import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";

import { MultiSendERC20, ERC20Mock } from "../../../typechain-types";
import { Artifacts } from "../../shared";

chai.use(solidity);

const { expect } = chai;
const { deployContract } = waffle;

describe("MultiSendERC20", () => {
let admin: SignerWithAddress;
let recipientOne: SignerWithAddress;
let recipientTwo: SignerWithAddress;
let recipientThree: SignerWithAddress;

let token: ERC20Mock;
let multiSend: MultiSendERC20;

beforeEach(async () => {
[admin, recipientOne, recipientTwo, recipientThree] = await ethers.getSigners();
token = (await deployContract(admin, Artifacts.ERC20Mock, [admin.address])) as ERC20Mock;
});

async function builder() {
return deployContract(admin, Artifacts.MultiSendERC20, [admin.address, token.address]);
}

describe("Deploy base contract", () => {
beforeEach(async () => {
multiSend = (await builder()) as MultiSendERC20;
});

it("deploys and set ups the initial state correctly", async () => {
expect(await multiSend.token()).to.eq(token.address);
expect(await multiSend.owner()).to.eq(admin.address);
expect(await multiSend.arrayLimit()).to.eq(200);
});
});

describe("Transfer behaviour", () => {
beforeEach(async () => {
multiSend = (await builder()) as MultiSendERC20;
await token.approve(multiSend.address, ethers.utils.parseEther("1000000000"));
});

it("Should transfer tokens between one account", async function () {
await multiSend.connect(admin).multisendToken([recipientOne.address], [50]);
const recipientOneBalance = await token.balanceOf(recipientOne.address);
expect(recipientOneBalance).to.equal(50);
});

it("Should transfer tokens between multiple accounts", async function () {
await multiSend.connect(admin).multisendToken([recipientOne.address, recipientTwo.address], [50, 100]);
const recipientOneBalance = await token.balanceOf(recipientOne.address);
const recipientTwoBalance = await token.balanceOf(recipientTwo.address);
expect(recipientOneBalance).to.equal(50);
expect(recipientTwoBalance).to.equal(100);
});
});
});
2 changes: 2 additions & 0 deletions test/shared/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import SmartBuilderScore from "../../artifacts/contracts/passport/SmartBuilderSc
import PassportSources from "../../artifacts/contracts/passport/PassportSources.sol/PassportSources.json";
import TalentTGEUnlock from "../../artifacts/contracts/talent/TalentTGEUnlock.sol/TalentTGEUnlock.json";
import PassportWalletRegistry from "../../artifacts/contracts/passport/PassportWalletRegistry.sol/PassportWalletRegistry.json";
import MultiSendERC20 from "../../artifacts/contracts/utils/MultiSendERC20.sol/MultiSendERC20.json";

export {
PassportRegistry,
Expand All @@ -22,4 +23,5 @@ export {
PassportSources,
TalentTGEUnlock,
PassportWalletRegistry,
MultiSendERC20,
};
Loading