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
Prev Previous commit
Implement ERC721 token with metadata
  • Loading branch information
facuspagnuolo committed Feb 26, 2018
commit e9f0b74d9d8afe0ff06b4a63bf273963db14a466
16 changes: 16 additions & 0 deletions contracts/mocks/ERC721TokenMetadataMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
pragma solidity ^0.4.18;

import "./ERC721TokenMock.sol";
import "../token/ERC721/ERC721TokenMetadata.sol";

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

import "./ERC721.sol";


/**
* @title Full ERC721 interface with metadata
* @dev see https://github.com/ethereum/eips/issues/721 and https://github.com/ethereum/EIPs/pull/841
*/
contract ERC721Metadata is ERC721 {
event MetadataUpdated(address _owner, uint256 _tokenId, string _newMetadata);

function setTokenMetadata(uint256 _tokenId, string _metadata) public;
function tokenMetadata(uint256 _tokenId) public view returns (string infoUrl);
}
39 changes: 39 additions & 0 deletions contracts/token/ERC721/ERC721TokenMetadata.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
pragma solidity ^0.4.18;

import "./ERC721Token.sol";
import "./ERC721Metadata.sol";


/**
* @title Full ERC721 Token with metadata
* This implementation includes all the functionality of the ERC721 standard besides metadata functionality
* @dev see https://github.com/ethereum/eips/issues/721 and https://github.com/ethereum/EIPs/pull/841
*/
contract ERC721TokenMetadata is ERC721Metadata, ERC721Token {
// Tokens metadata
mapping (uint256 => string) private _metadata;

/**
* @dev Constructor function
*/
function ERC721TokenMetadata(string name, string symbol) ERC721Token(name, symbol) public {}

/**
* @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);
}
}
88 changes: 88 additions & 0 deletions test/token/ERC721/ERC721TokenMetadata.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import assertRevert from '../../helpers/assertRevert';
const BigNumber = web3.BigNumber;
const ERC721TokenMetadata = artifacts.require('ERC721TokenMetadataMock.sol');

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

contract('ERC721TokenMetadata', 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 ERC721TokenMetadata.new(_name, _symbol, { from: _creator });
await token.mint(_creator, _firstTokenId, { from: _creator });
await token.mint(_creator, _secondTokenId, { from: _creator });
});

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 });
});
});
});
});
});