Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding the splitter contract at marketplace contracts #61

Merged
merged 22 commits into from
Sep 20, 2024
Merged
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
Prev Previous commit
Next Next commit
reducing gas cost
  • Loading branch information
EduardoMelo00 committed Sep 18, 2024
commit 17cb4c125a3917fa669454c9e26a9e22dc8ca3ce
28 changes: 1 addition & 27 deletions contracts/ERC20Splitter.sol
Original file line number Diff line number Diff line change
@@ -9,13 +9,11 @@ contract ERC20Splitter is ReentrancyGuard {
mapping(address => mapping(address => uint256)) public balances;
// userAddress => tokenAddress[]
mapping(address => address[]) private userTokens;
// tokenAddress => userAddress => boolean
mapping(address => mapping(address => bool)) private hasToken;

/** Events **/

event Deposit(
address indexed depositor,
address indexed user,
address[] tokenAddresses,
uint256[] amounts,
uint16[][] shares,
@@ -24,14 +22,6 @@ contract ERC20Splitter is ReentrancyGuard {

event Withdraw(address indexed user, address[] tokenAddresses, uint256[] amounts);

event RecipientSplit(
address indexed depositor,
address indexed tokenAddress,
address indexed recipient,
uint256 amount,
uint16 sharePercentage
);

uint16 public constant MAX_SHARES = 10000;

/** External Functions **/
@@ -95,8 +85,6 @@ contract ERC20Splitter is ReentrancyGuard {
}

withdrawnAmounts[i] = amount;

delete hasToken[recipient][tokenAddress];
}
emit Withdraw(recipient, userTokens[recipient], withdrawnAmounts);

@@ -137,20 +125,6 @@ contract ERC20Splitter is ReentrancyGuard {
for (uint256 i = 0; i < recipients.length; i++) {
uint256 recipientAmount = (amount * shares[i]) / MAX_SHARES;
balances[tokenAddress][recipients[i]] += recipientAmount;

_addTokenForUser(recipients[i], tokenAddress);

emit RecipientSplit(msg.sender, tokenAddress, recipients[i], recipientAmount, shares[i]);
}
}

/// @notice Adds a token to the list of tokens a user has received (for automatic withdrawals).
/// @param recipient The recipient of the token.
/// @param tokenAddress The address of the token.
function _addTokenForUser(address recipient, address tokenAddress) internal {
if (!hasToken[recipient][tokenAddress]) {
userTokens[recipient].push(tokenAddress);
hasToken[recipient][tokenAddress] = true;
}
}
}
103 changes: 101 additions & 2 deletions test/SplitterContract.test.ts
Original file line number Diff line number Diff line change
@@ -8,10 +8,14 @@ import { AddressZero } from '../utils/constants'
describe('ERC20Splitter', () => {
EduardoMelo00 marked this conversation as resolved.
Show resolved Hide resolved
let splitter: ERC20Splitter
let mockERC20: MockERC20
let mockERC20_2: MockERC20
let mockERC20_3: MockERC20
let mockERC20_4: MockERC20
let owner: Awaited<ReturnType<typeof ethers.getSigner>>
let recipient1: Awaited<ReturnType<typeof ethers.getSigner>>
let recipient2: Awaited<ReturnType<typeof ethers.getSigner>>
let recipient3: Awaited<ReturnType<typeof ethers.getSigner>>
let recipient4: Awaited<ReturnType<typeof ethers.getSigner>>
let anotherUser: Awaited<ReturnType<typeof ethers.getSigner>>
let maliciousRecipient: MaliciousRecipient

@@ -20,11 +24,15 @@ describe('ERC20Splitter', () => {

before(async function () {
// prettier-ignore
[owner, recipient1, recipient2, recipient3, anotherUser] = await ethers.getSigners()
[owner, recipient1, recipient2, recipient3,recipient4, anotherUser] = await ethers.getSigners()
})

async function deploySplitterContracts() {
const MockERC20 = await ethers.getContractFactory('MockERC20')
const MockERC20_2 = await ethers.getContractFactory('MockERC20')
const MockERC20_3 = await ethers.getContractFactory('MockERC20')
const MockERC20_4 = await ethers.getContractFactory('MockERC20')

const ERC20Splitter = await ethers.getContractFactory('ERC20Splitter')

const MaliciousRecipientFactory = await ethers.getContractFactory('MaliciousRecipient')
@@ -34,19 +42,34 @@ describe('ERC20Splitter', () => {
const mockERC20 = await MockERC20.deploy()
await mockERC20.waitForDeployment()

const mockERC20_2 = await MockERC20_2.deploy()
await mockERC20_2.waitForDeployment()

const mockERC20_3 = await MockERC20_3.deploy()
await mockERC20_3.waitForDeployment()

const mockERC20_4 = await MockERC20_4.deploy()
await mockERC20_4.waitForDeployment()

const splitter = await ERC20Splitter.deploy()
await splitter.waitForDeployment()

return { mockERC20, splitter }
return { mockERC20, mockERC20_2, mockERC20_3, mockERC20_4, splitter }
}

beforeEach(async () => {
const contracts = await loadFixture(deploySplitterContracts)
mockERC20 = contracts.mockERC20
mockERC20_2 = contracts.mockERC20_2
mockERC20_3 = contracts.mockERC20_3
mockERC20_4 = contracts.mockERC20_4
splitter = contracts.splitter

// Mint tokens to the owner
await mockERC20.connect(owner).mint(owner, ethers.parseEther('1000'))
await mockERC20_2.connect(owner).mint(owner, ethers.parseEther('1000'))
await mockERC20_3.connect(owner).mint(owner, ethers.parseEther('1000'))
await mockERC20_4.connect(owner).mint(owner, ethers.parseEther('1000'))

const splitterAddress = await splitter.getAddress()

@@ -62,6 +85,9 @@ describe('ERC20Splitter', () => {
const splitterSigner = await ethers.getSigner(splitterAddress)

await mockERC20.connect(splitterSigner).approve(splitterAddress, ethers.MaxUint256)
await mockERC20_2.connect(splitterSigner).approve(splitterAddress, ethers.MaxUint256)
await mockERC20_3.connect(splitterSigner).approve(splitterAddress, ethers.MaxUint256)
await mockERC20_4.connect(splitterSigner).approve(splitterAddress, ethers.MaxUint256)

await network.provider.request({
method: 'hardhat_stopImpersonatingAccount',
@@ -70,12 +96,85 @@ describe('ERC20Splitter', () => {

const tokenAmount = ethers.parseEther('100')
await mockERC20.mint(splitter, tokenAmount)
await mockERC20_2.mint(splitter, tokenAmount)
await mockERC20_3.mint(splitter, tokenAmount)
await mockERC20_4.mint(splitter, tokenAmount)
})

describe('Main Functions', async () => {
EduardoMelo00 marked this conversation as resolved.
Show resolved Hide resolved
describe('Deposit', async () => {
EduardoMelo00 marked this conversation as resolved.
Show resolved Hide resolved
beforeEach(async () => {
await mockERC20.connect(owner).approve(splitter.getAddress(), tokenAmount)
await mockERC20_2.connect(owner).approve(splitter.getAddress(), tokenAmount)
await mockERC20_3.connect(owner).approve(splitter.getAddress(), tokenAmount)
await mockERC20_4.connect(owner).approve(splitter.getAddress(), tokenAmount)
})

it('Should deposit ERC20 tokens for one recipient', async () => {
const shares = [[10000]] // 50%, 30%, 20%
const recipients = [[recipient1.address]]

await expect(
splitter.connect(owner).deposit([mockERC20.getAddress()], [tokenAmount], shares, recipients),
).to.emit(splitter, 'Deposit')

expect(await splitter.balances(mockERC20.getAddress(), recipient1.address)).to.equal(ethers.parseEther('100'))
})

it('Should deposit four ERC20 tokens and split them between recipients', async () => {
const tokenAmounts = [
ethers.parseEther('100'),
ethers.parseEther('100'),
ethers.parseEther('100'),
ethers.parseEther('100'),
]
const shares = [[10000], [10000], [10000], [10000]]
const recipients = [[recipient1.address], [recipient2.address], [recipient3.address], [recipient4.address]]

await expect(
splitter
.connect(owner)
.deposit(
[mockERC20.getAddress(), mockERC20_2.getAddress(), mockERC20_3.getAddress(), mockERC20_4.getAddress()],
tokenAmounts,
shares,
recipients,
),
).to.emit(splitter, 'Deposit')

expect(await splitter.balances(mockERC20.getAddress(), recipient1.address)).to.equal(ethers.parseEther('100'))
expect(await splitter.balances(mockERC20_2.getAddress(), recipient2.address)).to.equal(ethers.parseEther('100'))
expect(await splitter.balances(mockERC20_3.getAddress(), recipient3.address)).to.equal(ethers.parseEther('100'))
expect(await splitter.balances(mockERC20_4.getAddress(), recipient4.address)).to.equal(ethers.parseEther('100'))
})

it.only('Should deposit three ERC20 tokens and split them between recipients', async () => {
const tokenAmounts = [ethers.parseEther('100'), ethers.parseEther('100'), ethers.parseEther('100')]
const shares = [
[5000, 3000, 2000],
[5000, 3000, 2000],
[5000, 3000, 2000],
]
const recipients = [
[recipient1.address, recipient2.address, recipient3.address],
[recipient1.address, recipient2.address, recipient3.address],
[recipient1.address, recipient2.address, recipient3.address],
]

await expect(
splitter
.connect(owner)
.deposit(
[mockERC20.getAddress(), mockERC20_2.getAddress(), mockERC20_3.getAddress()],
tokenAmounts,
shares,
recipients,
),
).to.emit(splitter, 'Deposit')

expect(await splitter.balances(mockERC20.getAddress(), recipient1.address)).to.equal(ethers.parseEther('50'))
expect(await splitter.balances(mockERC20_2.getAddress(), recipient2.address)).to.equal(ethers.parseEther('30'))
expect(await splitter.balances(mockERC20_3.getAddress(), recipient3.address)).to.equal(ethers.parseEther('20'))
})

it('Should deposit ERC20 tokens and split them between recipients', async () => {