diff --git a/CHANGELOG.md b/CHANGELOG.md index 93a70f4d00c..c9b418e3f8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ * `AccessControl`: Added ERC165 interface detection. ([#2562](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2562)) * `AccessControlEnumerable`: Fixed `renounceRole` not updated underlying set. ([#2572](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2572)) * `ERC1155`: Make `uri` public so overloading function can call it using super. ([#2576](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2576)) + * `ERC777`: Make reception acquirement optional in `_mint`. ([#2552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2552)) ### How to upgrade from 3.x diff --git a/contracts/mocks/ERC777Mock.sol b/contracts/mocks/ERC777Mock.sol index 0f6f35c838c..319a483e212 100644 --- a/contracts/mocks/ERC777Mock.sol +++ b/contracts/mocks/ERC777Mock.sol @@ -27,6 +27,16 @@ contract ERC777Mock is Context, ERC777 { _mint(to, amount, userData, operatorData); } + function mintInternalExtended ( + address to, + uint256 amount, + bytes memory userData, + bytes memory operatorData, + bool requireReceptionAck + ) public { + _mint(to, amount, userData, operatorData, requireReceptionAck); + } + function approveInternal(address holder, address spender, uint256 value) public { _approve(holder, spender, value); } diff --git a/contracts/token/ERC777/ERC777.sol b/contracts/token/ERC777/ERC777.sol index 5ef348ce46c..1b38dd3288e 100644 --- a/contracts/token/ERC777/ERC777.sol +++ b/contracts/token/ERC777/ERC777.sol @@ -312,6 +312,37 @@ contract ERC777 is Context, IERC777, IERC20 { ) internal virtual + { + _mint(account, amount, userData, operatorData, true); + } + + /** + * @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * If `requireReceptionAck` is set to true, and if a send hook is + * registered for `account`, the corresponding function will be called with + * `operator`, `data` and `operatorData`. + * + * See {IERC777Sender} and {IERC777Recipient}. + * + * Emits {Minted} and {IERC20-Transfer} events. + * + * Requirements + * + * - `account` cannot be the zero address. + * - if `account` is a contract, it must implement the {IERC777Recipient} + * interface. + */ + function _mint( + address account, + uint256 amount, + bytes memory userData, + bytes memory operatorData, + bool requireReceptionAck + ) + internal + virtual { require(account != address(0), "ERC777: mint to the zero address"); @@ -323,7 +354,7 @@ contract ERC777 is Context, IERC777, IERC20 { _totalSupply += amount; _balances[account] += amount; - _callTokensReceived(operator, address(0), account, amount, userData, operatorData, true); + _callTokensReceived(operator, address(0), account, amount, userData, operatorData, requireReceptionAck); emit Minted(operator, account, amount, userData, operatorData); emit Transfer(address(0), account, amount); diff --git a/test/token/ERC777/ERC777.test.js b/test/token/ERC777/ERC777.test.js index 9f247d2529e..40b840c7d79 100644 --- a/test/token/ERC777/ERC777.test.js +++ b/test/token/ERC777/ERC777.test.js @@ -171,6 +171,133 @@ contract('ERC777', function (accounts) { shouldBehaveLikeERC777InternalMint(to, operator, amount, data, operatorData); }); }); + + describe('mint (internal extended)', function () { + const amount = new BN('5'); + + context('to anyone', function () { + beforeEach(async function () { + this.recipient = anyone; + }); + + context('with default operator', function () { + const operator = defaultOperatorA; + + it('without requireReceptionAck', async function () { + await this.token.mintInternalExtended( + this.recipient, + amount, + data, + operatorData, + false, + { from: operator }, + ); + }); + + it('with requireReceptionAck', async function () { + await this.token.mintInternalExtended( + this.recipient, + amount, + data, + operatorData, + true, + { from: operator }, + ); + }); + }); + + context('with non operator', function () { + const operator = newOperator; + + it('without requireReceptionAck', async function () { + await this.token.mintInternalExtended( + this.recipient, + amount, + data, + operatorData, + false, + { from: operator }, + ); + }); + + it('with requireReceptionAck', async function () { + await this.token.mintInternalExtended( + this.recipient, + amount, + data, + operatorData, + true, + { from: operator }, + ); + }); + }); + }); + + context('to non ERC777TokensRecipient implementer', function () { + beforeEach(async function () { + this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new(); + this.recipient = this.tokensRecipientImplementer.address; + }); + + context('with default operator', function () { + const operator = defaultOperatorA; + + it('without requireReceptionAck', async function () { + await this.token.mintInternalExtended( + this.recipient, + amount, + data, + operatorData, + false, + { from: operator }, + ); + }); + + it('with requireReceptionAck', async function () { + await expectRevert( + this.token.mintInternalExtended( + this.recipient, + amount, + data, + operatorData, + true, + { from: operator }, + ), + 'ERC777: token recipient contract has no implementer for ERC777TokensRecipient', + ); + }); + }); + + context('with non operator', function () { + const operator = newOperator; + + it('without requireReceptionAck', async function () { + await this.token.mintInternalExtended( + this.recipient, + amount, + data, + operatorData, + false, + { from: operator }, + ); + }); + + it('with requireReceptionAck', async function () { + await expectRevert( + this.token.mintInternalExtended( + this.recipient, + amount, + data, + operatorData, + true, + { from: operator }, + ), + 'ERC777: token recipient contract has no implementer for ERC777TokensRecipient', + ); + }); + }); + }); + }); }); describe('operator management', function () {