diff --git a/contracts/mocks/ERC721BasicMock.sol b/contracts/mocks/ERC721BasicMock.sol index 87add3adb7e..5b8417fd5fb 100644 --- a/contracts/mocks/ERC721BasicMock.sol +++ b/contracts/mocks/ERC721BasicMock.sol @@ -9,10 +9,10 @@ import "../token/ERC721/ERC721Basic.sol"; */ contract ERC721BasicMock is ERC721Basic { function mint(address _to, uint256 _tokenId) public { - super._mint(_to, _tokenId); + _mint(_to, _tokenId); } function burn(uint256 _tokenId) public { - super._burn(ownerOf(_tokenId), _tokenId); + _burn(ownerOf(_tokenId), _tokenId); } } diff --git a/contracts/mocks/ERC721MintableBurnableImpl.sol b/contracts/mocks/ERC721MintableBurnableImpl.sol new file mode 100644 index 00000000000..d0eae1fb353 --- /dev/null +++ b/contracts/mocks/ERC721MintableBurnableImpl.sol @@ -0,0 +1,18 @@ +pragma solidity ^0.4.24; + +import "../token/ERC721/ERC721.sol"; +import "../token/ERC721/ERC721Mintable.sol"; +import "../token/ERC721/ERC721Burnable.sol"; + + +/** + * @title ERC721MintableBurnableImpl + */ +contract ERC721MintableBurnableImpl is ERC721, ERC721Mintable, ERC721Burnable { + constructor() + ERC721Mintable() + ERC721("Test", "TEST") + public + { + } +} diff --git a/contracts/mocks/ERC721Mock.sol b/contracts/mocks/ERC721Mock.sol index b6c138fd2cd..88ac56a5682 100644 --- a/contracts/mocks/ERC721Mock.sol +++ b/contracts/mocks/ERC721Mock.sol @@ -1,6 +1,8 @@ pragma solidity ^0.4.24; import "../token/ERC721/ERC721.sol"; +import "../token/ERC721/ERC721Mintable.sol"; +import "../token/ERC721/ERC721Burnable.sol"; /** @@ -8,18 +10,11 @@ import "../token/ERC721/ERC721.sol"; * This mock just provides a public mint and burn functions for testing purposes, * and a public setter for metadata URI */ -contract ERC721Mock is ERC721 { - constructor(string name, string symbol) public - ERC721(name, symbol) - { } - - function mint(address _to, uint256 _tokenId) public { - _mint(_to, _tokenId); - } - - function burn(uint256 _tokenId) public { - _burn(ownerOf(_tokenId), _tokenId); - } +contract ERC721Mock is ERC721, ERC721Mintable, ERC721Burnable { + constructor(string _name, string _symbol) public + ERC721Mintable() + ERC721(_name, _symbol) + {} function exists(uint256 _tokenId) public view returns (bool) { return _exists(_tokenId); diff --git a/contracts/token/ERC20/ERC20Mintable.sol b/contracts/token/ERC20/ERC20Mintable.sol index 6de17feb8c6..430a3cdf795 100644 --- a/contracts/token/ERC20/ERC20Mintable.sol +++ b/contracts/token/ERC20/ERC20Mintable.sol @@ -5,13 +5,12 @@ import "../../access/roles/MinterRole.sol"; /** - * @title Mintable token - * @dev Simple ERC20 Token example, with mintable token creation - * Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol + * @title ERC20Mintable + * @dev ERC20 minting logic */ contract ERC20Mintable is ERC20, MinterRole { - event Mint(address indexed to, uint256 amount); - event MintFinished(); + event Minted(address indexed to, uint256 amount); + event MintingFinished(); bool private mintingFinished_ = false; @@ -43,7 +42,7 @@ contract ERC20Mintable is ERC20, MinterRole { returns (bool) { _mint(_to, _amount); - emit Mint(_to, _amount); + emit Minted(_to, _amount); return true; } @@ -58,7 +57,7 @@ contract ERC20Mintable is ERC20, MinterRole { returns (bool) { mintingFinished_ = true; - emit MintFinished(); + emit MintingFinished(); return true; } } diff --git a/contracts/token/ERC721/ERC721Burnable.sol b/contracts/token/ERC721/ERC721Burnable.sol new file mode 100644 index 00000000000..ea42dab0528 --- /dev/null +++ b/contracts/token/ERC721/ERC721Burnable.sol @@ -0,0 +1,13 @@ +pragma solidity ^0.4.24; + +import "./ERC721.sol"; + + +contract ERC721Burnable is ERC721 { + function burn(uint256 _tokenId) + public + { + require(_isApprovedOrOwner(msg.sender, _tokenId)); + _burn(ownerOf(_tokenId), _tokenId); + } +} diff --git a/contracts/token/ERC721/ERC721Mintable.sol b/contracts/token/ERC721/ERC721Mintable.sol new file mode 100644 index 00000000000..0329a92e3f3 --- /dev/null +++ b/contracts/token/ERC721/ERC721Mintable.sol @@ -0,0 +1,71 @@ +pragma solidity ^0.4.24; + +import "./ERC721.sol"; +import "../../access/roles/MinterRole.sol"; + + +/** + * @title ERC721Mintable + * @dev ERC721 minting logic + */ +contract ERC721Mintable is ERC721, MinterRole { + event Minted(address indexed to, uint256 tokenId); + event MintingFinished(); + + bool public mintingFinished = false; + + modifier onlyBeforeMintingFinished() { + require(!mintingFinished); + _; + } + + /** + * @dev Function to mint tokens + * @param _to The address that will receive the minted tokens. + * @param _tokenId The token id to mint. + * @return A boolean that indicates if the operation was successful. + */ + function mint( + address _to, + uint256 _tokenId + ) + public + onlyMinter + onlyBeforeMintingFinished + returns (bool) + { + _mint(_to, _tokenId); + emit Minted(_to, _tokenId); + return true; + } + + function mintWithTokenURI( + address _to, + uint256 _tokenId, + string _tokenURI + ) + public + onlyMinter + onlyBeforeMintingFinished + returns (bool) + { + mint(_to, _tokenId); + _setTokenURI(_tokenId, _tokenURI); + return true; + } + + /** + * @dev Function to stop minting new tokens. + * @return True if the operation was successful. + */ + function finishMinting() + public + onlyMinter + onlyBeforeMintingFinished + returns (bool) + { + mintingFinished = true; + emit MintingFinished(); + return true; + } +} diff --git a/test/token/ERC20/ERC20Capped.behavior.js b/test/token/ERC20/ERC20Capped.behavior.js index 004d512c042..3bec1930256 100644 --- a/test/token/ERC20/ERC20Capped.behavior.js +++ b/test/token/ERC20/ERC20Capped.behavior.js @@ -17,7 +17,7 @@ function shouldBehaveLikeERC20Capped (minter, [anyone], cap) { it('should mint when amount is less than cap', async function () { const { logs } = await this.token.mint(anyone, cap.sub(1), { from }); - expectEvent.inLogs(logs, 'Mint'); + expectEvent.inLogs(logs, 'Minted'); }); it('should fail to mint if the ammount exceeds the cap', async function () { diff --git a/test/token/ERC20/ERC20Mintable.behavior.js b/test/token/ERC20/ERC20Mintable.behavior.js index 0fb68e41a23..debca80975a 100644 --- a/test/token/ERC20/ERC20Mintable.behavior.js +++ b/test/token/ERC20/ERC20Mintable.behavior.js @@ -43,8 +43,7 @@ function shouldBehaveLikeERC20Mintable (minter, [anyone]) { it('emits a mint finished event', async function () { const { logs } = await this.token.finishMinting({ from }); - logs.length.should.be.equal(1); - logs[0].event.should.equal('MintFinished'); + await expectEvent.inLogs(logs, 'MintingFinished'); }); }); @@ -105,7 +104,7 @@ function shouldBehaveLikeERC20Mintable (minter, [anyone]) { }); it('emits a mint and a transfer event', async function () { - const mintEvent = expectEvent.inLogs(this.logs, 'Mint', { + const mintEvent = expectEvent.inLogs(this.logs, 'Minted', { to: anyone, }); mintEvent.args.amount.should.be.bignumber.equal(amount); diff --git a/test/token/ERC721/ERC721.test.js b/test/token/ERC721/ERC721.test.js index 5469c87cf28..081d8e86f9e 100644 --- a/test/token/ERC721/ERC721.test.js +++ b/test/token/ERC721/ERC721.test.js @@ -11,55 +11,57 @@ require('chai') .use(require('chai-bignumber')(BigNumber)) .should(); -contract('ERC721', function (accounts) { +contract('ERC721', function ([ + creator, + ...accounts +]) { const name = 'Non Fungible Token'; const symbol = 'NFT'; const firstTokenId = 100; const secondTokenId = 200; + const thirdTokenId = 300; const nonExistentTokenId = 999; - const creator = accounts[0]; - const anyone = accounts[9]; + + const minter = creator; + + const [ + owner, + newOwner, + another, + anyone, + ] = accounts; beforeEach(async function () { this.token = await ERC721.new(name, symbol, { from: creator }); }); - shouldBehaveLikeERC721Basic(accounts); - shouldBehaveLikeMintAndBurnERC721(accounts); - describe('like a full ERC721', function () { beforeEach(async function () { - await this.token.mint(creator, firstTokenId, { from: creator }); - await this.token.mint(creator, secondTokenId, { from: creator }); + await this.token.mint(owner, firstTokenId, { from: minter }); + await this.token.mint(owner, secondTokenId, { from: minter }); }); describe('mint', function () { - const to = accounts[1]; - const tokenId = 3; - beforeEach(async function () { - await this.token.mint(to, tokenId); + await this.token.mint(newOwner, thirdTokenId, { from: minter }); }); it('adjusts owner tokens by index', async function () { - (await this.token.tokenOfOwnerByIndex(to, 0)).toNumber().should.be.equal(tokenId); + (await this.token.tokenOfOwnerByIndex(newOwner, 0)).toNumber().should.be.equal(thirdTokenId); }); it('adjusts all tokens list', async function () { - (await this.token.tokenByIndex(2)).toNumber().should.be.equal(tokenId); + (await this.token.tokenByIndex(2)).toNumber().should.be.equal(thirdTokenId); }); }); describe('burn', function () { - const tokenId = firstTokenId; - const sender = creator; - beforeEach(async function () { - await this.token.burn(tokenId, { from: sender }); + await this.token.burn(firstTokenId, { from: owner }); }); it('removes that token from the token list of the owner', async function () { - (await this.token.tokenOfOwnerByIndex(sender, 0)).toNumber().should.be.equal(secondTokenId); + (await this.token.tokenOfOwnerByIndex(owner, 0)).toNumber().should.be.equal(secondTokenId); }); it('adjusts all tokens list', async function () { @@ -67,7 +69,7 @@ contract('ERC721', function (accounts) { }); it('burns all tokens', async function () { - await this.token.burn(secondTokenId, { from: sender }); + await this.token.burn(secondTokenId, { from: owner }); (await this.token.totalSupply()).toNumber().should.be.equal(0); await assertRevert(this.token.tokenByIndex(0)); }); @@ -76,25 +78,25 @@ contract('ERC721', function (accounts) { describe('removeTokenFrom', function () { it('reverts if the correct owner is not passed', async function () { await assertRevert( - this.token.removeTokenFrom(anyone, firstTokenId, { from: creator }) + this.token.removeTokenFrom(anyone, firstTokenId, { from: owner }) ); }); context('once removed', function () { beforeEach(async function () { - await this.token.removeTokenFrom(creator, firstTokenId, { from: creator }); + await this.token.removeTokenFrom(owner, firstTokenId, { from: owner }); }); it('has been removed', async function () { - await assertRevert(this.token.tokenOfOwnerByIndex(creator, 1)); + await assertRevert(this.token.tokenOfOwnerByIndex(owner, 1)); }); it('adjusts token list', async function () { - (await this.token.tokenOfOwnerByIndex(creator, 0)).toNumber().should.be.equal(secondTokenId); + (await this.token.tokenOfOwnerByIndex(owner, 0)).toNumber().should.be.equal(secondTokenId); }); it('adjusts owner count', async function () { - (await this.token.balanceOf(creator)).toNumber().should.be.equal(1); + (await this.token.balanceOf(owner)).toNumber().should.be.equal(1); }); it('does not adjust supply', async function () { @@ -125,7 +127,7 @@ contract('ERC721', function (accounts) { it('can burn token with metadata', async function () { await this.token.setTokenURI(firstTokenId, sampleUri); - await this.token.burn(firstTokenId); + await this.token.burn(firstTokenId, { from: owner }); (await this.token.exists(firstTokenId)).should.equal(false); }); @@ -145,9 +147,6 @@ contract('ERC721', function (accounts) { }); describe('tokenOfOwnerByIndex', function () { - const owner = creator; - const another = accounts[1]; - describe('when the given index is lower than the amount of tokens owned by the given address', function () { it('returns the token ID placed at the given index', async function () { (await this.token.tokenOfOwnerByIndex(owner, 0)).should.be.bignumber.equal(firstTokenId); @@ -197,13 +196,12 @@ contract('ERC721', function (accounts) { [firstTokenId, secondTokenId].forEach(function (tokenId) { it(`should return all tokens after burning token ${tokenId} and minting new tokens`, async function () { - const owner = accounts[0]; const newTokenId = 300; const anotherNewTokenId = 400; await this.token.burn(tokenId, { from: owner }); - await this.token.mint(owner, newTokenId, { from: owner }); - await this.token.mint(owner, anotherNewTokenId, { from: owner }); + await this.token.mint(newOwner, newTokenId, { from: minter }); + await this.token.mint(newOwner, anotherNewTokenId, { from: minter }); (await this.token.totalSupply()).toNumber().should.be.equal(3); @@ -218,6 +216,9 @@ contract('ERC721', function (accounts) { }); }); + shouldBehaveLikeERC721Basic(creator, minter, accounts); + shouldBehaveLikeMintAndBurnERC721(creator, minter, accounts); + shouldSupportInterfaces([ 'ERC165', 'ERC721', diff --git a/test/token/ERC721/ERC721Basic.behavior.js b/test/token/ERC721/ERC721Basic.behavior.js index 765614afce7..b3be95ed020 100644 --- a/test/token/ERC721/ERC721Basic.behavior.js +++ b/test/token/ERC721/ERC721Basic.behavior.js @@ -11,30 +11,34 @@ require('chai') .use(require('chai-bignumber')(BigNumber)) .should(); -function shouldBehaveLikeERC721Basic (accounts) { +function shouldBehaveLikeERC721Basic ( + creator, + minter, + [owner, approved, anotherApproved, operator, anyone] +) { const firstTokenId = 1; const secondTokenId = 2; const unknownTokenId = 3; - const creator = accounts[0]; const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; const RECEIVER_MAGIC_VALUE = '0x150b7a02'; describe('like an ERC721Basic', function () { beforeEach(async function () { - await this.token.mint(creator, firstTokenId, { from: creator }); - await this.token.mint(creator, secondTokenId, { from: creator }); + await this.token.mint(owner, firstTokenId, { from: minter }); + await this.token.mint(owner, secondTokenId, { from: minter }); + this.toWhom = anyone; // default to anyone for toWhom in context-dependent tests }); describe('balanceOf', function () { context('when the given address owns some tokens', function () { it('returns the amount of tokens owned by the given address', async function () { - (await this.token.balanceOf(creator)).should.be.bignumber.equal(2); + (await this.token.balanceOf(owner)).should.be.bignumber.equal(2); }); }); context('when the given address does not own any tokens', function () { it('returns 0', async function () { - (await this.token.balanceOf(accounts[1])).should.be.bignumber.equal(0); + (await this.token.balanceOf(anyone)).should.be.bignumber.equal(0); }); }); @@ -50,7 +54,7 @@ function shouldBehaveLikeERC721Basic (accounts) { const tokenId = firstTokenId; it('returns the owner of the given token ID', async function () { - (await this.token.ownerOf(tokenId)).should.be.equal(creator); + (await this.token.ownerOf(tokenId)).should.be.equal(owner); }); }); @@ -64,24 +68,19 @@ function shouldBehaveLikeERC721Basic (accounts) { }); describe('transfers', function () { - const owner = accounts[0]; - const approved = accounts[2]; - const operator = accounts[3]; - const unauthorized = accounts[4]; const tokenId = firstTokenId; const data = '0x42'; let logs = null; beforeEach(async function () { - this.to = accounts[1]; await this.token.approve(approved, tokenId, { from: owner }); await this.token.setApprovalForAll(operator, true, { from: owner }); }); const transferWasSuccessful = function ({ owner, tokenId, approved }) { it('transfers the ownership of the given token ID to the given address', async function () { - (await this.token.ownerOf(tokenId)).should.be.equal(this.to); + (await this.token.ownerOf(tokenId)).should.be.equal(this.toWhom); }); it('clears the approval for the token ID', async function () { @@ -93,7 +92,7 @@ function shouldBehaveLikeERC721Basic (accounts) { logs.length.should.be.equal(1); logs[0].event.should.be.equal('Transfer'); logs[0].args.from.should.be.equal(owner); - logs[0].args.to.should.be.equal(this.to); + logs[0].args.to.should.be.equal(this.toWhom); logs[0].args.tokenId.should.be.bignumber.equal(tokenId); }); } else { @@ -101,21 +100,19 @@ function shouldBehaveLikeERC721Basic (accounts) { logs.length.should.be.equal(1); logs[0].event.should.be.equal('Transfer'); logs[0].args.from.should.be.equal(owner); - logs[0].args.to.should.be.equal(this.to); + logs[0].args.to.should.be.equal(this.toWhom); logs[0].args.tokenId.should.be.bignumber.equal(tokenId); }); } it('adjusts owners balances', async function () { - (await this.token.balanceOf(this.to)).should.be.bignumber.equal(1); - (await this.token.balanceOf(owner)).should.be.bignumber.equal(1); }); it('adjusts owners tokens by index', async function () { if (!this.token.tokenOfOwnerByIndex) return; - (await this.token.tokenOfOwnerByIndex(this.to, 0)).toNumber().should.be.equal(tokenId); + (await this.token.tokenOfOwnerByIndex(this.toWhom, 0)).toNumber().should.be.equal(tokenId); (await this.token.tokenOfOwnerByIndex(owner, 0)).toNumber().should.not.be.equal(tokenId); }); @@ -124,21 +121,21 @@ function shouldBehaveLikeERC721Basic (accounts) { const shouldTransferTokensByUsers = function (transferFunction) { context('when called by the owner', function () { beforeEach(async function () { - ({ logs } = await transferFunction.call(this, owner, this.to, tokenId, { from: owner })); + ({ logs } = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: owner })); }); transferWasSuccessful({ owner, tokenId, approved }); }); context('when called by the approved individual', function () { beforeEach(async function () { - ({ logs } = await transferFunction.call(this, owner, this.to, tokenId, { from: approved })); + ({ logs } = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: approved })); }); transferWasSuccessful({ owner, tokenId, approved }); }); context('when called by the operator', function () { beforeEach(async function () { - ({ logs } = await transferFunction.call(this, owner, this.to, tokenId, { from: operator })); + ({ logs } = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: operator })); }); transferWasSuccessful({ owner, tokenId, approved }); }); @@ -146,7 +143,7 @@ function shouldBehaveLikeERC721Basic (accounts) { context('when called by the owner without an approved user', function () { beforeEach(async function () { await this.token.approve(ZERO_ADDRESS, tokenId, { from: owner }); - ({ logs } = await transferFunction.call(this, owner, this.to, tokenId, { from: operator })); + ({ logs } = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: operator })); }); transferWasSuccessful({ owner, tokenId, approved: null }); }); @@ -185,19 +182,22 @@ function shouldBehaveLikeERC721Basic (accounts) { context('when the address of the previous owner is incorrect', function () { it('reverts', async function () { - await assertRevert(transferFunction.call(this, unauthorized, this.to, tokenId, { from: owner })); + await assertRevert(transferFunction.call(this, anyone, anyone, tokenId, { from: owner }) + ); }); }); context('when the sender is not authorized for the token id', function () { it('reverts', async function () { - await assertRevert(transferFunction.call(this, owner, this.to, tokenId, { from: unauthorized })); + await assertRevert(transferFunction.call(this, owner, anyone, tokenId, { from: anyone }) + ); }); }); context('when the given token ID does not exist', function () { it('reverts', async function () { - await assertRevert(transferFunction.call(this, owner, this.to, unknownTokenId, { from: owner })); + await assertRevert(transferFunction.call(this, owner, anyone, unknownTokenId, { from: owner }) + ); }); }); @@ -237,13 +237,13 @@ function shouldBehaveLikeERC721Basic (accounts) { describe('to a valid receiver contract', function () { beforeEach(async function () { this.receiver = await ERC721Receiver.new(RECEIVER_MAGIC_VALUE, false); - this.to = this.receiver.address; + this.toWhom = this.receiver.address; }); shouldTransferTokensByUsers(transferFun); it('should call onERC721Received', async function () { - const result = await transferFun.call(this, owner, this.to, tokenId, { from: owner }); + const result = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: owner }); result.receipt.logs.length.should.be.equal(2); const [log] = decodeLogs([result.receipt.logs[1]], ERC721Receiver, this.receiver.address); log.event.should.be.equal('Received'); @@ -254,9 +254,15 @@ function shouldBehaveLikeERC721Basic (accounts) { }); it('should call onERC721Received from approved', async function () { - const result = await transferFun.call(this, owner, this.to, tokenId, { from: approved }); + const result = await transferFun.call(this, owner, this.receiver.address, tokenId, { + from: approved, + }); result.receipt.logs.length.should.be.equal(2); - const [log] = decodeLogs([result.receipt.logs[1]], ERC721Receiver, this.receiver.address); + const [log] = decodeLogs( + [result.receipt.logs[1]], + ERC721Receiver, + this.receiver.address + ); log.event.should.be.equal('Received'); log.args.operator.should.be.equal(approved); log.args.from.should.be.equal(owner); @@ -270,7 +276,7 @@ function shouldBehaveLikeERC721Basic (accounts) { transferFun.call( this, owner, - this.to, + this.receiver.address, unknownTokenId, { from: owner }, ) @@ -313,8 +319,6 @@ function shouldBehaveLikeERC721Basic (accounts) { describe('approve', function () { const tokenId = firstTokenId; - const sender = creator; - const to = accounts[1]; let logs = null; @@ -334,7 +338,7 @@ function shouldBehaveLikeERC721Basic (accounts) { it('emits an approval event', async function () { logs.length.should.be.equal(1); logs[0].event.should.be.equal('Approval'); - logs[0].args.owner.should.be.equal(sender); + logs[0].args.owner.should.be.equal(owner); logs[0].args.approved.should.be.equal(address); logs[0].args.tokenId.should.be.bignumber.equal(tokenId); }); @@ -343,7 +347,7 @@ function shouldBehaveLikeERC721Basic (accounts) { context('when clearing approval', function () { context('when there was no prior approval', function () { beforeEach(async function () { - ({ logs } = await this.token.approve(ZERO_ADDRESS, tokenId, { from: sender })); + ({ logs } = await this.token.approve(ZERO_ADDRESS, tokenId, { from: owner })); }); itClearsApproval(); @@ -352,8 +356,8 @@ function shouldBehaveLikeERC721Basic (accounts) { context('when there was a prior approval', function () { beforeEach(async function () { - await this.token.approve(to, tokenId, { from: sender }); - ({ logs } = await this.token.approve(ZERO_ADDRESS, tokenId, { from: sender })); + await this.token.approve(approved, tokenId, { from: owner }); + ({ logs } = await this.token.approve(ZERO_ADDRESS, tokenId, { from: owner })); }); itClearsApproval(); @@ -364,90 +368,87 @@ function shouldBehaveLikeERC721Basic (accounts) { context('when approving a non-zero address', function () { context('when there was no prior approval', function () { beforeEach(async function () { - ({ logs } = await this.token.approve(to, tokenId, { from: sender })); + ({ logs } = await this.token.approve(approved, tokenId, { from: owner })); }); - itApproves(to); - itEmitsApprovalEvent(to); + itApproves(approved); + itEmitsApprovalEvent(approved); }); context('when there was a prior approval to the same address', function () { beforeEach(async function () { - await this.token.approve(to, tokenId, { from: sender }); - ({ logs } = await this.token.approve(to, tokenId, { from: sender })); + await this.token.approve(approved, tokenId, { from: owner }); + ({ logs } = await this.token.approve(approved, tokenId, { from: owner })); }); - itApproves(to); - itEmitsApprovalEvent(to); + itApproves(approved); + itEmitsApprovalEvent(approved); }); context('when there was a prior approval to a different address', function () { beforeEach(async function () { - await this.token.approve(accounts[2], tokenId, { from: sender }); - ({ logs } = await this.token.approve(to, tokenId, { from: sender })); + await this.token.approve(anotherApproved, tokenId, { from: owner }); + ({ logs } = await this.token.approve(anotherApproved, tokenId, { from: owner })); }); - itApproves(to); - itEmitsApprovalEvent(to); + itApproves(anotherApproved); + itEmitsApprovalEvent(anotherApproved); }); }); context('when the address that receives the approval is the owner', function () { it('reverts', async function () { - await assertRevert(this.token.approve(sender, tokenId, { from: sender })); + await assertRevert( + this.token.approve(owner, tokenId, { from: owner }) + ); }); }); context('when the sender does not own the given token ID', function () { it('reverts', async function () { - await assertRevert(this.token.approve(to, tokenId, { from: accounts[2] })); + await assertRevert(this.token.approve(approved, tokenId, { from: anyone })); }); }); context('when the sender is approved for the given token ID', function () { it('reverts', async function () { - await this.token.approve(accounts[2], tokenId, { from: sender }); - await assertRevert(this.token.approve(to, tokenId, { from: accounts[2] })); + await this.token.approve(approved, tokenId, { from: owner }); + await assertRevert(this.token.approve(anotherApproved, tokenId, { from: approved })); }); }); context('when the sender is an operator', function () { - const operator = accounts[2]; beforeEach(async function () { - await this.token.setApprovalForAll(operator, true, { from: sender }); - ({ logs } = await this.token.approve(to, tokenId, { from: operator })); + await this.token.setApprovalForAll(operator, true, { from: owner }); + ({ logs } = await this.token.approve(approved, tokenId, { from: operator })); }); - itApproves(to); - itEmitsApprovalEvent(to); + itApproves(approved); + itEmitsApprovalEvent(approved); }); context('when the given token ID does not exist', function () { it('reverts', async function () { - await assertRevert(this.token.approve(to, unknownTokenId, { from: sender })); + await assertRevert(this.token.approve(approved, unknownTokenId, { from: operator })); }); }); }); describe('setApprovalForAll', function () { - const sender = creator; - context('when the operator willing to approve is not the owner', function () { - const operator = accounts[1]; - context('when there is no operator approval set by the sender', function () { it('approves the operator', async function () { - await this.token.setApprovalForAll(operator, true, { from: sender }); + await this.token.setApprovalForAll(operator, true, { from: owner }); - (await this.token.isApprovedForAll(sender, operator)).should.equal(true); + (await this.token.isApprovedForAll(owner, operator)).should.equal(true); }); it('emits an approval event', async function () { - const { logs } = await this.token.setApprovalForAll(operator, true, { from: sender }); + const { logs } = await this.token.setApprovalForAll(operator, true, { from: owner }); logs.length.should.be.equal(1); logs[0].event.should.be.equal('ApprovalForAll'); - logs[0].args.owner.should.be.equal(sender); + logs[0].args.owner.should.be.equal(owner); logs[0].args.operator.should.be.equal(operator); logs[0].args.approved.should.equal(true); }); @@ -455,49 +456,49 @@ function shouldBehaveLikeERC721Basic (accounts) { context('when the operator was set as not approved', function () { beforeEach(async function () { - await this.token.setApprovalForAll(operator, false, { from: sender }); + await this.token.setApprovalForAll(operator, false, { from: owner }); }); it('approves the operator', async function () { - await this.token.setApprovalForAll(operator, true, { from: sender }); + await this.token.setApprovalForAll(operator, true, { from: owner }); - (await this.token.isApprovedForAll(sender, operator)).should.equal(true); + (await this.token.isApprovedForAll(owner, operator)).should.equal(true); }); it('emits an approval event', async function () { - const { logs } = await this.token.setApprovalForAll(operator, true, { from: sender }); + const { logs } = await this.token.setApprovalForAll(operator, true, { from: owner }); logs.length.should.be.equal(1); logs[0].event.should.be.equal('ApprovalForAll'); - logs[0].args.owner.should.be.equal(sender); + logs[0].args.owner.should.be.equal(owner); logs[0].args.operator.should.be.equal(operator); logs[0].args.approved.should.equal(true); }); it('can unset the operator approval', async function () { - await this.token.setApprovalForAll(operator, false, { from: sender }); + await this.token.setApprovalForAll(operator, false, { from: owner }); - (await this.token.isApprovedForAll(sender, operator)).should.equal(false); + (await this.token.isApprovedForAll(owner, operator)).should.equal(false); }); }); context('when the operator was already approved', function () { beforeEach(async function () { - await this.token.setApprovalForAll(operator, true, { from: sender }); + await this.token.setApprovalForAll(operator, true, { from: owner }); }); it('keeps the approval to the given address', async function () { - await this.token.setApprovalForAll(operator, true, { from: sender }); + await this.token.setApprovalForAll(operator, true, { from: owner }); - (await this.token.isApprovedForAll(sender, operator)).should.equal(true); + (await this.token.isApprovedForAll(owner, operator)).should.equal(true); }); it('emits an approval event', async function () { - const { logs } = await this.token.setApprovalForAll(operator, true, { from: sender }); + const { logs } = await this.token.setApprovalForAll(operator, true, { from: owner }); logs.length.should.be.equal(1); logs[0].event.should.be.equal('ApprovalForAll'); - logs[0].args.owner.should.be.equal(sender); + logs[0].args.owner.should.be.equal(owner); logs[0].args.operator.should.be.equal(operator); logs[0].args.approved.should.equal(true); }); @@ -505,10 +506,8 @@ function shouldBehaveLikeERC721Basic (accounts) { }); context('when the operator is the owner', function () { - const operator = creator; - it('reverts', async function () { - await assertRevert(this.token.setApprovalForAll(operator, true, { from: sender })); + await assertRevert(this.token.setApprovalForAll(owner, true, { from: owner })); }); }); }); diff --git a/test/token/ERC721/ERC721Basic.test.js b/test/token/ERC721/ERC721Basic.test.js index f86a97f5b68..708523ed228 100644 --- a/test/token/ERC721/ERC721Basic.test.js +++ b/test/token/ERC721/ERC721Basic.test.js @@ -1,5 +1,4 @@ const { shouldBehaveLikeERC721Basic } = require('./ERC721Basic.behavior'); -const { shouldBehaveLikeMintAndBurnERC721 } = require('./ERC721MintBurn.behavior'); const BigNumber = web3.BigNumber; const ERC721Basic = artifacts.require('ERC721BasicMock.sol'); @@ -8,11 +7,10 @@ require('chai') .use(require('chai-bignumber')(BigNumber)) .should(); -contract('ERC721Basic', function (accounts) { +contract('ERC721Basic', function ([_, creator, ...accounts]) { beforeEach(async function () { - this.token = await ERC721Basic.new({ from: accounts[0] }); + this.token = await ERC721Basic.new({ from: creator }); }); - shouldBehaveLikeERC721Basic(accounts); - shouldBehaveLikeMintAndBurnERC721(accounts); + shouldBehaveLikeERC721Basic(creator, creator, accounts); }); diff --git a/test/token/ERC721/ERC721Burnable.test.js b/test/token/ERC721/ERC721Burnable.test.js new file mode 100644 index 00000000000..34605028ddb --- /dev/null +++ b/test/token/ERC721/ERC721Burnable.test.js @@ -0,0 +1,22 @@ +const { shouldBehaveLikeERC721Basic } = require('./ERC721Basic.behavior'); +const { + shouldBehaveLikeMintAndBurnERC721, +} = require('./ERC721MintBurn.behavior'); + +const BigNumber = web3.BigNumber; +const ERC721Burnable = artifacts.require('ERC721MintableBurnableImpl.sol'); + +require('chai') + .use(require('chai-bignumber')(BigNumber)) + .should(); + +contract('ERC721Burnable', function ([_, creator, ...accounts]) { + const minter = creator; + + beforeEach(async function () { + this.token = await ERC721Burnable.new({ from: creator }); + }); + + shouldBehaveLikeERC721Basic(creator, minter, accounts); + shouldBehaveLikeMintAndBurnERC721(creator, minter, accounts); +}); diff --git a/test/token/ERC721/ERC721MintBurn.behavior.js b/test/token/ERC721/ERC721MintBurn.behavior.js index c165f170356..2b22937d7e4 100644 --- a/test/token/ERC721/ERC721MintBurn.behavior.js +++ b/test/token/ERC721/ERC721MintBurn.behavior.js @@ -1,84 +1,100 @@ const { assertRevert } = require('../../helpers/assertRevert'); +const expectEvent = require('../../helpers/expectEvent'); const BigNumber = web3.BigNumber; require('chai') .use(require('chai-bignumber')(BigNumber)) .should(); -function shouldBehaveLikeMintAndBurnERC721 (accounts) { +function shouldBehaveLikeMintAndBurnERC721 ( + creator, + minter, + [owner, newOwner, approved, anyone] +) { const firstTokenId = 1; const secondTokenId = 2; - const unknownTokenId = 3; - const creator = accounts[0]; + const thirdTokenId = 3; + const unknownTokenId = 4; const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; + const MOCK_URI = 'https://example.com'; describe('like a mintable and burnable ERC721', function () { beforeEach(async function () { - await this.token.mint(creator, firstTokenId, { from: creator }); - await this.token.mint(creator, secondTokenId, { from: creator }); + await this.token.mint(owner, firstTokenId, { from: minter }); + await this.token.mint(owner, secondTokenId, { from: minter }); }); describe('mint', function () { - const to = accounts[1]; - const tokenId = unknownTokenId; let logs = null; describe('when successful', function () { beforeEach(async function () { - const result = await this.token.mint(to, tokenId); + const result = await this.token.mint(newOwner, thirdTokenId, { from: minter }); logs = result.logs; }); it('assigns the token to the new owner', async function () { - (await this.token.ownerOf(tokenId)).should.be.equal(to); + (await this.token.ownerOf(thirdTokenId)).should.be.equal(newOwner); }); it('increases the balance of its owner', async function () { - (await this.token.balanceOf(to)).should.be.bignumber.equal(1); + (await this.token.balanceOf(newOwner)).should.be.bignumber.equal(1); }); - it('emits a transfer event', async function () { - logs.length.should.be.equal(1); - logs[0].event.should.be.equal('Transfer'); - logs[0].args.from.should.be.equal(ZERO_ADDRESS); - logs[0].args.to.should.be.equal(to); - logs[0].args.tokenId.should.be.bignumber.equal(tokenId); + it('emits a transfer and minted event', async function () { + await expectEvent.inLogs(logs, 'Transfer', { + from: ZERO_ADDRESS, + to: newOwner, + }); + logs[0].args.tokenId.should.be.bignumber.equal(thirdTokenId); + + await expectEvent.inLogs(logs, 'Minted', { + to: newOwner, + }); + logs[1].args.tokenId.should.be.bignumber.equal(thirdTokenId); }); }); describe('when the given owner address is the zero address', function () { it('reverts', async function () { - await assertRevert(this.token.mint(ZERO_ADDRESS, tokenId)); + await assertRevert(this.token.mint(ZERO_ADDRESS, thirdTokenId)); }); }); describe('when the given token ID was already tracked by this contract', function () { it('reverts', async function () { - await assertRevert(this.token.mint(accounts[1], firstTokenId)); + await assertRevert(this.token.mint(owner, firstTokenId)); + }); + }); + }); + + describe('mintWithTokenURI', function () { + it('can mint with a tokenUri', async function () { + await this.token.mintWithTokenURI(newOwner, thirdTokenId, MOCK_URI, { + from: minter, }); }); }); describe('burn', function () { const tokenId = firstTokenId; - const sender = creator; let logs = null; describe('when successful', function () { beforeEach(async function () { - const result = await this.token.burn(tokenId, { from: sender }); + const result = await this.token.burn(tokenId, { from: owner }); logs = result.logs; }); it('burns the given token ID and adjusts the balance of the owner', async function () { await assertRevert(this.token.ownerOf(tokenId)); - (await this.token.balanceOf(sender)).should.be.bignumber.equal(1); + (await this.token.balanceOf(owner)).should.be.bignumber.equal(1); }); it('emits a burn event', async function () { logs.length.should.be.equal(1); logs[0].event.should.be.equal('Transfer'); - logs[0].args.from.should.be.equal(sender); + logs[0].args.from.should.be.equal(owner); logs[0].args.to.should.be.equal(ZERO_ADDRESS); logs[0].args.tokenId.should.be.bignumber.equal(tokenId); }); @@ -86,8 +102,8 @@ function shouldBehaveLikeMintAndBurnERC721 (accounts) { describe('when there is a previous approval', function () { beforeEach(async function () { - await this.token.approve(accounts[1], tokenId, { from: sender }); - const result = await this.token.burn(tokenId, { from: sender }); + await this.token.approve(approved, tokenId, { from: owner }); + const result = await this.token.burn(tokenId, { from: owner }); logs = result.logs; }); @@ -98,7 +114,38 @@ function shouldBehaveLikeMintAndBurnERC721 (accounts) { describe('when the given token ID was not tracked by this contract', function () { it('reverts', async function () { - await assertRevert(this.token.burn(unknownTokenId, { from: creator })); + await assertRevert( + this.token.burn(unknownTokenId, { from: creator }) + ); + }); + }); + }); + + describe('finishMinting', function () { + it('allows the minter to finish minting', async function () { + const { logs } = await this.token.finishMinting({ from: minter }); + expectEvent.inLogs(logs, 'MintingFinished'); + }); + }); + + context('mintingFinished', function () { + beforeEach(async function () { + await this.token.finishMinting({ from: minter }); + }); + + describe('mint', function () { + it('reverts', async function () { + await assertRevert( + this.token.mint(owner, thirdTokenId, { from: minter }) + ); + }); + }); + + describe('mintWithTokenURI', function () { + it('reverts', async function () { + await assertRevert( + this.token.mintWithTokenURI(owner, thirdTokenId, MOCK_URI, { from: minter }) + ); }); }); }); diff --git a/test/token/ERC721/ERC721Mintable.test.js b/test/token/ERC721/ERC721Mintable.test.js new file mode 100644 index 00000000000..b53ddb59301 --- /dev/null +++ b/test/token/ERC721/ERC721Mintable.test.js @@ -0,0 +1,24 @@ +const { shouldBehaveLikeERC721Basic } = require('./ERC721Basic.behavior'); +const { + shouldBehaveLikeMintAndBurnERC721, +} = require('./ERC721MintBurn.behavior'); + +const BigNumber = web3.BigNumber; +const ERC721Mintable = artifacts.require('ERC721MintableBurnableImpl.sol'); + +require('chai') + .use(require('chai-bignumber')(BigNumber)) + .should(); + +contract('ERC721Mintable', function ([_, creator, ...accounts]) { + const minter = creator; + + beforeEach(async function () { + this.token = await ERC721Mintable.new({ + from: creator, + }); + }); + + shouldBehaveLikeERC721Basic(creator, minter, accounts); + shouldBehaveLikeMintAndBurnERC721(creator, minter, accounts); +}); diff --git a/test/token/ERC721/ERC721Pausable.test.js b/test/token/ERC721/ERC721Pausable.test.js index 0cf0299cea9..240e478e8a3 100644 --- a/test/token/ERC721/ERC721Pausable.test.js +++ b/test/token/ERC721/ERC721Pausable.test.js @@ -9,38 +9,45 @@ require('chai') .use(require('chai-bignumber')(BigNumber)) .should(); -contract('ERC721Pausable', function ([_, pauser, otherPauser, recipient, operator, ...otherAccounts]) { +contract('ERC721Pausable', function ([ + _, + creator, + owner, + operator, + otherPauser, + ...accounts +]) { beforeEach(async function () { - this.token = await ERC721Pausable.new({ from: pauser }); + this.token = await ERC721Pausable.new({ from: creator }); }); describe('pauser role', function () { beforeEach(async function () { this.contract = this.token; - await this.contract.addPauser(otherPauser, { from: pauser }); + await this.contract.addPauser(otherPauser, { from: creator }); }); - shouldBehaveLikePublicRole(pauser, otherPauser, otherAccounts, 'pauser'); + shouldBehaveLikePublicRole(creator, otherPauser, accounts, 'pauser'); }); context('when token is paused', function () { beforeEach(async function () { - await this.token.pause({ from: pauser }); + await this.token.pause({ from: creator }); }); - shouldBehaveLikeERC721PausedToken(pauser, [...otherAccounts]); + shouldBehaveLikeERC721PausedToken(creator, accounts); }); context('when token is not paused yet', function () { - shouldBehaveLikeERC721Basic([pauser, ...otherAccounts]); + shouldBehaveLikeERC721Basic(creator, creator, accounts); }); context('when token is paused and then unpaused', function () { beforeEach(async function () { - await this.token.pause({ from: pauser }); - await this.token.unpause({ from: pauser }); + await this.token.pause({ from: creator }); + await this.token.unpause({ from: creator }); }); - shouldBehaveLikeERC721Basic([pauser, ...otherAccounts]); + shouldBehaveLikeERC721Basic(creator, creator, accounts); }); });