* added _safeTransferFrom function * added safeMint functions * added package-lock.json for consistency, don't know why it changes * added initial suggestions/modifications * change _safeTransferFrom to internal, reverted package-lock.json to original, and changed ERC721Pausable to override _transferFrom instead of transferFrom * included tests for safeMint functions * modified safeMint tests to be on ERC721Mock contract * added safeMint to ERC721Mintable & respective test to ERC721MintBurn.behavior.js
This commit is contained in:
committed by
Francisco Giordano
parent
571fa7f4e7
commit
377431bc4c
@ -4,9 +4,17 @@ import "../token/ERC721/ERC721.sol";
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @title ERC721Mock
|
* @title ERC721Mock
|
||||||
* This mock just provides a public mint and burn functions for testing purposes
|
* This mock just provides a public safeMint, mint, and burn functions for testing purposes
|
||||||
*/
|
*/
|
||||||
contract ERC721Mock is ERC721 {
|
contract ERC721Mock is ERC721 {
|
||||||
|
function safeMint(address to, uint256 tokenId) public {
|
||||||
|
_safeMint(to, tokenId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeMint(address to, uint256 tokenId, bytes memory _data) public {
|
||||||
|
_safeMint(to, tokenId, _data);
|
||||||
|
}
|
||||||
|
|
||||||
function mint(address to, uint256 tokenId) public {
|
function mint(address to, uint256 tokenId) public {
|
||||||
_mint(to, tokenId);
|
_mint(to, tokenId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -174,7 +174,24 @@ contract ERC721 is ERC165, IERC721 {
|
|||||||
* @param _data bytes data to send along with a safe transfer check
|
* @param _data bytes data to send along with a safe transfer check
|
||||||
*/
|
*/
|
||||||
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public {
|
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public {
|
||||||
transferFrom(from, to, tokenId);
|
require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
|
||||||
|
_safeTransferFrom(from, to, tokenId, _data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Safely transfers the ownership of a given token ID to another address
|
||||||
|
* If the target address is a contract, it must implement `onERC721Received`,
|
||||||
|
* which is called upon a safe transfer, and return the magic value
|
||||||
|
* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
|
||||||
|
* the transfer is reverted.
|
||||||
|
* Requires the msg.sender to be the owner, approved, or operator
|
||||||
|
* @param from current owner of the token
|
||||||
|
* @param to address to receive the ownership of the given token ID
|
||||||
|
* @param tokenId uint256 ID of the token to be transferred
|
||||||
|
* @param _data bytes data to send along with a safe transfer check
|
||||||
|
*/
|
||||||
|
function _safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) internal {
|
||||||
|
_transferFrom(from, to, tokenId);
|
||||||
require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
|
require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,6 +218,36 @@ contract ERC721 is ERC165, IERC721 {
|
|||||||
return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
|
return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Internal function to safely mint a new token.
|
||||||
|
* Reverts if the given token ID already exists.
|
||||||
|
* If the target address is a contract, it must implement `onERC721Received`,
|
||||||
|
* which is called upon a safe transfer, and return the magic value
|
||||||
|
* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
|
||||||
|
* the transfer is reverted.
|
||||||
|
* @param to The address that will own the minted token
|
||||||
|
* @param tokenId uint256 ID of the token to be minted
|
||||||
|
*/
|
||||||
|
function _safeMint(address to, uint256 tokenId) internal {
|
||||||
|
_safeMint(to, tokenId, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Internal function to safely mint a new token.
|
||||||
|
* Reverts if the given token ID already exists.
|
||||||
|
* If the target address is a contract, it must implement `onERC721Received`,
|
||||||
|
* which is called upon a safe transfer, and return the magic value
|
||||||
|
* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
|
||||||
|
* the transfer is reverted.
|
||||||
|
* @param to The address that will own the minted token
|
||||||
|
* @param tokenId uint256 ID of the token to be minted
|
||||||
|
* @param _data bytes data to send along with a safe transfer check
|
||||||
|
*/
|
||||||
|
function _safeMint(address to, uint256 tokenId, bytes memory _data) internal {
|
||||||
|
_mint(to, tokenId);
|
||||||
|
require(_checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Internal function to mint a new token.
|
* @dev Internal function to mint a new token.
|
||||||
* Reverts if the given token ID already exists.
|
* Reverts if the given token ID already exists.
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import "../../access/roles/MinterRole.sol";
|
|||||||
contract ERC721Mintable is ERC721, MinterRole {
|
contract ERC721Mintable is ERC721, MinterRole {
|
||||||
/**
|
/**
|
||||||
* @dev Function to mint tokens.
|
* @dev Function to mint tokens.
|
||||||
* @param to The address that will receive the minted tokens.
|
* @param to The address that will receive the minted token.
|
||||||
* @param tokenId The token id to mint.
|
* @param tokenId The token id to mint.
|
||||||
* @return A boolean that indicates if the operation was successful.
|
* @return A boolean that indicates if the operation was successful.
|
||||||
*/
|
*/
|
||||||
@ -18,4 +18,27 @@ contract ERC721Mintable is ERC721, MinterRole {
|
|||||||
_mint(to, tokenId);
|
_mint(to, tokenId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Function to safely mint tokens.
|
||||||
|
* @param to The address that will receive the minted token.
|
||||||
|
* @param tokenId The token id to mint.
|
||||||
|
* @return A boolean that indicates if the operation was successful.
|
||||||
|
*/
|
||||||
|
function safeMint(address to, uint256 tokenId) public onlyMinter returns (bool) {
|
||||||
|
_safeMint(to, tokenId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Function to safely mint tokens.
|
||||||
|
* @param to The address that will receive the minted token.
|
||||||
|
* @param tokenId The token id to mint.
|
||||||
|
* @param _data bytes data to send along with a safe transfer check.
|
||||||
|
* @return A boolean that indicates if the operation was successful.
|
||||||
|
*/
|
||||||
|
function safeMint(address to, uint256 tokenId, bytes memory _data) public onlyMinter returns (bool) {
|
||||||
|
_safeMint(to, tokenId, _data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ contract ERC721Pausable is ERC721, Pausable {
|
|||||||
super.setApprovalForAll(to, approved);
|
super.setApprovalForAll(to, approved);
|
||||||
}
|
}
|
||||||
|
|
||||||
function transferFrom(address from, address to, uint256 tokenId) public whenNotPaused {
|
function _transferFrom(address from, address to, uint256 tokenId) internal whenNotPaused {
|
||||||
super.transferFrom(from, to, tokenId);
|
super._transferFrom(from, to, tokenId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ const { ZERO_ADDRESS } = constants;
|
|||||||
const { shouldSupportInterfaces } = require('../../introspection/SupportsInterface.behavior');
|
const { shouldSupportInterfaces } = require('../../introspection/SupportsInterface.behavior');
|
||||||
|
|
||||||
const ERC721ReceiverMock = artifacts.require('ERC721ReceiverMock.sol');
|
const ERC721ReceiverMock = artifacts.require('ERC721ReceiverMock.sol');
|
||||||
|
const ERC721Mock = artifacts.require('ERC721Mock.sol');
|
||||||
|
|
||||||
function shouldBehaveLikeERC721 (
|
function shouldBehaveLikeERC721 (
|
||||||
creator,
|
creator,
|
||||||
@ -324,6 +325,68 @@ function shouldBehaveLikeERC721 (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('safe mint', function () {
|
||||||
|
const fourthTokenId = new BN(4);
|
||||||
|
const tokenId = fourthTokenId;
|
||||||
|
const data = '0x42';
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
this.ERC721Mock = await ERC721Mock.new();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('via safeMint', function () { // regular minting is tested in ERC721Mintable.test.js and others
|
||||||
|
it('should call onERC721Received — with data', async function () {
|
||||||
|
this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, false);
|
||||||
|
const receipt = await this.ERC721Mock.safeMint(this.receiver.address, tokenId, data);
|
||||||
|
|
||||||
|
await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
|
||||||
|
from: ZERO_ADDRESS,
|
||||||
|
tokenId: tokenId,
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onERC721Received — without data', async function () {
|
||||||
|
this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, false);
|
||||||
|
const receipt = await this.ERC721Mock.safeMint(this.receiver.address, tokenId);
|
||||||
|
|
||||||
|
await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', {
|
||||||
|
from: ZERO_ADDRESS,
|
||||||
|
tokenId: tokenId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('to a receiver contract returning unexpected value', function () {
|
||||||
|
it('reverts', async function () {
|
||||||
|
const invalidReceiver = await ERC721ReceiverMock.new('0x42', false);
|
||||||
|
await expectRevert(
|
||||||
|
this.ERC721Mock.safeMint(invalidReceiver.address, tokenId),
|
||||||
|
'ERC721: transfer to non ERC721Receiver implementer'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('to a receiver contract that throws', function () {
|
||||||
|
it('reverts', async function () {
|
||||||
|
const invalidReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, true);
|
||||||
|
await expectRevert(
|
||||||
|
this.ERC721Mock.safeMint(invalidReceiver.address, tokenId),
|
||||||
|
'ERC721ReceiverMock: reverting'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('to a contract that does not implement the required function', function () {
|
||||||
|
it('reverts', async function () {
|
||||||
|
const invalidReceiver = this.token;
|
||||||
|
await expectRevert.unspecified(
|
||||||
|
this.ERC721Mock.safeMint(invalidReceiver.address, tokenId)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('approve', function () {
|
describe('approve', function () {
|
||||||
const tokenId = firstTokenId;
|
const tokenId = firstTokenId;
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ function shouldBehaveLikeMintAndBurnERC721 (
|
|||||||
const thirdTokenId = new BN(3);
|
const thirdTokenId = new BN(3);
|
||||||
const unknownTokenId = new BN(4);
|
const unknownTokenId = new BN(4);
|
||||||
const MOCK_URI = 'https://example.com';
|
const MOCK_URI = 'https://example.com';
|
||||||
|
const data = '0x42';
|
||||||
|
|
||||||
describe('like a mintable and burnable ERC721', function () {
|
describe('like a mintable and burnable ERC721', function () {
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
@ -72,6 +73,18 @@ function shouldBehaveLikeMintAndBurnERC721 (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('safeMint', function () {
|
||||||
|
it('it can safely mint with data', async function () {
|
||||||
|
await this.token.methods['safeMint(address,uint256,bytes)'](...[newOwner, thirdTokenId, data],
|
||||||
|
{ from: minter });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it can safely mint without data', async function () {
|
||||||
|
await this.token.methods['safeMint(address,uint256)'](...[newOwner, thirdTokenId],
|
||||||
|
{ from: minter });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('burn', function () {
|
describe('burn', function () {
|
||||||
const tokenId = firstTokenId;
|
const tokenId = firstTokenId;
|
||||||
let logs = null;
|
let logs = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user