From 8d9a1dbdca9af59fc7e5073204c6fb33c12f2d23 Mon Sep 17 00:00:00 2001 From: Francisco Leal Date: Fri, 31 Jan 2025 23:20:27 +0000 Subject: [PATCH 1/3] create simple contract for multi send --- contracts/utils/MultiSendERC20.sol | 33 +++++++++++++ test/contracts/utils/MultiSendERC20.ts | 67 ++++++++++++++++++++++++++ test/shared/artifacts.ts | 2 + 3 files changed, 102 insertions(+) create mode 100644 contracts/utils/MultiSendERC20.sol create mode 100644 test/contracts/utils/MultiSendERC20.ts diff --git a/contracts/utils/MultiSendERC20.sol b/contracts/utils/MultiSendERC20.sol new file mode 100644 index 00000000..62a50881 --- /dev/null +++ b/contracts/utils/MultiSendERC20.sol @@ -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 { + uint256 total = 0; + require(_recipients.length <= arrayLimit, "Array length exceeds limit"); + uint8 i = 0; + for (i; i < _recipients.length; i++) { + token.transferFrom(msg.sender, _recipients[i], _amounts[i]); + total += _amounts[i]; + } + + emit Multisended(total, address(token)); + } +} diff --git a/test/contracts/utils/MultiSendERC20.ts b/test/contracts/utils/MultiSendERC20.ts new file mode 100644 index 00000000..e1f1bb7d --- /dev/null +++ b/test/contracts/utils/MultiSendERC20.ts @@ -0,0 +1,67 @@ +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"; + +import { findEvent } from "../../shared/utils"; + +chai.use(solidity); + +const { expect } = chai; +const { deployContract } = waffle; + +describe("TalentProtocolToken", () => { + 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); + }); + }); +}); diff --git a/test/shared/artifacts.ts b/test/shared/artifacts.ts index faadab73..fc117016 100644 --- a/test/shared/artifacts.ts +++ b/test/shared/artifacts.ts @@ -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, @@ -22,4 +23,5 @@ export { PassportSources, TalentTGEUnlock, PassportWalletRegistry, + MultiSendERC20, }; From 88f2a34cdac3bd81ac9da3ab305f9726c7e76101 Mon Sep 17 00:00:00 2001 From: Francisco Leal Date: Fri, 31 Jan 2025 23:33:52 +0000 Subject: [PATCH 2/3] Update test name --- test/contracts/utils/MultiSendERC20.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/contracts/utils/MultiSendERC20.ts b/test/contracts/utils/MultiSendERC20.ts index e1f1bb7d..49b53d39 100644 --- a/test/contracts/utils/MultiSendERC20.ts +++ b/test/contracts/utils/MultiSendERC20.ts @@ -7,14 +7,12 @@ import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { MultiSendERC20, ERC20Mock } from "../../../typechain-types"; import { Artifacts } from "../../shared"; -import { findEvent } from "../../shared/utils"; - chai.use(solidity); const { expect } = chai; const { deployContract } = waffle; -describe("TalentProtocolToken", () => { +describe("MultiSendERC20", () => { let admin: SignerWithAddress; let recipientOne: SignerWithAddress; let recipientTwo: SignerWithAddress; From 2cb059b58e938f064820aac53a1931587ef53be0 Mon Sep 17 00:00:00 2001 From: Francisco Leal Date: Sat, 1 Feb 2025 08:47:31 +0000 Subject: [PATCH 3/3] Add require to multisendToken --- contracts/utils/MultiSendERC20.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/MultiSendERC20.sol b/contracts/utils/MultiSendERC20.sol index 62a50881..c767bdef 100644 --- a/contracts/utils/MultiSendERC20.sol +++ b/contracts/utils/MultiSendERC20.sol @@ -24,7 +24,7 @@ contract MultiSendERC20 is Ownable { require(_recipients.length <= arrayLimit, "Array length exceeds limit"); uint8 i = 0; for (i; i < _recipients.length; i++) { - token.transferFrom(msg.sender, _recipients[i], _amounts[i]); + require(token.transferFrom(msg.sender, _recipients[i], _amounts[i]), "Transfer failed"); total += _amounts[i]; }