Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
52 changes: 52 additions & 0 deletions contracts/mTokenPermissioned.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import "./mToken.sol";

/**
* @title mTokenPermissioned
* @notice mToken with fully permissioned transfers
* @author RedDuck Software
*/
//solhint-disable contract-name-camelcase
abstract contract mTokenPermissioned is mToken {
/**
* @dev leaving a storage gap for futures updates
*/
uint256[50] private __gap;

/**
* @dev overrides _beforeTokenTransfer function to allow
* greenlisted users to use the token transfers functions
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual override(mToken) {
if (to != address(0)) {
if (from != address(0)) {
_onlyGreenlisted(from);
}
_onlyGreenlisted(to);
}

mToken._beforeTokenTransfer(from, to, amount);
}

/**
* @notice AC role of a greenlist
* @return role bytes32 role
*/
function _greenlistedRole() internal pure virtual returns (bytes32);

/**
* @dev checks that a given `account`
* have `greenlistedRole()`
*/
function _onlyGreenlisted(address account)
private
view
onlyRole(_greenlistedRole(), account)
{}
}
46 changes: 46 additions & 0 deletions contracts/testers/mTokenPermissionedTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import "../mTokenPermissioned.sol";

//solhint-disable contract-name-camelcase
contract mTokenPermissionedTest is mTokenPermissioned {
bytes32 public constant M_TOKEN_TEST_MINT_OPERATOR_ROLE =
keccak256("M_TOKEN_TEST_MINT_OPERATOR_ROLE");

bytes32 public constant M_TOKEN_TEST_BURN_OPERATOR_ROLE =
keccak256("M_TOKEN_TEST_BURN_OPERATOR_ROLE");

bytes32 public constant M_TOKEN_TEST_PAUSE_OPERATOR_ROLE =
keccak256("M_TOKEN_TEST_PAUSE_OPERATOR_ROLE");

bytes32 public constant M_TOKEN_TEST_GREENLISTED_ROLE =
keccak256("M_TOKEN_TEST_GREENLISTED_ROLE");

function _disableInitializers() internal override {}

function _getNameSymbol()
internal
pure
override
returns (string memory, string memory)
{
return ("mTokenPermissionedTest", "mTokenPermissionedTest");
}

function _minterRole() internal pure override returns (bytes32) {
return M_TOKEN_TEST_MINT_OPERATOR_ROLE;
}

function _burnerRole() internal pure override returns (bytes32) {
return M_TOKEN_TEST_BURN_OPERATOR_ROLE;
}

function _pauserRole() internal pure override returns (bytes32) {
return M_TOKEN_TEST_PAUSE_OPERATOR_ROLE;
}

function _greenlistedRole() internal pure override returns (bytes32) {
return M_TOKEN_TEST_GREENLISTED_ROLE;
}
}
2 changes: 1 addition & 1 deletion helpers/mtokens-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { MTokenName } from '../config';

export const mTokensMetadata: Record<
MTokenName,
{ name: string; symbol: string }
{ name: string; symbol: string; isPermissioned?: boolean }
> = {
mTBILL: {
name: 'Midas US Treasury Bill Token',
Expand Down
2 changes: 2 additions & 0 deletions helpers/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type TokenRoles = {
depositVaultAdmin: string;
redemptionVaultAdmin: string;
customFeedAdmin: string | null;
greenlisted: string;
};

type CommonRoles = {
Expand Down Expand Up @@ -118,6 +119,7 @@ export const getRolesNamesForToken = (token: MTokenName): TokenRoles => {
: `${tokenPrefix}_CUSTOM_AGGREGATOR_FEED_ADMIN_ROLE`,
depositVaultAdmin: `${restPrefix}DEPOSIT_VAULT_ADMIN_ROLE`,
redemptionVaultAdmin: `${restPrefix}REDEMPTION_VAULT_ADMIN_ROLE`,
greenlisted: `${restPrefix}GREENLISTED_ROLE`,
};
};
export const getRolesNamesCommon = (): CommonRoles => {
Expand Down
40 changes: 36 additions & 4 deletions scripts/deploy/codegen/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import {
getConfigFromUser,
getContractsToGenerateFromUser,
getShouldUseTokenLevelGreenListFromUser,
getShouldUseTokenPermissionedFromUser,
} from './ui/deployment-contracts';

import { MTokenName } from '../../../../config';
Expand All @@ -45,7 +47,10 @@
const generatorPerContract: Partial<
Record<
keyof TokenContractNames | 'layerZeroMinterBurner',
(mToken: MTokenName) =>
(
mToken: MTokenName,
optionalParams?: Record<string, unknown>,
) =>
| Promise<
| {
name: string;
Expand Down Expand Up @@ -78,12 +83,14 @@
name,
symbol,
mToken,
isPermissioned,
}: {
contractNamePrefix: string;
rolesPrefix: string;
name: string;
symbol: string;
mToken: string;
isPermissioned?: true;
},
) => {
const project = new Project();
Expand Down Expand Up @@ -159,7 +166,12 @@
initializer: (writer) =>
writer.write(`{
name: '${name}',
symbol: '${symbol}'
symbol: '${symbol}'${
isPermissioned
? `,
isPermissioned: true`
: ''
}
}`),
});
}
Expand Down Expand Up @@ -195,7 +207,7 @@
stdio: 'inherit',
},
);
} catch (error) {

Check warning on line 210 in scripts/deploy/codegen/common/index.ts

View workflow job for this annotation

GitHub Actions / static-checks

'error' is defined but never used
cancel('Failed to run lint&format fix for generated contracts');
process.exit(1);
}
Expand All @@ -209,7 +221,7 @@
stdio: 'inherit',
},
);
} catch (error) {

Check warning on line 224 in scripts/deploy/codegen/common/index.ts

View workflow job for this annotation

GitHub Actions / static-checks

'error' is defined but never used
cancel('Failed to run lint&format fix for generated contracts');
process.exit(1);
}
Expand Down Expand Up @@ -505,6 +517,20 @@

const contractsToGenerate = await getContractsToGenerateFromUser();

let shouldUseTokenLevelGreenList = false;
let shouldUseTokenPermissioned = false;

if (
contractsToGenerate.find((v) => v.startsWith('dv') || v.startsWith('rv'))
) {
shouldUseTokenLevelGreenList =
await getShouldUseTokenLevelGreenListFromUser();
}

if (contractsToGenerate.includes('token')) {
shouldUseTokenPermissioned = await getShouldUseTokenPermissionedFromUser();
}

const mToken = config.tokenContractName;

const folder = path.join(
Expand All @@ -523,11 +549,12 @@
name: config.tokenName,
symbol: config.tokenSymbol,
mToken,
isPermissioned: shouldUseTokenPermissioned ? true : undefined,
});
},
},
{
title: 'Generation files',
title: 'Generating files',
task: async () => {
const isFolderExists = await fs
.access(folder)
Expand All @@ -547,7 +574,12 @@
].filter((v) => v !== undefined);

const generatedContracts = await Promise.all(
generators.map((generator) => generator(mToken as MTokenName)),
generators.map((generator) =>
generator(mToken as MTokenName, {
vaultUseTokenLevelGreenList: shouldUseTokenLevelGreenList,
isPermissionedMToken: shouldUseTokenPermissioned,
}),
),
);

for (const contract of generatedContracts) {
Expand Down
20 changes: 19 additions & 1 deletion scripts/deploy/codegen/common/templates/dv.template.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { MTokenName } from '../../../../../config';
import { importWithoutCache } from '../../../../../helpers/utils';

export const getDvContractFromTemplate = async (mToken: MTokenName) => {
export const getDvContractFromTemplate = async (
mToken: MTokenName,
optionalParams?: Record<string, unknown>,
) => {
const { vaultUseTokenLevelGreenList = false } = optionalParams || {};

const { getTokenContractNames } = await importWithoutCache(
require.resolve('../../../../../helpers/contracts'),
);
Expand Down Expand Up @@ -38,6 +43,19 @@ export const getDvContractFromTemplate = async (mToken: MTokenName) => {
function vaultRole() public pure override returns (bytes32) {
return ${roles.depositVaultAdmin};
}

${
vaultUseTokenLevelGreenList
? `
/**
* @inheritdoc Greenlistable
*/
function greenlistedRole() public pure override returns (bytes32) {
return ${roles.greenlisted};
}
`
: ''
}
}`,
};
};
25 changes: 22 additions & 3 deletions scripts/deploy/codegen/common/templates/mtoken.template.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { MTokenName } from '../../../../../config';
import { importWithoutCache } from '../../../../../helpers/utils';

export const getTokenContractFromTemplate = async (mToken: MTokenName) => {
export const getTokenContractFromTemplate = async (
mToken: MTokenName,
optionalParams?: Record<string, unknown>,
) => {
const { isPermissionedMToken = false } = optionalParams || {};
const { getTokenContractNames } = await importWithoutCache(
require.resolve('../../../../../helpers/contracts'),
);
Expand All @@ -25,14 +29,17 @@ export const getTokenContractFromTemplate = async (mToken: MTokenName) => {
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import "../../mToken.sol";
import "../../mToken${isPermissionedMToken ? 'Permissioned' : ''}.sol";
${isPermissionedMToken ? `import "./${contractNames.roles}.sol";` : ''}
Comment on lines +32 to +33
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When isPermissionedMToken is true, the template imports both mTokenPermissioned.sol and ${contractNames.roles}.sol. However, when isPermissionedMToken is false, the template only imports mToken.sol. This could lead to inconsistencies in the generated code, especially if the generated contract relies on functionalities defined in ${contractNames.roles}.sol even when the permissioned token is not used. Consider adding a check to ensure that ${contractNames.roles}.sol is always imported when it is needed by the generated contract, regardless of the isPermissionedMToken value.

Suggested change
import "../../mToken${isPermissionedMToken ? 'Permissioned' : ''}.sol";
${isPermissionedMToken ? `import "./${contractNames.roles}.sol";` : ''}
import "../../mToken${isPermissionedMToken ? 'Permissioned' : ''}.sol";
import "./${contractNames.roles}.sol";


/**
* @title ${contractNames.token}
* @author RedDuck Software
*/
//solhint-disable contract-name-camelcase
contract ${contractNames.token} is mToken {
contract ${contractNames.token} is mToken${
isPermissionedMToken ? `Permissioned, ${contractNames.roles}` : ''
} {
Comment on lines +40 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The template uses a ternary operator to conditionally extend the contract with Permissioned, ${contractNames.roles}. However, if isPermissionedMToken is false, the contract will only extend mToken. This could lead to inconsistencies in the generated code, especially if the generated contract relies on functionalities defined in Permissioned or ${contractNames.roles} even when the permissioned token is not used. Consider adding a check to ensure that Permissioned and ${contractNames.roles} are always extended when they are needed by the generated contract, regardless of the isPermissionedMToken value.

Suggested change
contract ${contractNames.token} is mToken${
isPermissionedMToken ? `Permissioned, ${contractNames.roles}` : ''
} {
contract ${contractNames.token} is mToken,
${isPermissionedMToken ? `Permissioned, ${contractNames.roles}` : ''} {

/**
* @notice actor that can mint ${contractNames.token}
*/
Expand Down Expand Up @@ -88,6 +95,18 @@ export const getTokenContractFromTemplate = async (mToken: MTokenName) => {
function _pauserRole() internal pure override returns (bytes32) {
return ${roles.pauser};
}

${
isPermissionedMToken
? `
/**
* @inheritdoc mTokenPermissioned
*/
function _greenlistedRole() internal pure override returns (bytes32) {
return ${roles.greenlisted};
}`
: ''
}
}`,
};
};
20 changes: 19 additions & 1 deletion scripts/deploy/codegen/common/templates/rv-swapper.template.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { MTokenName } from '../../../../../config';
import { importWithoutCache } from '../../../../../helpers/utils';

export const getRvSwapperContractFromTemplate = async (mToken: MTokenName) => {
export const getRvSwapperContractFromTemplate = async (
mToken: MTokenName,
optionalParams?: Record<string, unknown>,
) => {
const { vaultUseTokenLevelGreenList = false } = optionalParams || {};

const { getTokenContractNames } = await importWithoutCache(
require.resolve('../../../../../helpers/contracts'),
);
Expand Down Expand Up @@ -41,6 +46,19 @@ export const getRvSwapperContractFromTemplate = async (mToken: MTokenName) => {
function vaultRole() public pure override returns (bytes32) {
return ${roles.redemptionVaultAdmin};
}

${
vaultUseTokenLevelGreenList
? `
/**
* @inheritdoc Greenlistable
*/
function greenlistedRole() public pure override returns (bytes32) {
return ${roles.greenlisted};
}
`
: ''
}
}`,
};
};
20 changes: 19 additions & 1 deletion scripts/deploy/codegen/common/templates/rv-ustb.template.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { MTokenName } from '../../../../../config';
import { importWithoutCache } from '../../../../../helpers/utils';

export const getRvUstbContractFromTemplate = async (mToken: MTokenName) => {
export const getRvUstbContractFromTemplate = async (
mToken: MTokenName,
optionalParams?: Record<string, unknown>,
) => {
const { vaultUseTokenLevelGreenList = false } = optionalParams || {};

const { getTokenContractNames } = await importWithoutCache(
require.resolve('../../../../../helpers/contracts'),
);
Expand Down Expand Up @@ -41,6 +46,19 @@ export const getRvUstbContractFromTemplate = async (mToken: MTokenName) => {
function vaultRole() public pure override returns (bytes32) {
return ${roles.redemptionVaultAdmin};
}

${
vaultUseTokenLevelGreenList
? `
/**
* @inheritdoc Greenlistable
*/
function greenlistedRole() public pure override returns (bytes32) {
return ${roles.greenlisted};
}
`
: ''
}
}`,
};
};
Loading
Loading