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
11 changes: 10 additions & 1 deletion contracts/interfaces/IMToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,22 @@ interface IMToken is IERC20Upgradeable {
function mint(address to, uint256 amount) external;

/**
* @notice burns mToken token `amount` to a given `to` address.
* @notice burns mToken token `amount` from a given `from` address.
* should be called only from permissioned actor
* @param from addres to burn tokens from
* @param amount amount to burn
*/
function burn(address from, uint256 amount) external;

/**
* @notice burns mToken token `amount` from a given `from` address,
* bypassing blacklist checks.
* should be called only from permissioned actor
* @param from address to burn tokens from
* @param amount amount to burn
*/
function forceBurn(address from, uint256 amount) external;

/**
* @notice updates contract`s metadata.
* should be called only from permissioned actor
Expand Down
11 changes: 11 additions & 0 deletions contracts/mToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ abstract contract mToken is ERC20PausableUpgradeable, Blacklistable, IMToken {
function burn(address from, uint256 amount)
external
onlyRole(_burnerRole(), msg.sender)
{
_onlyNotBlacklisted(from);
_burn(from, amount);
}

/**
* @inheritdoc IMToken
*/
function forceBurn(address from, uint256 amount)
external
onlyRole(_burnerRole(), msg.sender)
{
_burn(from, amount);
}
Expand Down
42 changes: 40 additions & 2 deletions test/common/token.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ export const tokenContractsTests = (token: MTokenName) => {
).revertedWith(acErrors.WMAC_HAS_ROLE);
});

it('burn(...) when address is blacklisted', async () => {
it('should fail: burn(...) when address is blacklisted', async () => {
const { owner, regularAccounts, accessControl, tokenContract } =
await deployMTokenWithFixture();

Expand All @@ -620,7 +620,45 @@ export const tokenContractsTests = (token: MTokenName) => {
{ blacklistable: tokenContract, accessControl, owner },
blacklisted,
);
await burn({ tokenContract, owner }, blacklisted, 1);
await burn({ tokenContract, owner }, blacklisted, 1, {
revertMessage: acErrors.WMAC_HAS_ROLE,
});
});

it('forceBurn(...) when address is blacklisted', async () => {
const { owner, regularAccounts, accessControl, tokenContract } =
await deployMTokenWithFixture();

const blacklisted = regularAccounts[0];

await mint({ tokenContract, owner }, blacklisted, 1);
await blackList(
{ blacklistable: tokenContract, accessControl, owner },
blacklisted,
);

const balanceBefore = await tokenContract.balanceOf(
blacklisted.address,
);
await expect(
tokenContract.connect(owner).forceBurn(blacklisted.address, 1),
).to.not.reverted;
const balanceAfter = await tokenContract.balanceOf(blacklisted.address);
expect(balanceBefore.sub(balanceAfter)).eq(1);
});

it('should fail: forceBurn(...) when caller lacks burner role', async () => {
const { owner, regularAccounts, tokenContract } =
await deployMTokenWithFixture();

const unauthorized = regularAccounts[0];
const target = regularAccounts[1];

await mint({ tokenContract, owner }, target, 1);

await expect(
tokenContract.connect(unauthorized).forceBurn(target.address, 1),
).revertedWith(acErrors.WMAC_HASNT_ROLE);
});

it('transferFrom(...) when caller address is blacklisted', async () => {
Expand Down
28 changes: 28 additions & 0 deletions test/unit/LayerZero.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
MidasLzOFTAdapter__factory,
MidasLzVaultComposerSyncTester,
} from '../../typechain-types';
import { acErrors, blackList } from '../common/ac.helpers';
import { approveBase18, mintToken } from '../common/common.helpers';
import { setRoundData } from '../common/data-feed.helpers';
import { deployProxyContract } from '../common/deploy.helpers';
Expand Down Expand Up @@ -149,6 +150,33 @@ describe('LayerZero', function () {
await sendOft(fixture, { amount: 100 }, { revertOnDst: true });
});

it('should fail: from A to B when sender is blacklisted', async () => {
const fixture = await loadFixture(layerZeroFixture);
const { owner, regularAccounts, accessControl, mTBILL } = fixture;

const blacklisted = regularAccounts[0];

await mint(
{ owner, tokenContract: mTBILL },
blacklisted,
parseUnits('100', 18),
);

await blackList(
{ blacklistable: mTBILL, accessControl, owner },
blacklisted,
);

await sendOft(
fixture,
{ amount: 100 },
{
from: blacklisted,
revertMessage: acErrors.WMAC_HAS_ROLE,
},
);
});

it('should fail: send mTBILL from A to B with rate limit exceeded', async () => {
const fixture = await loadFixture(layerZeroFixture);
const { oftAdapterA, eidB } = fixture;
Expand Down
Loading