Skip to content
Closed
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
38 changes: 38 additions & 0 deletions packages/evm/contracts/utils/MimicHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

pragma solidity ^0.8.20;

import { Math } from '@openzeppelin/contracts/utils/math/Math.sol';

/**
* @title Mimic Helper
* @dev Collection of helper functions for the Mimic Protocol
Expand All @@ -27,6 +29,16 @@ contract MimicHelper {
*/
event StorageSet(address indexed user, string indexed key, bytes indexed data);

/**
* @dev The percents add up to 100 or more
*/
error MimicHelperInvalidPercent();

/**
* @dev The percents array is empty
*/
error MimicHelperEmptyPercents();

/**
* @dev Tells the native token balance of an address
* @param target Address to get native token balance
Expand Down Expand Up @@ -61,4 +73,30 @@ contract MimicHelper {
_customStorage[msg.sender][key] = data;
emit StorageSet(msg.sender, key, data);
}

/**
* @dev Splits an amount by specified percentages.
* All remainder goes to the last split.
* @param amount Amount to be split
* @param percents Array of percents
* @return splits Array of `percents.length + 1` amounts; the last element holds the remainder
*/
function pct(uint256 amount, uint8[] calldata percents) external pure returns (uint256[] memory splits) {
uint256 len = percents.length;
if (len == 0) revert MimicHelperEmptyPercents();

splits = new uint256[](len + 1);
uint256 pctSum = 0;
uint256 amountSum = 0;

for (uint256 i = 0; i < len; i++) {
splits[i] = Math.mulDiv(amount, percents[i], 100);
amountSum += splits[i];
pctSum += percents[i];
}
if (pctSum >= 100) revert MimicHelperInvalidPercent();

splits[len] = amount - amountSum; // absorbs rounding dust; never underflows since pctSum < 100
return splits;
}
}
55 changes: 55 additions & 0 deletions packages/evm/test/utils/MimicHelper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,59 @@ describe('MimicHelper', () => {
})
})
})

describe('pct', () => {
context('when the percents leave a remainder', () => {
const amount = 101n
const percents = [50]

it('returns splits that add up to the total', async () => {
const splits = await mimicHelper.pct(amount, percents)

expect(splits).to.have.lengthOf(percents.length + 1)
expect(splits[0]).to.be.equal(50n)
expect(splits[1]).to.be.equal(51n)
expect(splits.reduce((total, split) => total + split, 0n)).to.be.equal(amount)
})
})

context('when there are multiple percents', () => {
const amount = 100n
const percents = [30, 30]

it('returns a split per percent plus the remainder', async () => {
const splits = await mimicHelper.pct(amount, percents)

expect(splits).to.have.lengthOf(percents.length + 1)
expect(splits[0]).to.be.equal(30n)
expect(splits[1]).to.be.equal(30n)
expect(splits[2]).to.be.equal(40n)
expect(splits.reduce((total, split) => total + split, 0n)).to.be.equal(amount)
})
})

context('when the percents sum to 100% or more', () => {
const amount = 101n
const percents = [50, 50]

it('reverts', async () => {
await expect(mimicHelper.pct(amount, percents)).to.be.revertedWithCustomError(
mimicHelper,
'MimicHelperInvalidPercent'
)
})
})

context('when the percents array is empty', () => {
const amount = 101n
const percents: number[] = []

it('reverts', async () => {
await expect(mimicHelper.pct(amount, percents)).to.be.revertedWithCustomError(
mimicHelper,
'MimicHelperEmptyPercents'
)
})
})
})
})
Loading