Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Detailed ERC721 implementation
  • Loading branch information
facuspagnuolo committed Jan 17, 2018
commit a11062c8a450bb7cf9e4089a278607706a1d808e
12 changes: 12 additions & 0 deletions contracts/mocks/DetailedERC721TokenMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pragma solidity ^0.4.18;

import "./ERC721TokenMock.sol";
import "../token/DetailedERC721Token.sol";

/**
* @title DetailedERC721TokenMock
* This mock just provides a public mint and burn functions for testing purposes.
*/
contract DetailedERC721TokenMock is ERC721TokenMock, DetailedERC721Token {
function DetailedERC721TokenMock(string name, string symbol) DetailedERC721Token(name, symbol) public { }
}
16 changes: 16 additions & 0 deletions contracts/token/DetailedERC721.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
pragma solidity ^0.4.18;

import "./ERC721.sol";

/**
* @title Detailed ERC721 interface
* @dev see https://github.com/ethereum/eips/issues/721
*/
contract DetailedERC721 is ERC721 {
function name() public view returns (string _name);
function symbol() public view returns (string _symbol);
function implementsERC721() public pure returns (bool);
function setTokenMetadata(uint256 _tokenId, string _metadata) public;
function tokenMetadata(uint256 _tokenId) public view returns (string infoUrl);
function tokenOfOwnerByIndex(address _owner, uint256 _index) public view returns (uint256 _tokenId);
}
85 changes: 85 additions & 0 deletions contracts/token/DetailedERC721Token.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
pragma solidity ^0.4.18;

import "./ERC721Token.sol";
import "./DetailedERC721.sol";

/**
* @title Detailed ERC721 Token
* This implementation includes all the required and optional functionality of the ERC721 standard
* @dev see https://github.com/ethereum/eips/issues/721
*/
contract DetailedERC721Token is DetailedERC721, ERC721Token {
// Token name
string private _name;

// Token symbol
string private _symbol;

// Token metadata
mapping (uint256 => string) private _metadata;

// Event triggered every time a token metadata gets updated
event MetadataUpdated(address _owner, uint256 _tokenId, string _newMetadata);

/**
* @dev Constructor function
*/
function DetailedERC721Token(string name, string symbol) public {
_name = name;
_symbol = symbol;
}

/**
* @dev Gets the token name
* @return string representing the token name
*/
function name() public view returns (string) {
return _name;
}

/**
* @dev Gets the token symbol
* @return string representing the token symbol
*/
function symbol() public view returns (string) {
return _symbol;
}

/**
* @dev Ensures this contract is an ERC721 implementation
* @return true to ensure this contract implements ERC721 functionality
*/
function implementsERC721() public pure returns (bool) {
return true;
}

/**
* @dev Gets the token ID at a given index of the tokens list of the requested owner
* @param _owner address owning the tokens list to be accessed
* @param _index uint256 representing the index to be accessed of the requested tokens list
* @return uint256 token ID at the given index of the tokens list owned by the requested address
*/
function tokenOfOwnerByIndex(address _owner, uint256 _index) public view returns (uint256) {
require(_index < balanceOf(_owner));
return tokensOf(_owner)[_index];
}

/**
* @dev Gets the metadata of the given token ID
* @param _tokenId uint256 ID of the token to query the metadata of
* @return string representing the metadata of the given token ID
*/
function tokenMetadata(uint256 _tokenId) public view returns (string) {
return _metadata[_tokenId];
}

/**
* @dev Sets the metadata of the given token ID
* @param _tokenId uint256 ID of the token to set the metadata of
* @param _newMetadata string representing the new metadata to be set
*/
function setTokenMetadata(uint256 _tokenId, string _newMetadata) public onlyOwnerOf(_tokenId) {
_metadata[_tokenId] = _newMetadata;
MetadataUpdated(msg.sender, _tokenId, _newMetadata);
}
}
133 changes: 133 additions & 0 deletions test/token/DetailedERC721Token.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import assertRevert from '../helpers/assertRevert';
const BigNumber = web3.BigNumber;
const DetailedERC721Token = artifacts.require('DetailedERC721TokenMock.sol');

require('chai')
.use(require('chai-as-promised'))
.use(require('chai-bignumber')(BigNumber))
.should();

contract('DetailedERC721Token', accounts => {
let token = null;
const _name = 'Non Fungible Token';
const _symbol = 'NFT';
const _firstTokenId = 1;
const _secondTokenId = 2;
const _unknownTokenId = 3;
const _creator = accounts[0];

beforeEach(async function () {
token = await DetailedERC721Token.new(_name, _symbol, { from: _creator });
await token.publicMint(_creator, _firstTokenId, { from: _creator });
await token.publicMint(_creator, _secondTokenId, { from: _creator });
});

describe('name', function () {
it('has a name', async function () {
const name = await token.name();
name.should.be.equal(_name);
});
});

describe('symbol', function () {
it('has a symbol', async function () {
const symbol = await token.symbol();
symbol.should.be.equal(_symbol);
});
});

describe('tokenOfOwnerByIndex', function () {
describe('when the given address owns some tokens', function () {
const owner = _creator;

describe('when the given index is lower than the amount of tokens owned by the given address', function () {
const index = 0;

it('returns the token ID placed at the given index', async function () {
const tokenId = await token.tokenOfOwnerByIndex(owner, index);
tokenId.should.be.bignumber.equal(_firstTokenId);
});
});

describe('when the index is greater than or equal to the total tokens owned by the given address', function () {
const index = 2;

it('reverts', async function () {
await assertRevert(token.tokenOfOwnerByIndex(owner, index));
});
});
});

describe('when the given address does not own any token', function () {
const owner = accounts[1];

it('reverts', async function () {
await assertRevert(token.tokenOfOwnerByIndex(owner, 0));
});
});
});

describe('tokenMetadata', function () {
describe('when no metadata was set', function () {
it('the given token has no metadata', async function () {
const metadata = await token.tokenMetadata(_firstTokenId);

metadata.should.be.equal('');
});
});

describe('when some metadata was set', function () {
it('returns the metadata of the given token', async function () {
await token.setTokenMetadata(_firstTokenId, 'dummy metadata', { from: _creator });

const metadata = await token.tokenMetadata(_firstTokenId);

metadata.should.be.equal('dummy metadata');
});
});
});

describe('setTokenMetadata', function () {
describe('when the sender is not the token owner', function () {
const sender = accounts[1];

it('reverts', async function () {
await assertRevert(token.setTokenMetadata(_firstTokenId, 'new metadata', { from: sender }));
});
});

describe('when the sender is the owner of the token', function () {
const sender = _creator;

describe('when the given token ID was tracked by this contract before', function () {
const tokenId = _firstTokenId;

it('updates the metadata of the given token ID', async function () {
await token.setTokenMetadata(_firstTokenId, 'new metadata', { from: sender });

const metadata = await token.tokenMetadata(tokenId);

metadata.should.be.equal('new metadata');
});

it('emits a metadata updated event', async function () {
const { logs } = await token.setTokenMetadata(tokenId, 'new metadata', { from: sender });

logs.length.should.be.equal(1);
logs[0].event.should.be.eq('MetadataUpdated');
logs[0].args._owner.should.be.equal(sender);
logs[0].args._tokenId.should.be.bignumber.equal(tokenId);
logs[0].args._newMetadata.should.be.equal('new metadata');
});
});

describe('when the given token ID was not tracked by this contract before', function () {
const tokenId = _unknownTokenId;

it('reverts', async function () {
await assertRevert(token.setTokenMetadata(tokenId, 'new metadata'), { from: sender });
});
});
});
});
});