-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
ERC721 (wip) #98
Changes from 4 commits
c9a5f22
98858f4
686bdbe
a71db20
aa2b0d6
128c8f7
b52457d
861d4a6
b0514b5
86464b2
b5d52ee
481d98b
6deadd6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) { | ||
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; | ||
} | ||
} |
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
} |
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); | ||
} | ||
} |
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
}) |
There was a problem hiding this comment.
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