Skip to content

Commit

Permalink
Merge 002da3f into 84f85a4
Browse files Browse the repository at this point in the history
  • Loading branch information
Alan Arvelo committed Jul 17, 2019
2 parents 84f85a4 + 002da3f commit 38f23e0
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 4 deletions.
10 changes: 9 additions & 1 deletion contracts/mocks/ERC721Mock.sol
Expand Up @@ -4,9 +4,17 @@ import "../token/ERC721/ERC721.sol";

/**
* @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 {
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 {
_mint(to, tokenId);
}
Expand Down
48 changes: 47 additions & 1 deletion contracts/token/ERC721/ERC721.sol
Expand Up @@ -174,7 +174,24 @@ contract ERC721 is ERC165, IERC721 {
* @param _data bytes data to send along with a safe transfer check
*/
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");
}

Expand All @@ -201,6 +218,35 @@ contract ERC721 is ERC165, IERC721 {
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
*/
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.
* Reverts if the given token ID already exists.
Expand Down
4 changes: 2 additions & 2 deletions contracts/token/ERC721/ERC721Pausable.sol
Expand Up @@ -16,7 +16,7 @@ contract ERC721Pausable is ERC721, Pausable {
super.setApprovalForAll(to, approved);
}

function transferFrom(address from, address to, uint256 tokenId) public whenNotPaused {
super.transferFrom(from, to, tokenId);
function _transferFrom(address from, address to, uint256 tokenId) internal whenNotPaused {
super._transferFrom(from, to, tokenId);
}
}
63 changes: 63 additions & 0 deletions test/token/ERC721/ERC721.behavior.js
Expand Up @@ -4,6 +4,7 @@ const { ZERO_ADDRESS } = constants;
const { shouldSupportInterfaces } = require('../../introspection/SupportsInterface.behavior');

const ERC721ReceiverMock = artifacts.require('ERC721ReceiverMock.sol');
const ERC721Mock = artifacts.require('ERC721Mock.sol');

function shouldBehaveLikeERC721 (
creator,
Expand Down Expand Up @@ -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 () {
const tokenId = firstTokenId;

Expand Down

0 comments on commit 38f23e0

Please sign in to comment.