Split tokens package into erc20 and erc721

This commit is contained in:
Amir Bandeali
2019-01-21 14:20:33 -08:00
parent 9fa8619590
commit 0758f231e2
104 changed files with 729 additions and 878 deletions

View File

@@ -0,0 +1,3 @@
contracts/tokens/ZRXToken/ERC20Token_v1.sol
contracts/tokens/ZRXToken/Token_v1.sol
contracts/tokens/ZRXToken/UnlimitedAllowanceToken_v1.sol

View File

@@ -0,0 +1,56 @@
[
{
"version": "2.0.0",
"changes": [
{
"note": "Upgrade the bignumber.js to v8.0.2",
"pr": 1517
}
]
},
{
"timestamp": 1547747677,
"version": "1.0.6",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1547561734,
"version": "1.0.5",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1547225310,
"version": "1.0.4",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1547040760,
"version": "1.0.3",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1544741676,
"version": "1.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
}
]

View File

@@ -0,0 +1,26 @@
<!--
changelogUtils.file is auto-generated using the monorepo-scripts package. Don't edit directly.
Edit the package's CHANGELOG.json file only.
-->
CHANGELOG
## v1.0.6 - _January 17, 2019_
* Dependencies updated
## v1.0.5 - _January 15, 2019_
* Dependencies updated
## v1.0.4 - _January 11, 2019_
* Dependencies updated
## v1.0.3 - _January 9, 2019_
* Dependencies updated
## v1.0.2 - _December 13, 2018_
* Dependencies updated

View File

@@ -0,0 +1,16 @@
[
{
"name": "ZRXToken",
"version": "1.0.0",
"changes": [
{
"note": "protocol v1 deploy",
"networks": {
"1": "0xe41d2489571d322189246dafa5ebde1f4699f498",
"3": "0xff67881f8d12f372d91baae9752eb3631ff0ed00",
"42": "0x2002d3812f58e35f0ea1ffbf80a75a38c32175fa"
}
}
]
}
]

View File

@@ -0,0 +1,74 @@
## Token contracts
Token smart contracts that are used in the 0x protocol. Addresses of the deployed contracts can be found in the 0x [wiki](https://0xproject.com/wiki#Deployed-Addresses) or the [CHANGELOG](./CHANGELOG.json) of this package.
## Usage
Token contracts that make up and interact with version 2.0.0 of the protocol can be found in the [contracts](./contracts) directory. The contents of this directory are broken down into the following subdirectories:
- [tokens](./contracts/tokens)
- This directory contains implementations of different tokens and token standards, including [wETH](https://weth.io/), ZRX, [ERC20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md), and [ERC721](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md).
- [test](./contracts/test)
- This directory contains mocks and other contracts that are used solely for testing contracts within the other directories.
## Bug bounty
A bug bounty for the 2.0.0 contracts is ongoing! Instructions can be found [here](https://0xproject.com/wiki#Bug-Bounty).
## Contributing
We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository.
For proposals regarding the 0x protocol's smart contract architecture, message format, or additional functionality, go to the [0x Improvement Proposals (ZEIPs)](https://github.com/0xProject/ZEIPs) repository and follow the contribution guidelines provided therein.
Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
### Install Dependencies
If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
```bash
yarn config set workspaces-experimental true
```
Then install dependencies
```bash
yarn install
```
### Build
To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory:
```bash
PKG=@0x/contracts-tokens yarn build
```
Or continuously rebuild on change:
```bash
PKG=@0x/contracts-tokens yarn watch
```
### Clean
```bash
yarn clean
```
### Lint
```bash
yarn lint
```
### Run Tests
```bash
yarn test
```
#### Testing options
Contracts testing options like coverage, profiling, revert traces or backing node choosing - are described [here](../TESTING.md).

View File

@@ -0,0 +1,30 @@
{
"artifactsDir": "./generated-artifacts",
"contractsDir": "./contracts",
"compilerSettings": {
"optimizer": {
"enabled": true,
"runs": 1000000
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode.object",
"evm.bytecode.sourceMap",
"evm.deployedBytecode.object",
"evm.deployedBytecode.sourceMap"
]
}
}
},
"contracts": [
"DummyERC721Receiver",
"InvalidERC721Receiver",
"DummyERC721Token",
"ERC721Token",
"IERC721Receiver",
"IERC721Token",
"MintableERC721Token"
]
}

View File

@@ -0,0 +1,277 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.4.24;
import "./interfaces/IERC721Token.sol";
import "./interfaces/IERC721Receiver.sol";
import "@0x/contracts-utils/contracts/src/SafeMath.sol";
contract ERC721Token is
IERC721Token,
SafeMath
{
// Function selector for ERC721Receiver.onERC721Received
// 0x150b7a02
bytes4 constant internal ERC721_RECEIVED = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
// Mapping of tokenId => owner
mapping (uint256 => address) internal owners;
// Mapping of tokenId => approved address
mapping (uint256 => address) internal approvals;
// Mapping of owner => number of tokens owned
mapping (address => uint256) internal balances;
// Mapping of owner => operator => approved
mapping (address => mapping (address => bool)) internal operatorApprovals;
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT. When transfer is complete, this function
/// checks if `_to` is a smart contract (code size > 0). If so, it calls
/// `onERC721Received` on `_to` and throws if the return value is not
/// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
/// @param _data Additional data with no specified format, sent in call to `_to`
function safeTransferFrom(
address _from,
address _to,
uint256 _tokenId,
bytes _data
)
external
{
transferFrom(
_from,
_to,
_tokenId
);
uint256 receiverCodeSize;
assembly {
receiverCodeSize := extcodesize(_to)
}
if (receiverCodeSize > 0) {
bytes4 selector = IERC721Receiver(_to).onERC721Received(
msg.sender,
_from,
_tokenId,
_data
);
require(
selector == ERC721_RECEIVED,
"ERC721_INVALID_SELECTOR"
);
}
}
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev This works identically to the other function with an extra data parameter,
/// except this function just sets data to "".
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function safeTransferFrom(
address _from,
address _to,
uint256 _tokenId
)
external
{
transferFrom(
_from,
_to,
_tokenId
);
uint256 receiverCodeSize;
assembly {
receiverCodeSize := extcodesize(_to)
}
if (receiverCodeSize > 0) {
bytes4 selector = IERC721Receiver(_to).onERC721Received(
msg.sender,
_from,
_tokenId,
""
);
require(
selector == ERC721_RECEIVED,
"ERC721_INVALID_SELECTOR"
);
}
}
/// @notice Change or reaffirm the approved address for an NFT
/// @dev The zero address indicates there is no approved address.
/// Throws unless `msg.sender` is the current NFT owner, or an authorized
/// operator of the current owner.
/// @param _approved The new approved NFT controller
/// @param _tokenId The NFT to approve
function approve(address _approved, uint256 _tokenId)
external
{
address owner = ownerOf(_tokenId);
require(
msg.sender == owner || isApprovedForAll(owner, msg.sender),
"ERC721_INVALID_SENDER"
);
approvals[_tokenId] = _approved;
emit Approval(
owner,
_approved,
_tokenId
);
}
/// @notice Enable or disable approval for a third party ("operator") to manage
/// all of `msg.sender`'s assets
/// @dev Emits the ApprovalForAll event. The contract MUST allow
/// multiple operators per owner.
/// @param _operator Address to add to the set of authorized operators
/// @param _approved True if the operator is approved, false to revoke approval
function setApprovalForAll(address _operator, bool _approved)
external
{
operatorApprovals[msg.sender][_operator] = _approved;
emit ApprovalForAll(
msg.sender,
_operator,
_approved
);
}
/// @notice Count all NFTs assigned to an owner
/// @dev NFTs assigned to the zero address are considered invalid, and this
/// function throws for queries about the zero address.
/// @param _owner An address for whom to query the balance
/// @return The number of NFTs owned by `_owner`, possibly zero
function balanceOf(address _owner)
external
view
returns (uint256)
{
require(
_owner != address(0),
"ERC721_ZERO_OWNER"
);
return balances[_owner];
}
/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
/// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
/// THEY MAY BE PERMANENTLY LOST
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function transferFrom(
address _from,
address _to,
uint256 _tokenId
)
public
{
require(
_to != address(0),
"ERC721_ZERO_TO_ADDRESS"
);
address owner = ownerOf(_tokenId);
require(
_from == owner,
"ERC721_OWNER_MISMATCH"
);
address spender = msg.sender;
address approvedAddress = getApproved(_tokenId);
require(
spender == owner ||
isApprovedForAll(owner, spender) ||
approvedAddress == spender,
"ERC721_INVALID_SPENDER"
);
if (approvedAddress != address(0)) {
approvals[_tokenId] = address(0);
}
owners[_tokenId] = _to;
balances[_from] = safeSub(balances[_from], 1);
balances[_to] = safeAdd(balances[_to], 1);
emit Transfer(
_from,
_to,
_tokenId
);
}
/// @notice Find the owner of an NFT
/// @dev NFTs assigned to zero address are considered invalid, and queries
/// about them do throw.
/// @param _tokenId The identifier for an NFT
/// @return The address of the owner of the NFT
function ownerOf(uint256 _tokenId)
public
view
returns (address)
{
address owner = owners[_tokenId];
require(
owner != address(0),
"ERC721_ZERO_OWNER"
);
return owner;
}
/// @notice Get the approved address for a single NFT
/// @dev Throws if `_tokenId` is not a valid NFT.
/// @param _tokenId The NFT to find the approved address for
/// @return The approved address for this NFT, or the zero address if there is none
function getApproved(uint256 _tokenId)
public
view
returns (address)
{
return approvals[_tokenId];
}
/// @notice Query if an address is an authorized operator for another address
/// @param _owner The address that owns the NFTs
/// @param _operator The address that acts on behalf of the owner
/// @return True if `_operator` is an approved operator for `_owner`, false otherwise
function isApprovedForAll(address _owner, address _operator)
public
view
returns (bool)
{
return operatorApprovals[_owner][_operator];
}
}

View File

@@ -0,0 +1,82 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.4.24;
import "./ERC721Token.sol";
contract MintableERC721Token is
ERC721Token
{
/// @dev Function to mint a new token
/// Reverts if the given token ID already exists
/// @param _to Address of the beneficiary that will own the minted token
/// @param _tokenId ID of the token to be minted by the msg.sender
function _mint(address _to, uint256 _tokenId)
internal
{
require(
_to != address(0),
"ERC721_ZERO_TO_ADDRESS"
);
address owner = owners[_tokenId];
require(
owner == address(0),
"ERC721_OWNER_ALREADY_EXISTS"
);
owners[_tokenId] = _to;
balances[_to] = safeAdd(balances[_to], 1);
emit Transfer(
address(0),
_to,
_tokenId
);
}
/// @dev Function to burn a token
/// Reverts if the given token ID doesn't exist
/// @param _owner Owner of token with given token ID
/// @param _tokenId ID of the token to be burned by the msg.sender
function _burn(address _owner, uint256 _tokenId)
internal
{
require(
_owner != address(0),
"ERC721_ZERO_OWNER_ADDRESS"
);
address owner = owners[_tokenId];
require(
owner == _owner,
"ERC721_OWNER_MISMATCH"
);
owners[_tokenId] = address(0);
balances[_owner] = safeSub(balances[_owner], 1);
emit Transfer(
_owner,
address(0),
_tokenId
);
}
}

View File

@@ -0,0 +1,44 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.4.24;
contract IERC721Receiver {
/// @notice Handle the receipt of an NFT
/// @dev The ERC721 smart contract calls this function on the recipient
/// after a `transfer`. This function MAY throw to revert and reject the
/// transfer. Return of other than the magic value MUST result in the
/// transaction being reverted.
/// Note: the contract address is always the message sender.
/// @param _operator The address which called `safeTransferFrom` function
/// @param _from The address which previously owned the token
/// @param _tokenId The NFT identifier which is being transferred
/// @param _data Additional data with no specified format
/// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
/// unless throwing
function onERC721Received(
address _operator,
address _from,
uint256 _tokenId,
bytes _data
)
external
returns (bytes4);
}

View File

@@ -0,0 +1,158 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.4.24;
contract IERC721Token {
/// @dev This emits when ownership of any NFT changes by any mechanism.
/// This event emits when NFTs are created (`from` == 0) and destroyed
/// (`to` == 0). Exception: during contract creation, any number of NFTs
/// may be created and assigned without emitting Transfer. At the time of
/// any transfer, the approved address for that NFT (if any) is reset to none.
event Transfer(
address indexed _from,
address indexed _to,
uint256 indexed _tokenId
);
/// @dev This emits when the approved address for an NFT is changed or
/// reaffirmed. The zero address indicates there is no approved address.
/// When a Transfer event emits, this also indicates that the approved
/// address for that NFT (if any) is reset to none.
event Approval(
address indexed _owner,
address indexed _approved,
uint256 indexed _tokenId
);
/// @dev This emits when an operator is enabled or disabled for an owner.
/// The operator can manage all NFTs of the owner.
event ApprovalForAll(
address indexed _owner,
address indexed _operator,
bool _approved
);
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// perator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT. When transfer is complete, this function
/// checks if `_to` is a smart contract (code size > 0). If so, it calls
/// `onERC721Received` on `_to` and throws if the return value is not
/// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
/// @param _data Additional data with no specified format, sent in call to `_to`
function safeTransferFrom(
address _from,
address _to,
uint256 _tokenId,
bytes _data
)
external;
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev This works identically to the other function with an extra data parameter,
/// except this function just sets data to "".
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function safeTransferFrom(
address _from,
address _to,
uint256 _tokenId
)
external;
/// @notice Change or reaffirm the approved address for an NFT
/// @dev The zero address indicates there is no approved address.
/// Throws unless `msg.sender` is the current NFT owner, or an authorized
/// operator of the current owner.
/// @param _approved The new approved NFT controller
/// @param _tokenId The NFT to approve
function approve(address _approved, uint256 _tokenId)
external;
/// @notice Enable or disable approval for a third party ("operator") to manage
/// all of `msg.sender`'s assets
/// @dev Emits the ApprovalForAll event. The contract MUST allow
/// multiple operators per owner.
/// @param _operator Address to add to the set of authorized operators
/// @param _approved True if the operator is approved, false to revoke approval
function setApprovalForAll(address _operator, bool _approved)
external;
/// @notice Count all NFTs assigned to an owner
/// @dev NFTs assigned to the zero address are considered invalid, and this
/// function throws for queries about the zero address.
/// @param _owner An address for whom to query the balance
/// @return The number of NFTs owned by `_owner`, possibly zero
function balanceOf(address _owner)
external
view
returns (uint256);
/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
/// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
/// THEY MAY BE PERMANENTLY LOST
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function transferFrom(
address _from,
address _to,
uint256 _tokenId
)
public;
/// @notice Find the owner of an NFT
/// @dev NFTs assigned to zero address are considered invalid, and queries
/// about them do throw.
/// @param _tokenId The identifier for an NFT
/// @return The address of the owner of the NFT
function ownerOf(uint256 _tokenId)
public
view
returns (address);
/// @notice Get the approved address for a single NFT
/// @dev Throws if `_tokenId` is not a valid NFT.
/// @param _tokenId The NFT to find the approved address for
/// @return The approved address for this NFT, or the zero address if there is none
function getApproved(uint256 _tokenId)
public
view
returns (address);
/// @notice Query if an address is an authorized operator for another address
/// @param _owner The address that owns the NFTs
/// @param _operator The address that acts on behalf of the owner
/// @return True if `_operator` is an approved operator for `_owner`, false otherwise
function isApprovedForAll(address _owner, address _operator)
public
view
returns (bool);
}

View File

@@ -0,0 +1,67 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.24;
import "../src/interfaces/IERC721Receiver.sol";
contract DummyERC721Receiver is
IERC721Receiver
{
// Function selector for ERC721Receiver.onERC721Received
// 0x150b7a02
bytes4 constant internal ERC721_RECEIVED = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
event TokenReceived(
address operator,
address from,
uint256 tokenId,
bytes data
);
/// @notice Handle the receipt of an NFT
/// @dev The ERC721 smart contract calls this function on the recipient
/// after a `transfer`. This function MAY throw to revert and reject the
/// transfer. Return of other than the magic value MUST result in the
/// transaction being reverted.
/// Note: the contract address is always the message sender.
/// @param _operator The address which called `safeTransferFrom` function
/// @param _from The address which previously owned the token
/// @param _tokenId The NFT identifier which is being transferred
/// @param _data Additional data with no specified format
/// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
/// unless throwing
function onERC721Received(
address _operator,
address _from,
uint256 _tokenId,
bytes _data
)
external
returns (bytes4)
{
emit TokenReceived(
_operator,
_from,
_tokenId,
_data
);
return ERC721_RECEIVED;
}
}

View File

@@ -0,0 +1,63 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.24;
import "../src/MintableERC721Token.sol";
import "@0x/contracts-utils/contracts/src/Ownable.sol";
// solhint-disable no-empty-blocks
contract DummyERC721Token is
Ownable,
MintableERC721Token
{
string public name;
string public symbol;
constructor (
string _name,
string _symbol
)
public
{
name = _name;
symbol = _symbol;
}
/// @dev Function to mint a new token
/// Reverts if the given token ID already exists
/// @param _to Address of the beneficiary that will own the minted token
/// @param _tokenId ID of the token to be minted by the msg.sender
function mint(address _to, uint256 _tokenId)
external
{
_mint(_to, _tokenId);
}
/// @dev Function to burn a token
/// Reverts if the given token ID doesn't exist or not called by contract owner
/// @param _owner Owner of token with given token ID
/// @param _tokenId ID of the token to be burned by the msg.sender
function burn(address _owner, uint256 _tokenId)
external
onlyOwner
{
_burn(_owner, _tokenId);
}
}

View File

@@ -0,0 +1,66 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.24;
import "../src/interfaces/IERC721Receiver.sol";
contract InvalidERC721Receiver is
IERC721Receiver
{
// Actual function signature is `onERC721Received(address,address,uint256,bytes)`
bytes4 constant internal INVALID_ERC721_RECEIVED = bytes4(keccak256("onERC721Received(address,uint256,bytes)"));
event TokenReceived(
address operator,
address from,
uint256 tokenId,
bytes data
);
/// @notice Handle the receipt of an NFT
/// @dev The ERC721 smart contract calls this function on the recipient
/// after a `transfer`. This function MAY throw to revert and reject the
/// transfer. Return of other than the magic value MUST result in the
/// transaction being reverted.
/// Note: the contract address is always the message sender.
/// @param _operator The address which called `safeTransferFrom` function
/// @param _from The address which previously owned the token
/// @param _tokenId The NFT identifier which is being transferred
/// @param _data Additional data with no specified format
/// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
/// unless throwing
function onERC721Received(
address _operator,
address _from,
uint256 _tokenId,
bytes _data
)
external
returns (bytes4)
{
emit TokenReceived(
_operator,
_from,
_tokenId,
_data
);
return INVALID_ERC721_RECEIVED;
}
}

View File

@@ -0,0 +1,89 @@
{
"name": "@0x/contracts-erc721",
"version": "1.0.6",
"engines": {
"node": ">=6.12"
},
"description": "Token contracts used by 0x protocol",
"main": "lib/src/index.js",
"directories": {
"test": "test"
},
"scripts": {
"build": "yarn pre_build && tsc -b",
"build:ci": "yarn build",
"pre_build": "run-s compile generate_contract_wrappers",
"test": "yarn run_mocha",
"rebuild_and_test": "run-s build test",
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
"test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",
"test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha",
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit",
"compile": "sol-compiler",
"watch": "sol-compiler -w",
"clean": "shx rm -rf lib generated-artifacts generated-wrappers",
"generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../../node_modules/@0x/abi-gen-templates/contract.handlebars --partials '../../node_modules/@0x/abi-gen-templates/partials/**/*.handlebars' --output generated-wrappers --backend ethers",
"lint": "tslint --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts",
"coverage:report:text": "istanbul report text",
"coverage:report:html": "istanbul report html && open coverage/index.html",
"profiler:report:html": "istanbul report html && open coverage/index.html",
"coverage:report:lcov": "istanbul report lcov",
"test:circleci": "yarn test",
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
},
"config": {
"abis": "generated-artifacts/@(DummyERC721Receiver|InvalidERC721Receiver|DummyERC721Token|ERC721Token|IERC721Receiver|IERC721Token|MintableERC721Token).json"
},
"repository": {
"type": "git",
"url": "https://github.com/0xProject/0x-monorepo.git"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/0xProject/0x-monorepo/issues"
},
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
"devDependencies": {
"@0x/abi-gen": "^1.0.22",
"@0x/contracts-test-utils": "^2.0.1",
"@0x/dev-utils": "^1.0.24",
"@0x/sol-compiler": "^2.0.2",
"@0x/subproviders": "^2.1.11",
"@0x/tslint-config": "^2.0.2",
"@types/bn.js": "^4.11.0",
"@types/lodash": "4.14.104",
"@types/node": "*",
"@types/yargs": "^10.0.0",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
"chai-bignumber": "^3.0.0",
"dirty-chai": "^2.0.1",
"ethereumjs-abi": "0.6.5",
"make-promises-safe": "^1.1.0",
"mocha": "^4.1.0",
"npm-run-all": "^4.1.2",
"shx": "^0.2.2",
"solhint": "^1.4.1",
"tslint": "5.11.0",
"typescript": "3.0.1",
"yargs": "^10.0.3"
},
"dependencies": {
"@0x/base-contract": "^3.0.13",
"@0x/contracts-exchange-libs": "^1.0.6",
"@0x/contracts-utils": "^1.0.6",
"@0x/order-utils": "^3.1.2",
"@0x/types": "^1.5.2",
"@0x/typescript-typings": "^3.0.8",
"@0x/utils": "^3.0.1",
"@0x/web3-wrapper": "^3.2.4",
"@types/js-combinatorics": "^0.5.29",
"bn.js": "^4.11.8",
"ethereum-types": "^1.1.6",
"ethereumjs-util": "^5.1.1",
"lodash": "^4.17.5"
},
"publishConfig": {
"access": "public"
}
}

View File

@@ -0,0 +1,20 @@
import { ContractArtifact } from 'ethereum-types';
import * as DummyERC721Receiver from '../../generated-artifacts/DummyERC721Receiver.json';
import * as DummyERC721Token from '../../generated-artifacts/DummyERC721Token.json';
import * as ERC721Token from '../../generated-artifacts/ERC721Token.json';
import * as IERC721Receiver from '../../generated-artifacts/IERC721Receiver.json';
import * as IERC721Token from '../../generated-artifacts/IERC721Token.json';
import * as InvalidERC721Receiver from '../../generated-artifacts/InvalidERC721Receiver.json';
import * as MintableERC721Token from '../../generated-artifacts/MintableERC721Token.json';
// tslint:disable:no-unnecessary-type-assertion
export const artifacts = {
DummyERC721Receiver: DummyERC721Receiver as ContractArtifact,
InvalidERC721Receiver: InvalidERC721Receiver as ContractArtifact,
DummyERC721Token: DummyERC721Token as ContractArtifact,
ERC721Token: ERC721Token as ContractArtifact,
IERC721Receiver: IERC721Receiver as ContractArtifact,
IERC721Token: IERC721Token as ContractArtifact,
MintableERC721Token: MintableERC721Token as ContractArtifact,
};

View File

@@ -0,0 +1,2 @@
export * from './wrappers';
export * from './artifacts';

View File

@@ -0,0 +1,7 @@
export * from '../../generated-wrappers/mintable_erc721_token';
export * from '../../generated-wrappers/invalid_erc721_receiver';
export * from '../../generated-wrappers/i_erc721_token';
export * from '../../generated-wrappers/i_erc721_receiver';
export * from '../../generated-wrappers/erc721_token';
export * from '../../generated-wrappers/dummy_erc721_token';
export * from '../../generated-wrappers/dummy_erc721_receiver';

View File

@@ -0,0 +1,282 @@
import {
chaiSetup,
constants,
expectTransactionFailedAsync,
expectTransactionFailedWithoutReasonAsync,
LogDecoder,
provider,
txDefaults,
web3Wrapper,
} from '@0x/contracts-test-utils';
import { BlockchainLifecycle } from '@0x/dev-utils';
import { RevertReason } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as chai from 'chai';
import { LogWithDecodedArgs } from 'ethereum-types';
import {
artifacts,
DummyERC721ReceiverContract,
DummyERC721ReceiverTokenReceivedEventArgs,
DummyERC721TokenContract,
DummyERC721TokenTransferEventArgs,
InvalidERC721ReceiverContract,
} from '../src';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
// tslint:disable:no-unnecessary-type-assertion
describe('ERC721Token', () => {
let owner: string;
let spender: string;
let token: DummyERC721TokenContract;
let erc721Receiver: DummyERC721ReceiverContract;
let logDecoder: LogDecoder;
const tokenId = new BigNumber(1);
before(async () => {
await blockchainLifecycle.startAsync();
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
before(async () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
owner = accounts[0];
spender = accounts[1];
token = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
artifacts.DummyERC721Token,
provider,
txDefaults,
constants.DUMMY_TOKEN_NAME,
constants.DUMMY_TOKEN_SYMBOL,
);
erc721Receiver = await DummyERC721ReceiverContract.deployFrom0xArtifactAsync(
artifacts.DummyERC721Receiver,
provider,
txDefaults,
);
logDecoder = new LogDecoder(web3Wrapper, artifacts);
await web3Wrapper.awaitTransactionSuccessAsync(
await token.mint.sendTransactionAsync(owner, tokenId, { from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('transferFrom', () => {
it('should revert if the tokenId is not owner', async () => {
const from = owner;
const to = erc721Receiver.address;
const unownedTokenId = new BigNumber(2);
await expectTransactionFailedAsync(
token.transferFrom.sendTransactionAsync(from, to, unownedTokenId),
RevertReason.Erc721ZeroOwner,
);
});
it('should revert if transferring to a null address', async () => {
const from = owner;
const to = constants.NULL_ADDRESS;
await expectTransactionFailedAsync(
token.transferFrom.sendTransactionAsync(from, to, tokenId),
RevertReason.Erc721ZeroToAddress,
);
});
it('should revert if the from address does not own the token', async () => {
const from = spender;
const to = erc721Receiver.address;
await expectTransactionFailedAsync(
token.transferFrom.sendTransactionAsync(from, to, tokenId),
RevertReason.Erc721OwnerMismatch,
);
});
it('should revert if spender does not own the token, is not approved, and is not approved for all', async () => {
const from = owner;
const to = erc721Receiver.address;
await expectTransactionFailedAsync(
token.transferFrom.sendTransactionAsync(from, to, tokenId, { from: spender }),
RevertReason.Erc721InvalidSpender,
);
});
it('should transfer the token if called by owner', async () => {
const from = owner;
const to = erc721Receiver.address;
const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
await token.transferFrom.sendTransactionAsync(from, to, tokenId),
);
const newOwner = await token.ownerOf.callAsync(tokenId);
expect(newOwner).to.be.equal(to);
const log = txReceipt.logs[0] as LogWithDecodedArgs<DummyERC721TokenTransferEventArgs>;
expect(log.args._from).to.be.equal(from);
expect(log.args._to).to.be.equal(to);
expect(log.args._tokenId).to.be.bignumber.equal(tokenId);
});
it('should transfer the token if spender is approved for all', async () => {
const isApproved = true;
await web3Wrapper.awaitTransactionSuccessAsync(
await token.setApprovalForAll.sendTransactionAsync(spender, isApproved),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const from = owner;
const to = erc721Receiver.address;
const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
await token.transferFrom.sendTransactionAsync(from, to, tokenId),
);
const newOwner = await token.ownerOf.callAsync(tokenId);
expect(newOwner).to.be.equal(to);
const log = txReceipt.logs[0] as LogWithDecodedArgs<DummyERC721TokenTransferEventArgs>;
expect(log.args._from).to.be.equal(from);
expect(log.args._to).to.be.equal(to);
expect(log.args._tokenId).to.be.bignumber.equal(tokenId);
});
it('should transfer the token if spender is individually approved', async () => {
await web3Wrapper.awaitTransactionSuccessAsync(
await token.approve.sendTransactionAsync(spender, tokenId),
constants.AWAIT_TRANSACTION_MINED_MS,
);
const from = owner;
const to = erc721Receiver.address;
const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
await token.transferFrom.sendTransactionAsync(from, to, tokenId),
);
const newOwner = await token.ownerOf.callAsync(tokenId);
expect(newOwner).to.be.equal(to);
const approvedAddress = await token.getApproved.callAsync(tokenId);
expect(approvedAddress).to.be.equal(constants.NULL_ADDRESS);
const log = txReceipt.logs[0] as LogWithDecodedArgs<DummyERC721TokenTransferEventArgs>;
expect(log.args._from).to.be.equal(from);
expect(log.args._to).to.be.equal(to);
expect(log.args._tokenId).to.be.bignumber.equal(tokenId);
});
});
describe('safeTransferFrom without data', () => {
it('should transfer token to a non-contract address if called by owner', async () => {
const from = owner;
const to = spender;
const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
await token.safeTransferFrom1.sendTransactionAsync(from, to, tokenId),
);
const newOwner = await token.ownerOf.callAsync(tokenId);
expect(newOwner).to.be.equal(to);
const log = txReceipt.logs[0] as LogWithDecodedArgs<DummyERC721TokenTransferEventArgs>;
expect(log.args._from).to.be.equal(from);
expect(log.args._to).to.be.equal(to);
expect(log.args._tokenId).to.be.bignumber.equal(tokenId);
});
it('should revert if transferring to a contract address without onERC721Received', async () => {
const contract = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
artifacts.DummyERC721Token,
provider,
txDefaults,
constants.DUMMY_TOKEN_NAME,
constants.DUMMY_TOKEN_SYMBOL,
);
const from = owner;
const to = contract.address;
await expectTransactionFailedWithoutReasonAsync(
token.safeTransferFrom1.sendTransactionAsync(from, to, tokenId),
);
});
it('should revert if onERC721Received does not return the correct value', async () => {
const invalidErc721Receiver = await InvalidERC721ReceiverContract.deployFrom0xArtifactAsync(
artifacts.InvalidERC721Receiver,
provider,
txDefaults,
);
const from = owner;
const to = invalidErc721Receiver.address;
await expectTransactionFailedAsync(
token.safeTransferFrom1.sendTransactionAsync(from, to, tokenId),
RevertReason.Erc721InvalidSelector,
);
});
it('should transfer to contract and call onERC721Received with correct return value', async () => {
const from = owner;
const to = erc721Receiver.address;
const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
await token.safeTransferFrom1.sendTransactionAsync(from, to, tokenId),
);
const newOwner = await token.ownerOf.callAsync(tokenId);
expect(newOwner).to.be.equal(to);
const transferLog = txReceipt.logs[0] as LogWithDecodedArgs<DummyERC721TokenTransferEventArgs>;
const receiverLog = txReceipt.logs[1] as LogWithDecodedArgs<DummyERC721ReceiverTokenReceivedEventArgs>;
expect(transferLog.args._from).to.be.equal(from);
expect(transferLog.args._to).to.be.equal(to);
expect(transferLog.args._tokenId).to.be.bignumber.equal(tokenId);
expect(receiverLog.args.operator).to.be.equal(owner);
expect(receiverLog.args.from).to.be.equal(from);
expect(receiverLog.args.tokenId).to.be.bignumber.equal(tokenId);
expect(receiverLog.args.data).to.be.equal(constants.NULL_BYTES);
});
});
describe('safeTransferFrom with data', () => {
const data = '0x0102030405060708090a0b0c0d0e0f';
it('should transfer token to a non-contract address if called by owner', async () => {
const from = owner;
const to = spender;
const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
await token.safeTransferFrom2.sendTransactionAsync(from, to, tokenId, data),
);
const newOwner = await token.ownerOf.callAsync(tokenId);
expect(newOwner).to.be.equal(to);
const log = txReceipt.logs[0] as LogWithDecodedArgs<DummyERC721TokenTransferEventArgs>;
expect(log.args._from).to.be.equal(from);
expect(log.args._to).to.be.equal(to);
expect(log.args._tokenId).to.be.bignumber.equal(tokenId);
});
it('should revert if transferring to a contract address without onERC721Received', async () => {
const contract = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
artifacts.DummyERC721Token,
provider,
txDefaults,
constants.DUMMY_TOKEN_NAME,
constants.DUMMY_TOKEN_SYMBOL,
);
const from = owner;
const to = contract.address;
await expectTransactionFailedWithoutReasonAsync(
token.safeTransferFrom2.sendTransactionAsync(from, to, tokenId, data),
);
});
it('should revert if onERC721Received does not return the correct value', async () => {
const invalidErc721Receiver = await InvalidERC721ReceiverContract.deployFrom0xArtifactAsync(
artifacts.InvalidERC721Receiver,
provider,
txDefaults,
);
const from = owner;
const to = invalidErc721Receiver.address;
await expectTransactionFailedAsync(
token.safeTransferFrom2.sendTransactionAsync(from, to, tokenId, data),
RevertReason.Erc721InvalidSelector,
);
});
it('should transfer to contract and call onERC721Received with correct return value', async () => {
const from = owner;
const to = erc721Receiver.address;
const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
await token.safeTransferFrom2.sendTransactionAsync(from, to, tokenId, data),
);
const newOwner = await token.ownerOf.callAsync(tokenId);
expect(newOwner).to.be.equal(to);
const transferLog = txReceipt.logs[0] as LogWithDecodedArgs<DummyERC721TokenTransferEventArgs>;
const receiverLog = txReceipt.logs[1] as LogWithDecodedArgs<DummyERC721ReceiverTokenReceivedEventArgs>;
expect(transferLog.args._from).to.be.equal(from);
expect(transferLog.args._to).to.be.equal(to);
expect(transferLog.args._tokenId).to.be.bignumber.equal(tokenId);
expect(receiverLog.args.operator).to.be.equal(owner);
expect(receiverLog.args.from).to.be.equal(from);
expect(receiverLog.args.tokenId).to.be.bignumber.equal(tokenId);
expect(receiverLog.args.data).to.be.equal(data);
});
});
});
// tslint:enable:no-unnecessary-type-assertion

View File

@@ -0,0 +1,17 @@
import { env, EnvVars } from '@0x/dev-utils';
import { coverage, profiler, provider } from '@0x/contracts-test-utils';
before('start web3 provider', () => {
provider.start();
});
after('generate coverage report', async () => {
if (env.parseBoolean(EnvVars.SolidityCoverage)) {
const coverageSubprovider = coverage.getCoverageSubproviderSingleton();
await coverageSubprovider.writeCoverageAsync();
}
if (env.parseBoolean(EnvVars.SolidityProfiler)) {
const profilerSubprovider = profiler.getProfilerSubproviderSingleton();
await profilerSubprovider.writeProfilerOutputAsync();
}
provider.stop();
});

View File

@@ -0,0 +1,19 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "lib",
"rootDir": ".",
"resolveJsonModule": true
},
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": [
"./generated-artifacts/DummyERC721Receiver.json",
"./generated-artifacts/InvalidERC721Receiver.json",
"./generated-artifacts/DummyERC721Token.json",
"./generated-artifacts/ERC721Token.json",
"./generated-artifacts/IERC721Receiver.json",
"./generated-artifacts/IERC721Token.json",
"./generated-artifacts/MintableERC721Token.json"
],
"exclude": ["./deploy/solc/solc_bin"]
}

View File

@@ -0,0 +1,6 @@
{
"extends": ["@0x/tslint-config"],
"rules": {
"custom-no-magic-numbers": false
}
}