Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERC721 (wip) #98

Closed
wants to merge 13 commits into from
140 changes: 140 additions & 0 deletions contracts/ERC721.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
pragma solidity ^0.4.18;
import './ERC721Interface.sol';


/// @title Interface for contracts conforming to ERC-721: Non-Fungible Tokens
/// @author Dieter Shirley <dete@axiomzen.co> (https://github.com/dete)
// https://github.com/ethereum/EIPs/issues/721
// Derived from https://github.com/decentraland/land/blob/master/contracts/BasicNFT.sol
// & https://github.com/axiomzen/cryptokitties-bounty/blob/master/contracts/KittyOwnership.sol
// & https://github.com/axiomzen/cryptokitties-bounty/blob/master/contracts/KittyBase.sol
contract ERC721 is ERC721Interface {

uint256 public totalSupply;
string public name;
string public symbol;

// Array of owned tokens for a user
mapping(address => uint256[]) public ownedTokens;
mapping(address => uint256) public balances;
mapping(uint256 => uint256) public tokenIndexInOwnerArray;

// Mapping from token ID to owner
mapping(uint256 => address) public tokenOwner;

// Allowed transfers for a token (only one at a time)
mapping(uint256 => address) public allowed;

// Metadata associated with each token
mapping(uint256 => string) public tokenMetadata;


modifier tokenExists(uint256 _tokenId) {
require(tokenOwner[_tokenId] != 0);
_;
}

//how many badges of a specific release someone owns
function balanceOf(address _owner) public view returns (uint256 balance) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose taking the named var return out.

Changing this: returns (uint256 balance)
To this: returns (uint256)

Since the balance var is not even used

return balances[_owner];
}

//who owns a specific badge
function ownerOf(uint256 _tokenId) public view tokenExists(_tokenId) returns (address owner) {
return tokenOwner[_tokenId];
}

//approve a contract to transfer badge on your behalf
//todo: returns success ala ERC20?
function approve(address _to, uint256 _tokenId) tokenExists(_tokenId) public {
internalApprove(msg.sender, _to, _tokenId);
}

//an approved contract transfers the badge for you (after approval)
//todo: returns success ala ERC20?
function transferFrom(address _from, address _to, uint256 _tokenId) tokenExists(_tokenId) public {
require(allowed[_tokenId] == msg.sender); //allowed to transfer
require(tokenOwner[_tokenId] == _from); //

internalTransfer(_from, _to, _tokenId);
}

//transfer badge to someone else.
//returns bool success ala erc20?
function transfer(address _to, uint256 _tokenId) tokenExists(_tokenId) public {
require(tokenOwner[_tokenId] == msg.sender); //sender must be owner
require(_to != 0); //not allowed to burn in transfer method

//transfer token
internalTransfer(msg.sender, _to, _tokenId);
}

function internalApprove(address _owner, address _to, uint256 _tokenId) internal {
require(tokenOwner[_tokenId] == _owner); //must be owner of the token to set approval
require(_to != _owner); //can't approve to same address

address oldApprovedAddress = allowed[_tokenId];
allowed[_tokenId] = _to;

if((oldApprovedAddress == 0 && _to != 0) //set new approval
|| (oldApprovedAddress != 0 && _to != 0) //change/reaffirm approval
|| (oldApprovedAddress != 0 && _to == 0)) { //clear approval
Approval(_owner, _to, _tokenId);
}
}

function internalTransfer(address _from, address _to, uint256 _tokenId) internal {
removeToken(_from, _tokenId);
addToken(_to, _tokenId);

Transfer(_from, _to, _tokenId);
}

function removeToken(address _from, uint256 _tokenId) internal {
//clear pending approvals
internalApprove(_from, 0, _tokenId);

uint256 index = tokenIndexInOwnerArray[_tokenId];

//1) Put last item into index of token to be removed.
ownedTokens[_from][index] = ownedTokens[_from][balances[_from]-1];
//2) delete last item (since it's now a duplicate)
delete ownedTokens[_from][balances[_from]-1];
//3) reduce length of array
ownedTokens[_from].length -= 1;
balances[_from] -= 1;
}

function addToken(address _to, uint256 _tokenId) internal {
tokenOwner[_tokenId] = _to;
ownedTokens[_to].push(_tokenId);
tokenIndexInOwnerArray[_tokenId] = ownedTokens[_to].length-1;
balances[_to] += 1;
}

event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

// Optionals

//a more specific transferFrom
function takeOwnership(uint256 _tokenId) public {
address _from = tokenOwner[_tokenId];
transferFrom(_from, msg.sender, _tokenId);
}

//helper function to get a specific token (eg by iterating and fetching all of it)
function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256 tokenId) {
require(_index >= 0 && _index < balanceOf(_owner));
return ownedTokens[_owner][_index];
}

function getAllTokens(address _owner) public view returns (uint256[]) {
uint size = balances[_owner];
uint[] memory result = new uint256[](size);
for (uint i = 0; i < size; i++) {
result[i] = ownedTokens[_owner][i];
}
return result;
}
}
40 changes: 40 additions & 0 deletions contracts/ERC721Interface.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
pragma solidity ^0.4.18;


/// @title Interface for contracts conforming to ERC-721: Non-Fungible Tokens
/// @author Dieter Shirley <dete@axiomzen.co> (https://github.com/dete)
contract ERC721Interface {

//TODO: should this be done vs https://github.com/ethereum/EIPs/issues/165?
function implementsERC721() public pure returns (bool);
Copy link
Collaborator

@GNSPS GNSPS Dec 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also another interface definition not implemented in the actual token contract.


function totalSupply() public view returns (uint256 total);

function balanceOf(address _owner) public view returns (uint256 balance);

function ownerOf(uint256 _tokenId) public view returns (address owner);

function approve(address _to, uint256 _tokenId) public;

function transferFrom(address _from, address _to, uint256 _tokenId) public;

function transfer(address _to, uint256 _tokenId) public;

event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

// Optionals

//just a more specific transferFrom
function takeOwnership(uint tokenId) public; //specific transferFrom

function name() public view returns (string _name);

function symbol() public view returns (string _symbol);

//helper function to get a specific token (eg by iterating and fetching all of it)
function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256 tokenId);

//links to more metadata
function tokenMetadata(uint256 _tokenId) public view returns (string infoUrl);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also if we're inheriting the interface to make sure all the functions are specified in the contract then this one is not.

}
19 changes: 19 additions & 0 deletions contracts/TestERC721Implementation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
pragma solidity ^0.4.18;
import './ERC721.sol';


contract TestERC721Implementation is ERC721 {

address public admin;

function TestERC721Implementation() public {
admin = msg.sender;
}

function createToken(address _minter) public {
require(msg.sender == admin);
totalSupply += 1;

addToken(_minter, totalSupply);
}
}
17 changes: 17 additions & 0 deletions test/StandardERC721.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// const expectThrow = require('./utils').expectThrow
const TestERC721ImplementationAbstraction = artifacts.require('TestERC721Implementation')
let ERC721

contract('TestERC721Implementation', function (accounts) {
beforeEach(async () => {
// todo: deployment is OOG-ing.
Copy link
Collaborator

@GNSPS GNSPS Dec 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not OOGing. It is actually failing at compile time for not implementing all the function definitions on the interface.

ERC721 = await TestERC721ImplementationAbstraction.new({gas: 6700000, from: accounts[0]})
})

it('creation: admin should be set', async () => {
const admin = await ERC721.admin.call()
assert.strictEqual(admin, accounts[0])
})

// todo: add other tests
})
5 changes: 5 additions & 0 deletions truffle.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ module.exports = {
},
testrpc: {
network_id: 'default'
},
ganache: {
host: '127.0.0.1',
port: '7545',
network_id: '5777'
}
}
}