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
Closed
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["es2015", "stage-2", "stage-3"]
}
148 changes: 148 additions & 0 deletions contracts/ERC721.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
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) {
return balances[_owner];
}

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

//approve a contract to transfer badge on your behalf
function approve(address _to, uint256 _tokenId) public {
internalApprove(msg.sender, _to, _tokenId);
}

//an approved contract transfers the badge for you (after approval)
function transferFrom(address _from, address _to, uint256 _tokenId) public {
require(allowed[_tokenId] == msg.sender); //allowed to transfer
require(tokenOwner[_tokenId] == _from); //token should still be in control of owner

internalTransfer(_from, _to, _tokenId);
}

//transfer badge to someone else.
function transfer(address _to, uint256 _tokenId) public {
require(tokenOwner[_tokenId] == msg.sender); //sender must be owner

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

function internalApprove(address _owner, address _to, uint256 _tokenId) tokenExists(_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 {
require(_to != 0); //not allowed to burn in transfer method
removeToken(_from, _tokenId);
addToken(_to, _tokenId);

Transfer(_from, _to, _tokenId);
}

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

delete tokenOwner[_tokenId]; //remove token from circulation (effectively remove ownership access)

//next: remove token from owner's helper data structures.
uint256 index = tokenIndexInOwnerArray[_tokenId];

//1) Put last item into index of token to be removed.
ownedTokens[_from][index] = ownedTokens[_from][balances[_from]-1];

//2) remove from index array
delete tokenIndexInOwnerArray[_tokenId];

//3 update index array of token that was moved into the token that was removed
tokenIndexInOwnerArray[ownedTokens[_from][index]] = index;

//4) delete last item (since it's now a duplicate)
delete ownedTokens[_from][balances[_from]-1];

//5) 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 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) {
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;
}
}
42 changes: 42 additions & 0 deletions contracts/ERC721Interface.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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 {

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


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

// This function is implemented through a public variable (which creates appropriate method).
// Solidity doesn't recognize those functions as "implementing the interface".
// function totalSupply() public view returns (uint256 total);

// OPTIONALS
//just a more specific transferFrom
function takeOwnership(uint256 tokenId) public; //specific transferFrom
//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);

// These function are implemented through public variables (which creates appropriate methods).
// Solidity doesn't recognize those functions as "implementing the interface".
// function name() public view returns (string _name);
// function symbol() public view returns (string _symbol);
// function tokenMetadata(uint256 _tokenId) public view returns (string infoUrl);

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

/*
The API names here should not be regarded as conforming to a specific API.
Perhaps EIP 612 should be used here: https://github.com/ethereum/EIPs/pull/621
This is just to test creation/burning of an ERC721 token.
*/
contract TestERC721Implementation is ERC721 {

address public admin;
uint256 public counter = 0;

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

function createToken(address _minter) public {
require(msg.sender == admin);
addToken(_minter, counter);
Transfer(0, _minter, counter);
totalSupply += 1;
counter += 1; // every new token gets a new ID
}

function burnToken(uint256 _tokenId) public {
require(tokenOwner[_tokenId] == msg.sender); //token should be in control of owner
removeToken(msg.sender, _tokenId);
Transfer(msg.sender, 0, _tokenId);
totalSupply -= 1;
}
}
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,15 @@
},
"homepage": "https://github.com/ConsenSys/Tokens#readme",
"dependencies": {
"truffle": "4.0.1",
"truffle": "4.0.4",
"truffle-hdwallet-provider": "0.0.3"
},
"devDependencies": {
"babel-polyfill": "^6.26.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"babel-preset-stage-3": "^6.24.1",
"babel-register": "^6.26.0",
"eslint-plugin-import": "2.7.0",
"eslint-plugin-node": "5.1.0",
"eslint-plugin-react": "7.1.0",
Expand Down
Loading