diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 00000000..bf292d24 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,81 @@ +# FAQ + +## IDE (Truffle, Hardhat, ...) + +> Why do you continue using Truffle instead of migrating to HardHat or Foundry? + +**Hardhat VS Truffle** + +- Our tests are not working with Hardhat so to migrate to hardhat, we will have to update our tests which will require a lot of works. +- Moreover, we do not see a use case where hardhat will be better than Truffle. +- Hardhat has a lot of plugins, but for example, for the coverage, we can run the coverage without be fully compatible with Hardhat. + +**Truffle VS Foundry** + +- The plugin "upgrades plugin" by OpenZeppelin is not available with Foundry and it is a very good tool to check the proxy implementation and perform automatic tests. See [https://docs.openzeppelin.com/upgrades-plugins/1.x/](https://docs.openzeppelin.com/upgrades-plugins/1.x/) +- The tests for the gasless module (MetaTx) will be difficult to write in Solidity, see [https://github.com/CMTA/CMTAT/blob/master/test/common/MetaTxModuleCommon.js](https://github.com/CMTA/CMTAT/blob/master/test/common/MetaTxModuleCommon.js) +- OpenZeppelin, the main libraries we use, have their tests mainly written in JavaScript, so it provides good examples for our tests +- But for performance, we have seen indeed that Foundry is better than Truffle, notably to test the Snapshot Module + +> Do you plan to support Foundry in the near Future? I see a CMTAT-Foundry repo. Is it reliable? + +No, it is currently not reliable. + +We have not planned to export all the tests in their Solidity version, but some tests are available + +The repo CMTAT-Foundry will have the latest CMTAT version + +Please, note that we provide only a minimal support for the foundry repository as well as Hardhat. + +We use Truffle to maintain the project. + +> Hardhat tests: are they really working in v2.3.0? + +No, please use Truffle to run the tests + +## Modules + +> Why the Snapshot module is not audited in the version v2.3.0? + +It was out of scope because it’s not really used yet and will likely be subject to changes soon. + +At deployment, this module is not included by default + +> What is the status for ERC1404 compatibility? + +We have not planned to be fully compatible since this ERC is not an ERC, it is only an EIP. + +To be fully compatible, we have to inherit of ERC20 inside the interface and it will break our architecture. + +See [https://erc1404.org/](https://erc1404.org/) + +> What is exactly the purpose of the flag parameter in BaseModule? +> I see that it’s a variable (uint256) to include some information, but I don’t see any use case in the code. + +It is just a variable to include some additional information under the form of bit flags. +It is not used inside the code because it is destined to provide more information on the tokens to the "outside", for example for the token owners. + + + +> Question regarding the ValidationModule optional module. +> +> Why is it optional? The module is required by Pauser and Enforcer mandatory modules + +- ValidationModule is optional from the legal perspective, but you can ask admin@cmta.ch to have a better/clearer information on that. +- It is the opposite: PauseModule and EnforcementModule are required to use the ValidationModule (but indeed, you actually need the ValidationModule for the functions to be called) +- If you remove the ValidationModule and want to use the Pause and Enforcement module, you have to call the functions of modules inside the main contracts. It was initially the case but we have changed this behaviour by fixing the CVF-1 +Here an old version: [https://github.com/CMTA/CMTAT/blob/ed23bfc69cfacc932945da751485c6472705c975/contracts/CMTAT.sol#L205](https://github.com/CMTA/CMTAT/blob/ed23bfc69cfacc932945da751485c6472705c975/contracts/CMTAT.sol#L205) +The PR: [https://github.com/CMTA/CMTAT/pull/153](https://github.com/CMTA/CMTAT/pull/153) +We could probably move the ValidationModule inside the mandatory modules and think about a better architecture (but probably not for the next release) + +## Documentation + +> What is the code coverage? + +A code coverage is available here: [https://github.com/CMTA/CMTAT/blob/master/doc/general/test/coverage/index.html](https://github.com/CMTA/CMTAT/blob/master/doc/general/test/coverage/index.html) + +Normally, you can run the code coverage with `npx hardhat coverage` + +Please clone the repository and open the file inside your navigator + +You will find a summary of all automatic tests in the file [test.pdf](https://github.com/CMTA/CMTAT/blob/master/doc/general/test/test.pdf) \ No newline at end of file diff --git a/README.md b/README.md index 6b5ed5e8..d2e23bae 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ The second audit covered version [2.2](https://github.com/CMTA/CMTAT/releases/ta Version 2.3 contains the different fixes and improvements related to this audit. -The report is available in [ABDK_CMTA_CMTATRuleEngine_v_1_0.pdf](doc/audits/ABDK_CMTA_CMTATRuleEngine_v_1_0.pdf). +The report is available in [ABDK_CMTA_CMTATRuleEngine_v_1_0.pdf](doc/audits/ABDK_CMTA_CMTATRuleEngine_v_1_0/ABDK_CMTA_CMTATRuleEngine_v_1_0.pdf). ### Tools diff --git a/contracts/modules/wrapper/mandatory/BurnModule.sol b/contracts/modules/wrapper/mandatory/BurnModule.sol index d9398030..d1d540b8 100644 --- a/contracts/modules/wrapper/mandatory/BurnModule.sol +++ b/contracts/modules/wrapper/mandatory/BurnModule.sol @@ -38,6 +38,7 @@ abstract contract BurnModule is ERC20Upgradeable, AuthorizationModule { * @dev Destroys `amount` tokens from `account` * * See {ERC20-_burn} + * Emits a {Burn} event */ function forceBurn( address account, @@ -48,5 +49,42 @@ abstract contract BurnModule is ERC20Upgradeable, AuthorizationModule { emit Burn(account, amount, reason); } + /** + * + * @dev batch version of {forceBurn}. + * + * See {ERC20-_burn} and {OpenZeppelin ERC1155_burnBatch}. + * + * Emits a {Burn} event by burn action. + * + * Requirements: + * - `tos` and `amounts` must have the same length + * - the caller must have the `BURNER_ROLE`. + */ + function forceBurnBatch( + address[] calldata accounts, + uint256[] calldata amounts, + string memory reason + ) public onlyRole(BURNER_ROLE) { + require( + accounts.length > 0, + "CMTAT: tos is empty" + ); + // We do not check that amounts is not empty since + // this require will throw an error in this case. + require( + accounts.length == amounts.length, + "CMTAT: accounts and amounts length mismatch" + ); + + for (uint256 i = 0; i < accounts.length; ) { + _burn(accounts[i], amounts[i]); + emit Burn(accounts[i], amounts[i], reason); + unchecked { + ++i; + } + } + } + uint256[50] private __gap; } diff --git a/contracts/modules/wrapper/mandatory/ERC20BaseModule.sol b/contracts/modules/wrapper/mandatory/ERC20BaseModule.sol index 90e2d766..11a60c9e 100644 --- a/contracts/modules/wrapper/mandatory/ERC20BaseModule.sol +++ b/contracts/modules/wrapper/mandatory/ERC20BaseModule.sol @@ -85,6 +85,42 @@ abstract contract ERC20BaseModule is ERC20Upgradeable { return result; } + /** + * @notice batch version of transfer + * @param tos can not be empty, must have the same length as values + * @param values can not be empty + * @dev See {OpenZeppelin ERC20-transfer & ERC1155-safeBatchTransferFrom}. + * + * + * Requirements: + * - `tos` and `values` must have the same length + * - `tos`cannot contain a zero address + * - the caller must have a balance cooresponding to the total values + */ + function transferBatch( + address[] calldata tos, + uint256[] calldata values + ) public { + require( + tos.length > 0, + "CMTAT: tos is empty" + ); + // We do not check that values is not empty since + // this require will throw an error in this case. + require( + tos.length == values.length, + "CMTAT: tos and values length mismatch" + ); + bool result; + for (uint256 i = 0; i < tos.length; ) { + result = ERC20Upgradeable.transfer(tos[i], values[i]); + require(result, "CMTAT: transfer failed"); + unchecked { + ++i; + } + } + } + /** * @dev See {IERC20-approve}. * @@ -106,4 +142,4 @@ abstract contract ERC20BaseModule is ERC20Upgradeable { } uint256[50] private __gap; -} +} \ No newline at end of file diff --git a/contracts/modules/wrapper/mandatory/MintModule.sol b/contracts/modules/wrapper/mandatory/MintModule.sol index 4d6bbaf6..c50f191b 100644 --- a/contracts/modules/wrapper/mandatory/MintModule.sol +++ b/contracts/modules/wrapper/mandatory/MintModule.sol @@ -48,5 +48,41 @@ abstract contract MintModule is ERC20Upgradeable, AuthorizationModule { emit Mint(to, amount); } + /** + * + * @dev batch version of {mint}. + * + * See {ERC20-_mint} and {OpenZeppelin ERC1155_mintBatch}. + * + * Emits a {Mint} event. + * + * Requirements: + * - `tos` and `amounts` must have the same length + * - the caller must have the `MINTER_ROLE`. + */ + function mintBatch( + address[] calldata tos, + uint256[] calldata amounts + ) public onlyRole(MINTER_ROLE) { + require( + tos.length > 0, + "CMTAT: tos is empty" + ); + // We do not check that amounts is not empty since + // this require will throw an error in this case. + require( + tos.length == amounts.length, + "CMTAT: tos and amounts length mismatch" + ); + + for (uint256 i = 0; i < tos.length; ) { + _mint(tos[i], amounts[i]); + emit Mint(tos[i], amounts[i]); + unchecked { + ++i; + } + } + } + uint256[50] private __gap; } diff --git a/doc/audits/ABDK-CMTAT-audit-20210910.pdf b/doc/audits/ABDK-CMTAT-audit-20210910/ABDK-CMTAT-audit-20210910.pdf similarity index 100% rename from doc/audits/ABDK-CMTAT-audit-20210910.pdf rename to doc/audits/ABDK-CMTAT-audit-20210910/ABDK-CMTAT-audit-20210910.pdf diff --git a/doc/audits/workDocument/CMTAT-Audit-20210910-summary.odt b/doc/audits/ABDK-CMTAT-audit-20210910/CMTAT-Audit-20210910-summary.odt similarity index 100% rename from doc/audits/workDocument/CMTAT-Audit-20210910-summary.odt rename to doc/audits/ABDK-CMTAT-audit-20210910/CMTAT-Audit-20210910-summary.odt diff --git a/doc/audits/CMTAT-Audit-20210910-summary.pdf b/doc/audits/ABDK-CMTAT-audit-20210910/CMTAT-Audit-20210910-summary.pdf similarity index 100% rename from doc/audits/CMTAT-Audit-20210910-summary.pdf rename to doc/audits/ABDK-CMTAT-audit-20210910/CMTAT-Audit-20210910-summary.pdf diff --git a/doc/audits/ABDK_CMTA_CMTATRuleEngine_v_1_0.pdf b/doc/audits/ABDK_CMTA_CMTATRuleEngine_v_1_0/ABDK_CMTA_CMTATRuleEngine_v_1_0.pdf similarity index 100% rename from doc/audits/ABDK_CMTA_CMTATRuleEngine_v_1_0.pdf rename to doc/audits/ABDK_CMTA_CMTATRuleEngine_v_1_0/ABDK_CMTA_CMTATRuleEngine_v_1_0.pdf diff --git a/doc/audits/ABDK_CMTA_CMTATRuleEngine_v_1_0/Taurus. Audit 3.3. Collected.ods b/doc/audits/ABDK_CMTA_CMTATRuleEngine_v_1_0/Taurus. Audit 3.3. Collected.ods new file mode 100644 index 00000000..a544cd7e Binary files /dev/null and b/doc/audits/ABDK_CMTA_CMTATRuleEngine_v_1_0/Taurus. Audit 3.3. Collected.ods differ diff --git a/doc/audits/ABDK_CMTA_CMTATRuleEngine_v_1_0/Taurus.Audit3.1.CollectedIssues.ods b/doc/audits/ABDK_CMTA_CMTATRuleEngine_v_1_0/Taurus.Audit3.1.CollectedIssues.ods new file mode 100644 index 00000000..683b6679 Binary files /dev/null and b/doc/audits/ABDK_CMTA_CMTATRuleEngine_v_1_0/Taurus.Audit3.1.CollectedIssues.ods differ diff --git a/doc/audits/workDocument/Taurus.Audit3.1.CollectedIssues.ods b/doc/audits/workDocument/Taurus.Audit3.1.CollectedIssues.ods deleted file mode 100644 index 2221c13c..00000000 Binary files a/doc/audits/workDocument/Taurus.Audit3.1.CollectedIssues.ods and /dev/null differ diff --git a/doc/modules/presentation/mandatory/burn.md b/doc/modules/presentation/mandatory/burn.md index 62b7311e..67ff7cc5 100644 --- a/doc/modules/presentation/mandatory/burn.md +++ b/doc/modules/presentation/mandatory/burn.md @@ -26,28 +26,6 @@ This document defines Burn Module for the CMTA Token specification. -## Sūrya's Description Report - -### Files Description Table - - -| File Name | SHA-1 Hash | -| ------------------------------------------ | ---------------------------------------- | -| ./modules/wrapper/mandatory/BurnModule.sol | 3547e217049388e5b1a48524255301aac8d301de | - - -### Contracts Description Table - - -| Contract | Type | Bases | | | -| :------------: | :-------------------------: | :-----------------------------------: | :------------: | :--------------: | -| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | -| | | | | | -| **BurnModule** | Implementation | ERC20Upgradeable, AuthorizationModule | | | -| └ | __BurnModule_init | Internal 🔒 | 🛑 | onlyInitializing | -| └ | __BurnModule_init_unchained | Internal 🔒 | 🛑 | onlyInitializing | -| └ | forceBurn | Public ❗️ | 🛑 | onlyRole | - ### Legend @@ -64,28 +42,44 @@ This section describes the Ethereum API of Burn Module. #### `forceBurn(address,uint256,string)` -##### Signature: +##### Definition ```solidity function forceBurn(address account,uint256 amount,string memory reason) public onlyRole(BURNER_ROLE) ``` -##### Description: +##### Description Redeem the given `amount` of tokens from the given `account`. Only authorized users are allowed to call this function. +#### `forceBurnBatch(address[],uint256[],string) ` + +##### Definition + +```solidity +function forceBurnBatch(address[] calldata accounts,uint256[] calldata amounts,string memory reason) +public onlyRole(BURNER_ROLE) +``` + +##### Description + +For each account in `accounts`, redeem the corresponding amount of tokens given by `amounts`. +Only authorized users are allowed to call this function. + +The burn `reason`is the same for all `accounts` which tokens are burnt. + ### Events #### `Burn(address,uint,string)` -##### Signature: +##### Definition ```solidity event Burn(address indexed owner, uint256 amount, string reason) ``` -##### Description: +##### Description Emitted when the specified `amount` of tokens was burnt from the specified `account`. diff --git a/doc/modules/presentation/mandatory/enforcement.md b/doc/modules/presentation/mandatory/enforcement.md index e7ab2a8b..77e7c34e 100644 --- a/doc/modules/presentation/mandatory/enforcement.md +++ b/doc/modules/presentation/mandatory/enforcement.md @@ -14,7 +14,7 @@ This document defines Enforcement Module for the CMTA Token specification. #### EnforcementModule -![surya_inheritance_EnforcementModule.sol](/home/ryan/Downloads/CM/cmtat-2.3/CMTAT-doc/doc/modules/schema/surya_inheritance/surya_inheritance_EnforcementModule.sol.png) +![surya_inheritance_EnforcementModule.sol](../../schema/surya_inheritance/surya_inheritance_EnforcementModule.sol.png) #### EnforcementModuleInternal diff --git a/doc/modules/presentation/mandatory/mint.md b/doc/modules/presentation/mandatory/mint.md index 6651c6d1..7a011814 100644 --- a/doc/modules/presentation/mandatory/mint.md +++ b/doc/modules/presentation/mandatory/mint.md @@ -33,7 +33,7 @@ This document defines Mint Module for the CMTA Token specification. | File Name | SHA-1 Hash | | ------------------------------------------ | ---------------------------------------- | -| ./modules/wrapper/mandatory/MintModule.sol | c0300d093480b66e7a9c5acd1a1c46c34f6221bb | +| ./modules/wrapper/mandatory/MintModule.sol | 59896c200ba366d171fc377d8b78d757aefbc69d | ### Contracts Description Table @@ -47,6 +47,7 @@ This document defines Mint Module for the CMTA Token specification. | └ | __MintModule_init | Internal 🔒 | 🛑 | onlyInitializing | | └ | __MintModule_init_unchained | Internal 🔒 | 🛑 | onlyInitializing | | └ | mint | Public ❗️ | 🛑 | onlyRole | +| └ | mintBatch | Public ❗️ | 🛑 | onlyRole | ### Legend @@ -56,6 +57,8 @@ This document defines Mint Module for the CMTA Token specification. | 🛑 | Function can modify state | | 💵 | Function is payable | + + ## API for Ethereum This section describes the Ethereum API of Issue Module. @@ -64,23 +67,37 @@ This section describes the Ethereum API of Issue Module. #### `mint(address,uint256)` -##### Definition: +##### Definition ```solidity function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) ``` -##### Description: +##### Description Create the given `amount` of tokens and allocate them to the given address`to`. Only authorized users are allowed to call this function. +#### `mintBatch(address[],uint256[]) ` + +##### Definition + +```solidity +function mintBatch(address[] calldata to,uint256[] calldata amounts) +public onlyRole(MINTER_ROLE) +``` + +##### Description + +For each address in `to`, create the corresponding amount of tokens given by `amounts` and allocate them to the given address`to`. +Only authorized users are allowed to call this function. + ### Events #### `Mint(address,uint256)` -##### Definition: +##### Definition ```solidity diff --git a/doc/modules/schema/sol2uml/mandatory/BurnModule.svg b/doc/modules/schema/sol2uml/mandatory/BurnModule.svg index 6a6c60e2..08a35b36 100644 --- a/doc/modules/schema/sol2uml/mandatory/BurnModule.svg +++ b/doc/modules/schema/sol2uml/mandatory/BurnModule.svg @@ -4,69 +4,70 @@ - - + + UmlClassDiagram - + cluster_0 - -contracts/modules/security + +contracts/modules/security cluster_1 - + contracts/modules/wrapper/mandatory - + -19 - -<<Abstract>> -AuthorizationModule -contracts/modules/security/AuthorizationModule.sol - -Private: -   __gap: uint256[50] -Public: -   BURNER_ROLE: bytes32 -   ENFORCER_ROLE: bytes32 -   MINTER_ROLE: bytes32 -   PAUSER_ROLE: bytes32 -   SNAPSHOOTER_ROLE: bytes32 -   DEBT_ROLE: bytes32 -   DEBT_CREDIT_EVENT_ROLE: bytes32 - -Internal: -    __AuthorizationModule_init(admin: address) <<onlyInitializing>> -    __AuthorizationModule_init_unchained(admin: address) <<onlyInitializing>> -Public: -    hasRole(role: bytes32, account: address): bool +17 + +<<Abstract>> +AuthorizationModule +contracts/modules/security/AuthorizationModule.sol + +Private: +   __gap: uint256[50] +Public: +   BURNER_ROLE: bytes32 +   ENFORCER_ROLE: bytes32 +   MINTER_ROLE: bytes32 +   PAUSER_ROLE: bytes32 +   SNAPSHOOTER_ROLE: bytes32 +   DEBT_ROLE: bytes32 +   DEBT_CREDIT_EVENT_ROLE: bytes32 + +Internal: +    __AuthorizationModule_init(admin: address) <<onlyInitializing>> +    __AuthorizationModule_init_unchained(admin: address) <<onlyInitializing>> +Public: +    hasRole(role: bytes32, account: address): bool - + -24 - -<<Abstract>> -BurnModule -contracts/modules/wrapper/mandatory/BurnModule.sol - -Private: -   __gap: uint256[50] - -Internal: -    __BurnModule_init(name_: string, symbol_: string, admin: address) <<onlyInitializing>> -    __BurnModule_init_unchained() <<onlyInitializing>> -Public: -    <<event>> Burn(owner: address, amount: uint256, reason: string) -    forceBurn(account: address, amount: uint256, reason: string) <<onlyRole>> +27 + +<<Abstract>> +BurnModule +contracts/modules/wrapper/mandatory/BurnModule.sol + +Private: +   __gap: uint256[50] + +Internal: +    __BurnModule_init(name_: string, symbol_: string, admin: address) <<onlyInitializing>> +    __BurnModule_init_unchained() <<onlyInitializing>> +Public: +    <<event>> Burn(owner: address, amount: uint256, reason: string) +    forceBurn(account: address, amount: uint256, reason: string) <<onlyRole>> +    forceBurnBatch(accounts: address[], amounts: uint256[], reason: string) <<onlyRole>> - + -24->19 - - +27->17 + + diff --git a/doc/modules/schema/sol2uml/mandatory/MintModule.svg b/doc/modules/schema/sol2uml/mandatory/MintModule.svg index ab3444ef..74239d2c 100644 --- a/doc/modules/schema/sol2uml/mandatory/MintModule.svg +++ b/doc/modules/schema/sol2uml/mandatory/MintModule.svg @@ -4,69 +4,70 @@ - - + + UmlClassDiagram - + cluster_0 - -contracts/modules/security + +contracts/modules/security cluster_1 - + contracts/modules/wrapper/mandatory - + -19 - -<<Abstract>> -AuthorizationModule -contracts/modules/security/AuthorizationModule.sol - -Private: -   __gap: uint256[50] -Public: -   BURNER_ROLE: bytes32 -   ENFORCER_ROLE: bytes32 -   MINTER_ROLE: bytes32 -   PAUSER_ROLE: bytes32 -   SNAPSHOOTER_ROLE: bytes32 -   DEBT_ROLE: bytes32 -   DEBT_CREDIT_EVENT_ROLE: bytes32 - -Internal: -    __AuthorizationModule_init(admin: address) <<onlyInitializing>> -    __AuthorizationModule_init_unchained(admin: address) <<onlyInitializing>> -Public: -    hasRole(role: bytes32, account: address): bool +17 + +<<Abstract>> +AuthorizationModule +contracts/modules/security/AuthorizationModule.sol + +Private: +   __gap: uint256[50] +Public: +   BURNER_ROLE: bytes32 +   ENFORCER_ROLE: bytes32 +   MINTER_ROLE: bytes32 +   PAUSER_ROLE: bytes32 +   SNAPSHOOTER_ROLE: bytes32 +   DEBT_ROLE: bytes32 +   DEBT_CREDIT_EVENT_ROLE: bytes32 + +Internal: +    __AuthorizationModule_init(admin: address) <<onlyInitializing>> +    __AuthorizationModule_init_unchained(admin: address) <<onlyInitializing>> +Public: +    hasRole(role: bytes32, account: address): bool - + -27 - -<<Abstract>> -MintModule -contracts/modules/wrapper/mandatory/MintModule.sol - -Private: -   __gap: uint256[50] - -Internal: -    __MintModule_init(name_: string, symbol_: string, admin: address) <<onlyInitializing>> -    __MintModule_init_unchained() <<onlyInitializing>> -Public: -    <<event>> Mint(beneficiary: address, amount: uint256) -    mint(to: address, amount: uint256) <<onlyRole>> +30 + +<<Abstract>> +MintModule +contracts/modules/wrapper/mandatory/MintModule.sol + +Private: +   __gap: uint256[50] + +Internal: +    __MintModule_init(name_: string, symbol_: string, admin: address) <<onlyInitializing>> +    __MintModule_init_unchained() <<onlyInitializing>> +Public: +    <<event>> Mint(beneficiary: address, amount: uint256) +    mint(to: address, amount: uint256) <<onlyRole>> +    mintBatch(to: address[], amounts: uint256[]) <<onlyRole>> - + -27->19 - - +30->17 + + diff --git a/doc/modules/schema/surya_graph/surya_graph_BurnModule.sol.png b/doc/modules/schema/surya_graph/surya_graph_BurnModule.sol.png index 7b621eb5..2c44984c 100644 Binary files a/doc/modules/schema/surya_graph/surya_graph_BurnModule.sol.png and b/doc/modules/schema/surya_graph/surya_graph_BurnModule.sol.png differ diff --git a/doc/modules/schema/surya_graph/surya_graph_MintModule.sol.png b/doc/modules/schema/surya_graph/surya_graph_MintModule.sol.png index abe2b073..ab81b30a 100644 Binary files a/doc/modules/schema/surya_graph/surya_graph_MintModule.sol.png and b/doc/modules/schema/surya_graph/surya_graph_MintModule.sol.png differ diff --git a/package.json b/package.json index f671a668..4def96f4 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,11 @@ "lint:js:fix": "npx eslint test --fix", "lint:sol": "npx solium -d contracts", "lint:sol:fix": "npx solium -d contracts --fix", - "lint:all": "npx run lint && npm run lint:sol", - "lint:all:fix": "npx run lint:fix && npm run lint:sol:fix", + "lint:all": "npx run lint && npx run lint:sol", + "lint:all:fix": "npm run-script lint:js:fix && npm run-script lint:sol:fix", "lint:sol:prettier": "npx prettier --write 'contracts/**/*.sol'", "lint:js:prettier": "npx prettier test", + "lint:all:prettier": "npm run-script lint:js:prettier && npm run-script lint:sol:prettier", "console": "truffle console", "coverage": "npx hardhat coverage", "docgen": "npx hardhat docgen", diff --git a/test/common/BurnModuleCommon.js b/test/common/BurnModuleCommon.js index d3c235e8..c460a8ca 100644 --- a/test/common/BurnModuleCommon.js +++ b/test/common/BurnModuleCommon.js @@ -1,19 +1,23 @@ -const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers') +const { BN, expectEvent, expectRevert } = require('@openzeppelin/test-helpers') const { BURNER_ROLE, ZERO_ADDRESS } = require('../utils') const { should } = require('chai').should() function BurnModuleCommon (admin, address1, address2) { context('Burn', function () { + const INITIAL_SUPPLY = new BN(50) + const REASON = 'BURN_TEST' + const VALUE1 = new BN(20) + const DIFFERENCE = INITIAL_SUPPLY.sub(VALUE1) + beforeEach(async function () { - await this.cmtat.mint(address1, 50, { from: admin }); - (await this.cmtat.totalSupply()).should.be.bignumber.equal('50') + await this.cmtat.mint(address1, INITIAL_SUPPLY, { from: admin }); + (await this.cmtat.totalSupply()).should.be.bignumber.equal(INITIAL_SUPPLY) }) - it('testCanBeBurntByAdminWithAllowance', async function () { - const reason = 'BURN_TEST'; + it('testCanBeBurntByAdmin', async function () { // Act // Burn 20 - ({ logs: this.logs1 } = await this.cmtat.forceBurn(address1, 20, reason, { + ({ logs: this.logs1 } = await this.cmtat.forceBurn(address1, VALUE1, REASON, { from: admin })) // Assert @@ -21,21 +25,21 @@ function BurnModuleCommon (admin, address1, address2) { expectEvent.inLogs(this.logs1, 'Transfer', { from: address1, to: ZERO_ADDRESS, - value: '20' + value: VALUE1 }) // Emits a Burn event expectEvent.inLogs(this.logs1, 'Burn', { owner: address1, - amount: '20', - reason + amount: VALUE1, + reason: REASON }); // Check balances and total supply - (await this.cmtat.balanceOf(address1)).should.be.bignumber.equal('30'); - (await this.cmtat.totalSupply()).should.be.bignumber.equal('30'); + (await this.cmtat.balanceOf(address1)).should.be.bignumber.equal(DIFFERENCE); + (await this.cmtat.totalSupply()).should.be.bignumber.equal(DIFFERENCE); // Burn 30 // Act - ({ logs: this.logs2 } = await this.cmtat.forceBurn(address1, 30, reason, { + ({ logs: this.logs2 } = await this.cmtat.forceBurn(address1, DIFFERENCE, REASON, { from: admin })) // Assert @@ -43,40 +47,39 @@ function BurnModuleCommon (admin, address1, address2) { expectEvent.inLogs(this.logs2, 'Transfer', { from: address1, to: ZERO_ADDRESS, - value: '30' + value: DIFFERENCE }) // Emits a Burn event expectEvent.inLogs(this.logs2, 'Burn', { owner: address1, - amount: '30', - reason + amount: DIFFERENCE, + reason: REASON }); // Check balances and total supply - (await this.cmtat.balanceOf(address1)).should.be.bignumber.equal('0'); - (await this.cmtat.totalSupply()).should.be.bignumber.equal('0') + (await this.cmtat.balanceOf(address1)).should.be.bignumber.equal(BN(0)); + (await this.cmtat.totalSupply()).should.be.bignumber.equal(BN(0)) }) it('testCanBeBurntByBurnerRole', async function () { - const reason = 'BURN_TEST' // Arrange await this.cmtat.grantRole(BURNER_ROLE, address2, { from: admin }); // Act - ({ logs: this.logs } = await this.cmtat.forceBurn(address1, 20, reason, { from: address2 })); + ({ logs: this.logs } = await this.cmtat.forceBurn(address1, VALUE1, REASON, { from: address2 })); // Assert - (await this.cmtat.balanceOf(address1)).should.be.bignumber.equal('30'); - (await this.cmtat.totalSupply()).should.be.bignumber.equal('30') + (await this.cmtat.balanceOf(address1)).should.be.bignumber.equal(DIFFERENCE); + (await this.cmtat.totalSupply()).should.be.bignumber.equal(DIFFERENCE) // Emits a Transfer event expectEvent.inLogs(this.logs, 'Transfer', { from: address1, to: ZERO_ADDRESS, - value: '20' + value: VALUE1 }) // Emits a Burn event expectEvent.inLogs(this.logs, 'Burn', { owner: address1, - amount: '20', - reason + amount: VALUE1, + reason: REASON }) }) @@ -99,5 +102,135 @@ function BurnModuleCommon (admin, address1, address2) { ) }) }) + context('BurnBatch', function () { + const REASON = 'BURN_TEST' + const TOKEN_HOLDER = [admin, address1, address2] + const TOKEN_SUPPLY_BY_HOLDERS = [BN(10), BN(100), BN(1000)] + const INITIAL_SUPPLY = TOKEN_SUPPLY_BY_HOLDERS.reduce((a, b) => { return a.add(b) }) + const TOKEN_BY_HOLDERS_TO_BURN = [BN(5), BN(50), BN(500)] + const TOKEN_BALANCE_BY_HOLDERS_AFTER_BURN = [ + TOKEN_SUPPLY_BY_HOLDERS[0].sub(TOKEN_BY_HOLDERS_TO_BURN[0]), + TOKEN_SUPPLY_BY_HOLDERS[1].sub(TOKEN_BY_HOLDERS_TO_BURN[1]), + TOKEN_SUPPLY_BY_HOLDERS[2].sub(TOKEN_BY_HOLDERS_TO_BURN[2])] + const TOTAL_SUPPLY_AFTER_BURN = INITIAL_SUPPLY.sub(TOKEN_BY_HOLDERS_TO_BURN.reduce((a, b) => { return a.add(b) })) + + beforeEach(async function () { + // await this.cmtat.mint(address1, INITIAL_SUPPLY, { from: admin }); + ({ logs: this.logs1 } = await this.cmtat.mintBatch(TOKEN_HOLDER, TOKEN_SUPPLY_BY_HOLDERS, { + from: admin + })); + (await this.cmtat.totalSupply()).should.be.bignumber.equal(INITIAL_SUPPLY) + }) + + it('testCanBeBurntBatchByAdmin', async function () { + // Act + // Burn + ({ logs: this.logs1 } = await this.cmtat.forceBurnBatch(TOKEN_HOLDER, TOKEN_BY_HOLDERS_TO_BURN, REASON, { + from: admin + })) + // Assert + // emits a Transfer event + // Assert event + // emits a Transfer event + for (let i = 0; i < TOKEN_HOLDER.length; ++i) { + // emits a Mint event + expectEvent.inLogs(this.logs1, 'Transfer', { + from: TOKEN_HOLDER[i], + to: ZERO_ADDRESS, + value: TOKEN_BY_HOLDERS_TO_BURN[i] + }) + } + + for (let i = 0; i < TOKEN_HOLDER.length; ++i) { + // emits a Mint event + expectEvent.inLogs(this.logs1, 'Burn', { + owner: TOKEN_HOLDER[i], + amount: TOKEN_BY_HOLDERS_TO_BURN[i], + reason: REASON + }) + } + // Check balances and total supply + // Assert + for (let i = 0; i < TOKEN_HOLDER.length; ++i) { + (await this.cmtat.balanceOf(TOKEN_HOLDER[i])).should.be.bignumber.equal(TOKEN_BALANCE_BY_HOLDERS_AFTER_BURN[i]) + } + + (await this.cmtat.totalSupply()).should.be.bignumber.equal(TOTAL_SUPPLY_AFTER_BURN) + }) + + it('testCanBeBurntByBurnerRole', async function () { + // Arrange + await this.cmtat.grantRole(BURNER_ROLE, address2, { from: admin }); + + // Act + // Burn + ({ logs: this.logs1 } = await this.cmtat.forceBurnBatch(TOKEN_HOLDER, TOKEN_BY_HOLDERS_TO_BURN, REASON, { + from: address2 + })) + // Assert + // emits a Transfer event + // Assert event + // emits a Transfer event + for (let i = 0; i < TOKEN_HOLDER.length; ++i) { + // emits a Mint event + expectEvent.inLogs(this.logs1, 'Transfer', { + from: TOKEN_HOLDER[i], + to: ZERO_ADDRESS, + value: TOKEN_BY_HOLDERS_TO_BURN[i] + }) + } + + for (let i = 0; i < TOKEN_HOLDER.length; ++i) { + // emits a Mint event + expectEvent.inLogs(this.logs1, 'Burn', { + owner: TOKEN_HOLDER[i], + amount: TOKEN_BY_HOLDERS_TO_BURN[i] + }) + } + // Check balances and total supply + // Assert + for (let i = 0; i < TOKEN_HOLDER.length; ++i) { + (await this.cmtat.balanceOf(TOKEN_HOLDER[i])).should.be.bignumber.equal(TOKEN_BALANCE_BY_HOLDERS_AFTER_BURN[i]) + } + + (await this.cmtat.totalSupply()).should.be.bignumber.equal(TOTAL_SUPPLY_AFTER_BURN) + }) + + it('testCannotBeBurntIfOneBalanceExceeds', async function () { + const TOKEN_BY_HOLDERS_TO_BURN_FAIL = [BN(5), BN(50), BN(5000000)] + // Act + await expectRevert( + this.cmtat.forceBurnBatch(TOKEN_HOLDER, TOKEN_BY_HOLDERS_TO_BURN_FAIL, '', { from: admin }), + 'ERC20: burn amount exceeds balance' + ) + }) + + it('testCannotBeBurntWithoutBurnerRole', async function () { + // Act + await expectRevert( + this.cmtat.forceBurnBatch(TOKEN_HOLDER, TOKEN_BY_HOLDERS_TO_BURN, '', { from: address2 }), + 'AccessControl: account ' + + address2.toLowerCase() + + ' is missing role ' + + BURNER_ROLE + ) + }) + + it('testCannotBurnIfLengthMismatch', async function () { + const TOKEN_HOLDER_INVALID = [admin, address1] + await expectRevert( + this.cmtat.forceBurnBatch(TOKEN_HOLDER_INVALID, TOKEN_BY_HOLDERS_TO_BURN, REASON, { from: admin }), + 'CMTAT: accounts and amounts length mismatch' + ) + }) + + it('testCannotBurnBatchIfAccountsIsEmpty', async function () { + const TOKEN_ADDRESS_TOS_INVALID = [] + await expectRevert( + this.cmtat.transferBatch(TOKEN_ADDRESS_TOS_INVALID, TOKEN_BY_HOLDERS_TO_BURN, { from: admin }), + 'CMTAT: tos is empty' + ) + }) + }) } module.exports = BurnModuleCommon diff --git a/test/common/ERC20BaseModuleCommon.js b/test/common/ERC20BaseModuleCommon.js index 879f480c..fa3d15dd 100644 --- a/test/common/ERC20BaseModuleCommon.js +++ b/test/common/ERC20BaseModuleCommon.js @@ -1,8 +1,8 @@ -const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers') +const { BN, expectEvent, expectRevert } = require('@openzeppelin/test-helpers') const { DEFAULT_ADMIN_ROLE } = require('../utils') const { should } = require('chai').should() -function BaseModuleCommon (owner, address1, address2, address3, proxyTest) { +function BaseModuleCommon (admin, address1, address2, address3, proxyTest) { context('Token structure', function () { it('testHasTheDefinedName', async function () { // Act + Assert @@ -177,9 +177,9 @@ function BaseModuleCommon (owner, address1, address2, address3, proxyTest) { context('Transfer', function () { beforeEach(async function () { - await this.cmtat.mint(address1, 31, { from: owner }) - await this.cmtat.mint(address2, 32, { from: owner }) - await this.cmtat.mint(address3, 33, { from: owner }) + await this.cmtat.mint(address1, 31, { from: admin }) + await this.cmtat.mint(address2, 32, { from: admin }) + await this.cmtat.mint(address3, 33, { from: admin }) }) it('testTransferFromOneAccountToAnother', async function () { @@ -273,5 +273,96 @@ function BaseModuleCommon (owner, address1, address2, address3, proxyTest) { ) }) }) + + context('transferFrom', function () { + beforeEach(async function () { + await this.cmtat.mint(address1, 31, { from: admin }) + await this.cmtat.mint(address2, 32, { from: admin }) + await this.cmtat.mint(address3, 33, { from: admin }) + }) + + it('testTransferFromOneAccountToAnother', async function () { + // Act + ({ logs: this.logs } = await this.cmtat.transfer(address2, 11, { + from: address1 + })); + // Assert + (await this.cmtat.balanceOf(address1)).should.be.bignumber.equal('20'); + (await this.cmtat.balanceOf(address2)).should.be.bignumber.equal('43'); + (await this.cmtat.balanceOf(address3)).should.be.bignumber.equal('33'); + (await this.cmtat.totalSupply()).should.be.bignumber.equal('96') + // emits a Transfer event + expectEvent.inLogs(this.logs, 'Transfer', { + from: address1, + to: address2, + value: '11' + }) + }) + + // ADDRESS1 -> ADDRESS2 + it('testCannotTransferMoreTokensThanOwn', async function () { + // Act + await expectRevert( + this.cmtat.transfer(address2, 50, { from: address1 }), + 'ERC20: transfer amount exceeds balance' + ) + }) + }) + + context('transferBatch', function () { + const TOKEN_ADDRESS_TOS = [address1, address2, address3] + const TOKEN_AMOUNTS = [BN(10), BN(100), BN(1000)] + + beforeEach(async function () { + await this.cmtat.mint(admin, TOKEN_AMOUNTS.reduce((a, b) => { return a.add(b) }), { from: admin }) + }) + + it('testTransferBatch', async function () { + // Act + ({ logs: this.logs } = await this.cmtat.transferBatch(TOKEN_ADDRESS_TOS, TOKEN_AMOUNTS, { + from: admin + })); + // Assert + for (let i = 0; i < TOKEN_ADDRESS_TOS.length; ++i) { + (await this.cmtat.balanceOf(TOKEN_ADDRESS_TOS[i])).should.be.bignumber.equal(TOKEN_AMOUNTS[i]) + } + // emits a Transfer event + for (let i = 0; i < TOKEN_ADDRESS_TOS.length; ++i) { + expectEvent.inLogs(this.logs, 'Transfer', { + from: admin, + to: TOKEN_ADDRESS_TOS[i], + value: TOKEN_AMOUNTS[i] + }) + } + }) + + // ADDRESS1 -> ADDRESS2 + it('testCannotTransferBatchMoreTokensThanOwn', async function () { + const TOKEN_AMOUNTS_INVALID = [TOKEN_AMOUNTS[0], TOKEN_AMOUNTS[1].add(BN(1)), TOKEN_AMOUNTS[2]] + // Act + await expectRevert( + this.cmtat.transferBatch(TOKEN_ADDRESS_TOS, TOKEN_AMOUNTS_INVALID, { + from: admin + }), + 'ERC20: transfer amount exceeds balance' + ) + }) + + it('testCannotTransferBatchIfLengthMismatch', async function () { + const TOKEN_ADDRESS_TOS_INVALID = [address1, address2] + await expectRevert( + this.cmtat.transferBatch(TOKEN_ADDRESS_TOS_INVALID, TOKEN_AMOUNTS, { from: admin }), + 'CMTAT: tos and values length mismatch' + ) + }) + + it('testCannotTransferBatchIfTOSIsEmpty', async function () { + const TOKEN_ADDRESS_TOS_INVALID = [] + await expectRevert( + this.cmtat.transferBatch(TOKEN_ADDRESS_TOS_INVALID, TOKEN_AMOUNTS, { from: admin }), + 'CMTAT: tos is empty' + ) + }) + }) } module.exports = BaseModuleCommon diff --git a/test/common/MintModuleCommon.js b/test/common/MintModuleCommon.js index 47f915b0..1152beba 100644 --- a/test/common/MintModuleCommon.js +++ b/test/common/MintModuleCommon.js @@ -1,92 +1,231 @@ -const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers') +const { BN, expectEvent, expectRevert } = require('@openzeppelin/test-helpers') const { ZERO_ADDRESS, MINTER_ROLE } = require('../utils') const { should } = require('chai').should() function MintModuleCommon (admin, address1, address2) { context('Minting', function () { + const VALUE1 = new BN(20) + const VALUE2 = new BN(50) /** The admin is assigned the MINTER role when the contract is deployed */ it('testCanBeMintedByAdmin', async function () { + // Arrange + // Arrange - Assert // Check first balance - (await this.cmtat.balanceOf(admin)).should.be.bignumber.equal('0'); + (await this.cmtat.balanceOf(admin)).should.be.bignumber.equal(BN(0)); // Act // Issue 20 and check balances and total supply - ({ logs: this.logs1 } = await this.cmtat.mint(address1, 20, { + ({ logs: this.logs1 } = await this.cmtat.mint(address1, VALUE1, { from: admin })); // Assert - (await this.cmtat.balanceOf(address1)).should.be.bignumber.equal('20'); - (await this.cmtat.totalSupply()).should.be.bignumber.equal('20'); + (await this.cmtat.balanceOf(address1)).should.be.bignumber.equal(VALUE1); + (await this.cmtat.totalSupply()).should.be.bignumber.equal(VALUE1) + + // Assert event + // emits a Transfer event + expectEvent.inLogs(this.logs1, 'Transfer', { + from: ZERO_ADDRESS, + to: address1, + value: VALUE1 + }) + // emits a Mint event + expectEvent.inLogs(this.logs1, 'Mint', { + beneficiary: address1, + amount: VALUE1 + }); // Act // Issue 50 and check intermediate balances and total supply - ({ logs: this.logs2 } = await this.cmtat.mint(address2, 50, { + ({ logs: this.logs2 } = await this.cmtat.mint(address2, VALUE2, { from: admin })); // Assert - (await this.cmtat.balanceOf(address2)).should.be.bignumber.equal('50'); - (await this.cmtat.totalSupply()).should.be.bignumber.equal('70') + (await this.cmtat.balanceOf(address2)).should.be.bignumber.equal(VALUE2); + (await this.cmtat.totalSupply()).should.be.bignumber.equal(VALUE1.add(VALUE2)) // Assert event // emits a Transfer event expectEvent.inLogs(this.logs2, 'Transfer', { from: ZERO_ADDRESS, to: address2, - value: '50' + value: VALUE2 }) // emits a Mint event expectEvent.inLogs(this.logs2, 'Mint', { beneficiary: address2, - amount: '50' + amount: VALUE2 }) }) - it('testCanBeMintedByANewMinter', async function () { + it('testCanMintByANewMinter', async function () { // Arrange await this.cmtat.grantRole(MINTER_ROLE, address1, { from: admin }); // Arrange - Assert // Check first balance - (await this.cmtat.balanceOf(admin)).should.be.bignumber.equal('0'); + (await this.cmtat.balanceOf(admin)).should.be.bignumber.equal(BN(0)); // Act // Issue 20 - ({ logs: this.logs1 } = await this.cmtat.mint(address1, 20, { + ({ logs: this.logs1 } = await this.cmtat.mint(address1, VALUE1, { from: address1 })); // Assert // Check balances and total supply - (await this.cmtat.balanceOf(address1)).should.be.bignumber.equal('20'); - (await this.cmtat.totalSupply()).should.be.bignumber.equal('20') + (await this.cmtat.balanceOf(address1)).should.be.bignumber.equal(VALUE1); + (await this.cmtat.totalSupply()).should.be.bignumber.equal(VALUE1) // Assert event // emits a Transfer event expectEvent.inLogs(this.logs1, 'Transfer', { from: ZERO_ADDRESS, to: address1, - value: '20' + value: VALUE1 }) // emits a Mint event expectEvent.inLogs(this.logs1, 'Mint', { beneficiary: address1, - amount: '20' + amount: VALUE1 }) }) // reverts when issuing by a non minter - it('testCannotIssuingByNonMinter', async function () { + it('testCannotMintByNonMinter', async function () { + await expectRevert( + this.cmtat.mint(address1, VALUE1, { from: address1 }), + 'AccessControl: account ' + + address1.toLowerCase() + + ' is missing role ' + + MINTER_ROLE + ) + }) + }) + + context('Batch Minting', function () { + const TOKEN_HOLDER = [admin, address1, address2] + const TOKEN_SUPPLY_BY_HOLDERS = [BN(10), BN(100), BN(1000)] + + /** + The admin is assigned the MINTER role when the contract is deployed + */ + it('testCanBeMintedBatchByAdmin', async function () { + // Arrange - Assert + // Check first balance + for (let i = 0; i < TOKEN_HOLDER.length; ++i) { + (await this.cmtat.balanceOf(TOKEN_HOLDER[i])).should.be.bignumber.equal(BN(0)) + } + + // Act + // Issue 20 and check balances and total supply + ({ logs: this.logs1 } = await this.cmtat.mintBatch(TOKEN_HOLDER, TOKEN_SUPPLY_BY_HOLDERS, { + from: admin + })) + + // Assert + for (let i = 0; i < TOKEN_HOLDER.length; ++i) { + (await this.cmtat.balanceOf(TOKEN_HOLDER[i])).should.be.bignumber.equal(TOKEN_SUPPLY_BY_HOLDERS[i]) + } + + (await this.cmtat.totalSupply()).should.be.bignumber.equal(TOKEN_SUPPLY_BY_HOLDERS.reduce((a, b) => { return a.add(b) })) + + // Assert event + // emits a Transfer event + for (let i = 0; i < TOKEN_HOLDER.length; ++i) { + // emits a Mint event + expectEvent.inLogs(this.logs1, 'Transfer', { + from: ZERO_ADDRESS, + to: TOKEN_HOLDER[i], + value: TOKEN_SUPPLY_BY_HOLDERS[i] + }) + } + + for (let i = 0; i < TOKEN_HOLDER.length; ++i) { + // emits a Mint event + expectEvent.inLogs(this.logs1, 'Mint', { + beneficiary: TOKEN_HOLDER[i], + amount: TOKEN_SUPPLY_BY_HOLDERS[i] + }) + } + }) + + it('testCanBeMinteBatchdByANewMinter', async function () { + // Arrange + await this.cmtat.grantRole(MINTER_ROLE, address1, { from: admin }) + const TOKEN_HOLDER = [admin, address1, address2] + const TOKEN_SUPPLY_BY_HOLDERS = [BN(10), BN(100), BN(1000)] + + // Arrange - Assert + // Check first balance + for (let i = 0; i < TOKEN_HOLDER.length; ++i) { + (await this.cmtat.balanceOf(TOKEN_HOLDER[i])).should.be.bignumber.equal(BN(0)) + } + + // Act + // Issue 20 and check balances and total supply + ({ logs: this.logs1 } = await this.cmtat.mintBatch(TOKEN_HOLDER, TOKEN_SUPPLY_BY_HOLDERS, { + from: address1 + })) + + // Assert + for (let i = 0; i < TOKEN_HOLDER.length; ++i) { + (await this.cmtat.balanceOf(TOKEN_HOLDER[i])).should.be.bignumber.equal(TOKEN_SUPPLY_BY_HOLDERS[i]) + } + + (await this.cmtat.totalSupply()).should.be.bignumber.equal(TOKEN_SUPPLY_BY_HOLDERS.reduce((a, b) => { return a.add(b) })) + + // Assert event + // emits a Transfer event + for (let i = 0; i < TOKEN_HOLDER.length; ++i) { + expectEvent.inLogs(this.logs1, 'Transfer', { + from: ZERO_ADDRESS, + to: TOKEN_HOLDER[i], + value: TOKEN_SUPPLY_BY_HOLDERS[i] + }) + } + + // emits a Mint event + for (let i = 0; i < TOKEN_HOLDER.length; ++i) { + expectEvent.inLogs(this.logs1, 'Mint', { + beneficiary: TOKEN_HOLDER[i], + amount: TOKEN_SUPPLY_BY_HOLDERS[i] + }) + } + }) + + it('testCannotMintBatchByNonMinter', async function () { + const TOKEN_HOLDER = [admin, address1, address2] + const TOKEN_SUPPLY_BY_HOLDERS = [BN(10), BN(100), BN(1000)] await expectRevert( - this.cmtat.mint(address1, 20, { from: address1 }), + this.cmtat.mintBatch(TOKEN_HOLDER, TOKEN_SUPPLY_BY_HOLDERS, { from: address1 }), 'AccessControl: account ' + address1.toLowerCase() + ' is missing role ' + MINTER_ROLE ) }) + + it('testCannotMintBatchIfLengthMismatch', async function () { + const TOKEN_HOLDER_INVALID = [admin, address1] + const TOKEN_SUPPLY_BY_HOLDERS = [BN(10), BN(100), BN(1000)] + await expectRevert( + this.cmtat.mintBatch(TOKEN_HOLDER_INVALID, TOKEN_SUPPLY_BY_HOLDERS, { from: admin }), + 'CMTAT: tos and amounts length mismatch' + ) + }) + + it('testCannotMintBatchIfTOSIsEmpty', async function () { + const TOKEN_HOLDER_INVALID = [] + const TOKEN_SUPPLY_BY_HOLDERS = [] + await expectRevert( + this.cmtat.mintBatch(TOKEN_HOLDER_INVALID, TOKEN_SUPPLY_BY_HOLDERS, { from: admin }), + 'CMTAT: tos is empty' + ) + }) }) } module.exports = MintModuleCommon diff --git a/test/common/SnapshotModuleCommon/SnapshotModuleUtils/SnapshotModuleUtils.js b/test/common/SnapshotModuleCommon/SnapshotModuleUtils/SnapshotModuleUtils.js index 844f1b4f..c2a74fd8 100644 --- a/test/common/SnapshotModuleCommon/SnapshotModuleUtils/SnapshotModuleUtils.js +++ b/test/common/SnapshotModuleCommon/SnapshotModuleUtils/SnapshotModuleUtils.js @@ -1,4 +1,3 @@ - const getUnixTimestamp = () => { return Math.round(new Date().getTime() / 1000) } diff --git a/test/standard/modules/EnforcementModule.test.js b/test/standard/modules/EnforcementModule.test.js index 305fb267..3f4f6a67 100644 --- a/test/standard/modules/EnforcementModule.test.js +++ b/test/standard/modules/EnforcementModule.test.js @@ -1,4 +1,3 @@ - const CMTAT = artifacts.require('CMTAT_STANDALONE') const EnforcementModuleCommon = require('../../common/EnforcementModuleCommon') const { ZERO_ADDRESS } = require('../../utils')