Address ERC1155 changes (#2267)

* Make holder fns public

* Add context, remove msg.sender from check

* Fix location of Holder arguments

* Add beforeTransfer hook

* Minor test improvements

* Add ERC1155Burnable and tests

* Add ERC1155Pausable

* Add ERC1155PresetMinterPauser.sol

* Add uri constructors

* Improved revert reasons

* Initial docs improvements

* Add missing docs

* Improve acceptance checks revert reasons

* Apply suggestions from code review

Co-authored-by: Francisco Giordano <frangio.1@gmail.com>

* Remove note about 1155 preset uri in mint

* Add rquirements to balanceOfBatch

* Add note about URI and uri

* Fix list in docs

* Fix lint errors

* Use natural sorting for API titles

* Fix doc references

* Escape {id} references to remove docgen warnings

* Added intro docs, fixed links

* Apply suggestions from code review

Co-authored-by: Francisco Giordano <frangio.1@gmail.com>

* Add changelog entry

Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
This commit is contained in:
Nicolás Venturo
2020-06-09 14:47:51 -03:00
committed by GitHub
parent 13e2132b69
commit d9fa59f30a
26 changed files with 876 additions and 198 deletions

View File

@ -0,0 +1,136 @@
const { accounts, contract, web3 } = require('@openzeppelin/test-environment');
const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
const { ZERO_ADDRESS } = constants;
const { expect } = require('chai');
const ERC1155PresetMinterPauser = contract.fromArtifact('ERC1155PresetMinterPauser');
describe('ERC1155PresetMinterPauser', function () {
const [ deployer, other ] = accounts;
const firstTokenId = new BN('845');
const firstTokenIdAmount = new BN('5000');
const secondTokenId = new BN('48324');
const secondTokenIdAmount = new BN('77875');
const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
const MINTER_ROLE = web3.utils.soliditySha3('MINTER_ROLE');
const PAUSER_ROLE = web3.utils.soliditySha3('PAUSER_ROLE');
const uri = 'https://token.com';
beforeEach(async function () {
this.token = await ERC1155PresetMinterPauser.new(uri, { from: deployer });
});
it('deployer has the default admin role', async function () {
expect(await this.token.getRoleMemberCount(DEFAULT_ADMIN_ROLE)).to.be.bignumber.equal('1');
expect(await this.token.getRoleMember(DEFAULT_ADMIN_ROLE, 0)).to.equal(deployer);
});
it('deployer has the minter role', async function () {
expect(await this.token.getRoleMemberCount(MINTER_ROLE)).to.be.bignumber.equal('1');
expect(await this.token.getRoleMember(MINTER_ROLE, 0)).to.equal(deployer);
});
it('deployer has the pauser role', async function () {
expect(await this.token.getRoleMemberCount(PAUSER_ROLE)).to.be.bignumber.equal('1');
expect(await this.token.getRoleMember(PAUSER_ROLE, 0)).to.equal(deployer);
});
it('minter and pauser role admin is the default admin', async function () {
expect(await this.token.getRoleAdmin(MINTER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
expect(await this.token.getRoleAdmin(PAUSER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE);
});
describe('minting', function () {
it('deployer can mint tokens', async function () {
const receipt = await this.token.mint(other, firstTokenId, firstTokenIdAmount, '0x', { from: deployer });
expectEvent(receipt, 'TransferSingle',
{ operator: deployer, from: ZERO_ADDRESS, to: other, value: firstTokenIdAmount, id: firstTokenId }
);
expect(await this.token.balanceOf(other, firstTokenId)).to.be.bignumber.equal(firstTokenIdAmount);
});
it('other accounts cannot mint tokens', async function () {
await expectRevert(
this.token.mint(other, firstTokenId, firstTokenIdAmount, '0x', { from: other }),
'ERC1155PresetMinterPauser: must have minter role to mint'
);
});
});
describe('batched minting', function () {
it('deployer can batch mint tokens', async function () {
const receipt = await this.token.mintBatch(
other, [firstTokenId, secondTokenId], [firstTokenIdAmount, secondTokenIdAmount], '0x', { from: deployer }
);
expectEvent(receipt, 'TransferBatch',
{ operator: deployer, from: ZERO_ADDRESS, to: other }
);
expect(await this.token.balanceOf(other, firstTokenId)).to.be.bignumber.equal(firstTokenIdAmount);
});
it('other accounts cannot batch mint tokens', async function () {
await expectRevert(
this.token.mintBatch(
other, [firstTokenId, secondTokenId], [firstTokenIdAmount, secondTokenIdAmount], '0x', { from: other }
),
'ERC1155PresetMinterPauser: must have minter role to mint'
);
});
});
describe('pausing', function () {
it('deployer can pause', async function () {
const receipt = await this.token.pause({ from: deployer });
expectEvent(receipt, 'Paused', { account: deployer });
expect(await this.token.paused()).to.equal(true);
});
it('deployer can unpause', async function () {
await this.token.pause({ from: deployer });
const receipt = await this.token.unpause({ from: deployer });
expectEvent(receipt, 'Unpaused', { account: deployer });
expect(await this.token.paused()).to.equal(false);
});
it('cannot mint while paused', async function () {
await this.token.pause({ from: deployer });
await expectRevert(
this.token.mint(other, firstTokenId, firstTokenIdAmount, '0x', { from: deployer }),
'ERC1155Pausable: token transfer while paused'
);
});
it('other accounts cannot pause', async function () {
await expectRevert(
this.token.pause({ from: other }),
'ERC1155PresetMinterPauser: must have pauser role to pause'
);
});
});
describe('burning', function () {
it('holders can burn their tokens', async function () {
await this.token.mint(other, firstTokenId, firstTokenIdAmount, '0x', { from: deployer });
const receipt = await this.token.burn(other, firstTokenId, firstTokenIdAmount.subn(1), { from: other });
expectEvent(receipt, 'TransferSingle',
{ operator: other, from: other, to: ZERO_ADDRESS, value: firstTokenIdAmount.subn(1), id: firstTokenId }
);
expect(await this.token.balanceOf(other, firstTokenId)).to.be.bignumber.equal('1');
});
});
});

View File

@ -90,7 +90,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
[firstTokenHolder, secondTokenHolder, firstTokenHolder, secondTokenHolder],
[firstTokenId, secondTokenId, unknownTokenId]
),
'ERC1155: accounts and IDs must have same lengths'
'ERC1155: accounts and ids length mismatch'
);
});
@ -100,7 +100,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
[firstTokenHolder, secondTokenHolder, ZERO_ADDRESS],
[firstTokenId, secondTokenId, unknownTokenId]
),
'ERC1155: some address in batch balance query is zero'
'ERC1155: batch balance query for the zero address'
);
});
@ -168,7 +168,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
it('reverts if attempting to approve self as an operator', async function () {
await expectRevert(
this.token.setApprovalForAll(multiTokenHolder, true, { from: multiTokenHolder }),
'ERC1155: cannot set approval status for self'
'ERC1155: setting approval status for self'
);
});
});
@ -213,7 +213,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
'0x',
{ from: multiTokenHolder },
),
'ERC1155: target address must be non-zero'
'ERC1155: transfer to the zero address'
);
});
@ -275,7 +275,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
this.token.safeTransferFrom(multiTokenHolder, recipient, firstTokenId, firstAmount, '0x', {
from: proxy,
}),
'ERC1155: need operator approval for 3rd party transfers'
'ERC1155: caller is not owner nor approved'
);
});
});
@ -391,7 +391,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstAmount, '0x', {
from: multiTokenHolder,
}),
'ERC1155: got unknown value from onERC1155Received'
'ERC1155: ERC1155Receiver rejected tokens'
);
});
});
@ -450,7 +450,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
[firstAmount, secondAmount.addn(1)],
'0x', { from: multiTokenHolder }
),
'ERC1155: insufficient balance of some token type for transfer'
'ERC1155: insufficient balance for transfer'
);
});
@ -462,7 +462,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
[firstAmount, secondAmount],
'0x', { from: multiTokenHolder }
),
'ERC1155: IDs and values must have same lengths'
'ERC1155: ids and amounts length mismatch'
);
});
@ -474,7 +474,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
[firstAmount, secondAmount],
'0x', { from: multiTokenHolder }
),
'ERC1155: target address must be non-zero'
'ERC1155: transfer to the zero address'
);
});
@ -538,7 +538,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
[firstAmount, secondAmount],
'0x', { from: proxy }
),
'ERC1155: need operator approval for 3rd party transfers'
'ERC1155: transfer caller is not owner nor approved'
);
});
});
@ -658,7 +658,7 @@ function shouldBehaveLikeERC1155 ([minter, firstTokenHolder, secondTokenHolder,
[firstAmount, secondAmount],
'0x', { from: multiTokenHolder },
),
'ERC1155: got unknown value from onERC1155BatchReceived'
'ERC1155: ERC1155Receiver rejected tokens'
);
});
});

View File

@ -9,12 +9,12 @@ const { shouldBehaveLikeERC1155 } = require('./ERC1155.behavior');
const ERC1155Mock = contract.fromArtifact('ERC1155Mock');
describe('ERC1155', function () {
const [creator, tokenHolder, tokenBatchHolder, ...otherAccounts] = accounts;
const [operator, tokenHolder, tokenBatchHolder, ...otherAccounts] = accounts;
const initialURI = 'https://token-cdn-domain/{id}.json';
beforeEach(async function () {
this.token = await ERC1155Mock.new(initialURI, { from: creator });
this.token = await ERC1155Mock.new(initialURI);
});
shouldBehaveLikeERC1155(otherAccounts);
@ -30,8 +30,8 @@ describe('ERC1155', function () {
const data = '0xcafebabe';
describe('_mint(address, uint256, uint256, bytes memory)', function () {
it('reverts with a null destination address', async function () {
describe('_mint', function () {
it('reverts with a zero destination address', async function () {
await expectRevert(
this.token.mint(ZERO_ADDRESS, tokenId, mintAmount, data),
'ERC1155: mint to the zero address'
@ -40,18 +40,12 @@ describe('ERC1155', function () {
context('with minted tokens', function () {
beforeEach(async function () {
({ logs: this.logs } = await this.token.mint(
tokenHolder,
tokenId,
mintAmount,
data,
{ from: creator }
));
({ logs: this.logs } = await this.token.mint(tokenHolder, tokenId, mintAmount, data, { from: operator }));
});
it('emits a TransferSingle event', function () {
expectEvent.inLogs(this.logs, 'TransferSingle', {
operator: creator,
operator,
from: ZERO_ADDRESS,
to: tokenHolder,
id: tokenId,
@ -60,26 +54,23 @@ describe('ERC1155', function () {
});
it('credits the minted amount of tokens', async function () {
expect(await this.token.balanceOf(
tokenHolder,
tokenId
)).to.be.bignumber.equal(mintAmount);
expect(await this.token.balanceOf(tokenHolder, tokenId)).to.be.bignumber.equal(mintAmount);
});
});
});
describe('_mintBatch(address, uint256[] memory, uint256[] memory, bytes memory)', function () {
it('reverts with a null destination address', async function () {
describe('_mintBatch', function () {
it('reverts with a zero destination address', async function () {
await expectRevert(
this.token.mintBatch(ZERO_ADDRESS, tokenBatchIds, mintAmounts, data),
'ERC1155: batch mint to the zero address'
'ERC1155: mint to the zero address'
);
});
it('reverts if length of inputs do not match', async function () {
await expectRevert(
this.token.mintBatch(tokenBatchHolder, tokenBatchIds, mintAmounts.slice(1), data),
'ERC1155: minted IDs and values must have same lengths'
'ERC1155: ids and amounts length mismatch'
);
});
@ -90,17 +81,15 @@ describe('ERC1155', function () {
tokenBatchIds,
mintAmounts,
data,
{ from: creator }
{ from: operator }
));
});
it('emits a TransferBatch event', function () {
expectEvent.inLogs(this.logs, 'TransferBatch', {
operator: creator,
operator,
from: ZERO_ADDRESS,
to: tokenBatchHolder,
// ids: tokenBatchIds,
// values: mintAmounts,
});
});
@ -117,18 +106,18 @@ describe('ERC1155', function () {
});
});
describe('_burn(address, uint256, uint256)', function () {
describe('_burn', function () {
it('reverts when burning the zero account\'s tokens', async function () {
await expectRevert(
this.token.burn(ZERO_ADDRESS, tokenId, mintAmount),
'ERC1155: attempting to burn tokens on zero account'
'ERC1155: burn from the zero address'
);
});
it('reverts when burning a non-existent token id', async function () {
await expectRevert(
this.token.burn(tokenHolder, tokenId, mintAmount),
'ERC1155: attempting to burn more than balance'
'ERC1155: burn amount exceeds balance'
);
});
@ -139,13 +128,13 @@ describe('ERC1155', function () {
tokenHolder,
tokenId,
burnAmount,
{ from: creator }
{ from: operator }
));
});
it('emits a TransferSingle event', function () {
expectEvent.inLogs(this.logs, 'TransferSingle', {
operator: creator,
operator,
from: tokenHolder,
to: ZERO_ADDRESS,
id: tokenId,
@ -162,25 +151,25 @@ describe('ERC1155', function () {
});
});
describe('_burnBatch(address, uint256[] memory, uint256[] memory)', function () {
describe('_burnBatch', function () {
it('reverts when burning the zero account\'s tokens', async function () {
await expectRevert(
this.token.burnBatch(ZERO_ADDRESS, tokenBatchIds, burnAmounts),
'ERC1155: attempting to burn batch of tokens on zero account'
'ERC1155: burn from the zero address'
);
});
it('reverts if length of inputs do not match', async function () {
await expectRevert(
this.token.burnBatch(tokenBatchHolder, tokenBatchIds, burnAmounts.slice(1)),
'ERC1155: burnt IDs and values must have same lengths'
'ERC1155: ids and amounts length mismatch'
);
});
it('reverts when burning a non-existent token id', async function () {
await expectRevert(
this.token.burnBatch(tokenBatchHolder, tokenBatchIds, burnAmounts),
'ERC1155: attempting to burn more than balance for some token'
'ERC1155: burn amount exceeds balance'
);
});
@ -191,13 +180,13 @@ describe('ERC1155', function () {
tokenBatchHolder,
tokenBatchIds,
burnAmounts,
{ from: creator }
{ from: operator }
));
});
it('emits a TransferBatch event', function () {
expectEvent.inLogs(this.logs, 'TransferBatch', {
operator: creator,
operator,
from: tokenBatchHolder,
to: ZERO_ADDRESS,
// ids: tokenBatchIds,

View File

@ -0,0 +1,69 @@
const { accounts, contract } = require('@openzeppelin/test-environment');
const { BN, expectRevert } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const ERC1155BurnableMock = contract.fromArtifact('ERC1155BurnableMock');
describe('ERC1155Burnable', function () {
const [ holder, operator, other ] = accounts;
const uri = 'https://token.com';
const tokenIds = [new BN('42'), new BN('1137')];
const amounts = [new BN('3000'), new BN('9902')];
beforeEach(async function () {
this.token = await ERC1155BurnableMock.new(uri);
await this.token.mint(holder, tokenIds[0], amounts[0], '0x');
await this.token.mint(holder, tokenIds[1], amounts[1], '0x');
});
describe('burn', function () {
it('holder can burn their tokens', async function () {
await this.token.burn(holder, tokenIds[0], amounts[0].subn(1), { from: holder });
expect(await this.token.balanceOf(holder, tokenIds[0])).to.be.bignumber.equal('1');
});
it('approved operators can burn the holder\'s tokens', async function () {
await this.token.setApprovalForAll(operator, true, { from: holder });
await this.token.burn(holder, tokenIds[0], amounts[0].subn(1), { from: operator });
expect(await this.token.balanceOf(holder, tokenIds[0])).to.be.bignumber.equal('1');
});
it('unapproved accounts cannot burn the holder\'s tokens', async function () {
await expectRevert(
this.token.burn(holder, tokenIds[0], amounts[0].subn(1), { from: other }),
'ERC1155: caller is not owner nor approved'
);
});
});
describe('burnBatch', function () {
it('holder can burn their tokens', async function () {
await this.token.burnBatch(holder, tokenIds, [ amounts[0].subn(1), amounts[1].subn(2) ], { from: holder });
expect(await this.token.balanceOf(holder, tokenIds[0])).to.be.bignumber.equal('1');
expect(await this.token.balanceOf(holder, tokenIds[1])).to.be.bignumber.equal('2');
});
it('approved operators can burn the holder\'s tokens', async function () {
await this.token.setApprovalForAll(operator, true, { from: holder });
await this.token.burnBatch(holder, tokenIds, [ amounts[0].subn(1), amounts[1].subn(2) ], { from: operator });
expect(await this.token.balanceOf(holder, tokenIds[0])).to.be.bignumber.equal('1');
expect(await this.token.balanceOf(holder, tokenIds[1])).to.be.bignumber.equal('2');
});
it('unapproved accounts cannot burn the holder\'s tokens', async function () {
await expectRevert(
this.token.burnBatch(holder, tokenIds, [ amounts[0].subn(1), amounts[1].subn(2) ], { from: other }),
'ERC1155: caller is not owner nor approved'
);
});
});
});

View File

@ -0,0 +1,110 @@
const { accounts, contract } = require('@openzeppelin/test-environment');
const { BN, expectRevert } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const ERC1155PausableMock = contract.fromArtifact('ERC1155PausableMock');
describe('ERC1155Pausable', function () {
const [ holder, operator, receiver, other ] = accounts;
const uri = 'https://token.com';
beforeEach(async function () {
this.token = await ERC1155PausableMock.new(uri);
});
context('when token is paused', function () {
const firstTokenId = new BN('37');
const firstTokenAmount = new BN('42');
const secondTokenId = new BN('19842');
const secondTokenAmount = new BN('23');
beforeEach(async function () {
await this.token.setApprovalForAll(operator, true, { from: holder });
await this.token.mint(holder, firstTokenId, firstTokenAmount, '0x');
await this.token.pause();
});
it('reverts when trying to safeTransferFrom from holder', async function () {
await expectRevert(
this.token.safeTransferFrom(holder, receiver, firstTokenId, firstTokenAmount, '0x', { from: holder }),
'ERC1155Pausable: token transfer while paused'
);
});
it('reverts when trying to safeTransferFrom from operator', async function () {
await expectRevert(
this.token.safeTransferFrom(holder, receiver, firstTokenId, firstTokenAmount, '0x', { from: operator }),
'ERC1155Pausable: token transfer while paused'
);
});
it('reverts when trying to safeBatchTransferFrom from holder', async function () {
await expectRevert(
this.token.safeBatchTransferFrom(holder, receiver, [firstTokenId], [firstTokenAmount], '0x', { from: holder }),
'ERC1155Pausable: token transfer while paused'
);
});
it('reverts when trying to safeBatchTransferFrom from operator', async function () {
await expectRevert(
this.token.safeBatchTransferFrom(
holder, receiver, [firstTokenId], [firstTokenAmount], '0x', { from: operator }
),
'ERC1155Pausable: token transfer while paused'
);
});
it('reverts when trying to mint', async function () {
await expectRevert(
this.token.mint(holder, secondTokenId, secondTokenAmount, '0x'),
'ERC1155Pausable: token transfer while paused'
);
});
it('reverts when trying to mintBatch', async function () {
await expectRevert(
this.token.mintBatch(holder, [secondTokenId], [secondTokenAmount], '0x'),
'ERC1155Pausable: token transfer while paused'
);
});
it('reverts when trying to burn', async function () {
await expectRevert(
this.token.burn(holder, firstTokenId, firstTokenAmount),
'ERC1155Pausable: token transfer while paused'
);
});
it('reverts when trying to burnBatch', async function () {
await expectRevert(
this.token.burn(holder, [firstTokenId], [firstTokenAmount]),
'ERC1155Pausable: token transfer while paused'
);
});
describe('setApprovalForAll', function () {
it('approves an operator', async function () {
await this.token.setApprovalForAll(other, true, { from: holder });
expect(await this.token.isApprovedForAll(holder, other)).to.equal(true);
});
});
describe('balanceOf', function () {
it('returns the amount of tokens owned by the given address', async function () {
const balance = await this.token.balanceOf(holder, firstTokenId);
expect(balance).to.be.bignumber.equal(firstTokenAmount);
});
});
describe('isApprovedForAll', function () {
it('returns the approval of the operator', async function () {
expect(await this.token.isApprovedForAll(holder, operator)).to.equal(true);
});
});
});
});