diff --git a/contracts/introspection/ERC165.sol b/contracts/introspection/ERC165.sol new file mode 100644 index 000000000..bed328b99 --- /dev/null +++ b/contracts/introspection/ERC165.sol @@ -0,0 +1,20 @@ +pragma solidity ^0.4.23; + + +/** + * @title ERC165 + * @dev https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md + */ +interface ERC165 { + + /** + * @notice Query if a contract implements an interface + * @param _interfaceId The interface identifier, as specified in ERC-165 + * @dev Interface identification is specified in ERC-165. This function + * @dev uses less than 30,000 gas. + */ + function supportsInterface(bytes4 _interfaceId) + external + view + returns (bool); +} diff --git a/contracts/introspection/SupportsInterfaceWithLookup.sol b/contracts/introspection/SupportsInterfaceWithLookup.sol new file mode 100644 index 000000000..8fda9330c --- /dev/null +++ b/contracts/introspection/SupportsInterfaceWithLookup.sol @@ -0,0 +1,53 @@ +pragma solidity ^0.4.23; + +import "./ERC165.sol"; + + +/** + * @title SupportsInterfaceWithLookup + * @author Matt Condon (@shrugs) + * @dev Implements ERC165 using a lookup table. + */ +contract SupportsInterfaceWithLookup is ERC165 { + bytes4 public constant InterfaceId_ERC165 = 0x01ffc9a7; + /** + * 0x01ffc9a7 === + * bytes4(keccak256('supportsInterface(bytes4)')) + */ + + /** + * @dev a mapping of interface id to whether or not it's supported + */ + mapping(bytes4 => bool) internal supportedInterfaces; + + /** + * @dev A contract implementing SupportsInterfaceWithLookup + * @dev implement ERC165 itself + */ + constructor() + public + { + _registerInterface(InterfaceId_ERC165); + } + + /** + * @dev implement supportsInterface(bytes4) using a lookup table + */ + function supportsInterface(bytes4 _interfaceId) + external + view + returns (bool) + { + return supportedInterfaces[_interfaceId]; + } + + /** + * @dev private method for registering an interface + */ + function _registerInterface(bytes4 _interfaceId) + internal + { + require(_interfaceId != 0xffffffff); + supportedInterfaces[_interfaceId] = true; + } +} diff --git a/contracts/mocks/SupportsInterfaceWithLookupMock.sol b/contracts/mocks/SupportsInterfaceWithLookupMock.sol new file mode 100644 index 000000000..3bdee76b1 --- /dev/null +++ b/contracts/mocks/SupportsInterfaceWithLookupMock.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.4.23; + +import "../introspection/SupportsInterfaceWithLookup.sol"; + + +contract SupportsInterfaceWithLookupMock is SupportsInterfaceWithLookup { + function registerInterface(bytes4 _interfaceId) + public + { + _registerInterface(_interfaceId); + } +} diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index 0a56c66a0..524ce00ec 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -8,6 +8,21 @@ import "./ERC721Basic.sol"; * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721Enumerable is ERC721Basic { + + bytes4 private constant InterfaceId_ERC721Enumerable = 0x780e9d63; + /** + * 0x780e9d63 === + * bytes4(keccak256('totalSupply()')) ^ + * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) ^ + * bytes4(keccak256('tokenByIndex(uint256)')) + */ + + constructor() + public + { + _registerInterface(InterfaceId_ERC721Enumerable); + } + function totalSupply() public view returns (uint256); function tokenOfOwnerByIndex( address _owner, @@ -26,8 +41,23 @@ contract ERC721Enumerable is ERC721Basic { * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721Metadata is ERC721Basic { - function name() public view returns (string _name); - function symbol() public view returns (string _symbol); + + bytes4 private constant InterfaceId_ERC721Metadata = 0x5b5e139f; + /** + * 0x5b5e139f === + * bytes4(keccak256('name()')) ^ + * bytes4(keccak256('symbol()')) ^ + * bytes4(keccak256('tokenURI(uint256)')) + */ + + constructor() + public + { + _registerInterface(InterfaceId_ERC721Metadata); + } + + function name() external view returns (string _name); + function symbol() external view returns (string _symbol); function tokenURI(uint256 _tokenId) public view returns (string); } diff --git a/contracts/token/ERC721/ERC721Basic.sol b/contracts/token/ERC721/ERC721Basic.sol index c61938767..e7d60c9ea 100644 --- a/contracts/token/ERC721/ERC721Basic.sol +++ b/contracts/token/ERC721/ERC721Basic.sol @@ -1,20 +1,22 @@ pragma solidity ^0.4.23; +import "../../introspection/SupportsInterfaceWithLookup.sol"; + /** * @title ERC721 Non-Fungible Token Standard basic interface * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ -contract ERC721Basic { +contract ERC721Basic is SupportsInterfaceWithLookup { event Transfer( address indexed _from, address indexed _to, - uint256 _tokenId + uint256 indexed _tokenId ); event Approval( address indexed _owner, address indexed _approved, - uint256 _tokenId + uint256 indexed _tokenId ); event ApprovalForAll( address indexed _owner, @@ -22,6 +24,34 @@ contract ERC721Basic { bool _approved ); + bytes4 private constant InterfaceId_ERC721 = 0x80ac58cd; + /* + * 0x80ac58cd === + * bytes4(keccak256('balanceOf(address)')) ^ + * bytes4(keccak256('ownerOf(uint256)')) ^ + * bytes4(keccak256('approve(address,uint256)')) ^ + * bytes4(keccak256('getApproved(uint256)')) ^ + * bytes4(keccak256('setApprovalForAll(address,bool)')) ^ + * bytes4(keccak256('isApprovedForAll(address,address)')) ^ + * bytes4(keccak256('transferFrom(address,address,uint256)')) ^ + * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) ^ + * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) + */ + + bytes4 private constant InterfaceId_ERC721Exists = 0x4f558e79; + /* + * 0x4f558e79 === + * bytes4(keccak256('exists(uint256)')) + */ + + constructor () + public + { + // register the supported interfaces to conform to ERC721 via ERC165 + _registerInterface(InterfaceId_ERC721); + _registerInterface(InterfaceId_ERC721Exists); + } + function balanceOf(address _owner) public view returns (uint256 _balance); function ownerOf(uint256 _tokenId) public view returns (address _owner); function exists(uint256 _tokenId) public view returns (bool _exists); diff --git a/contracts/token/ERC721/ERC721BasicToken.sol b/contracts/token/ERC721/ERC721BasicToken.sol index 75480514d..f0aae755c 100644 --- a/contracts/token/ERC721/ERC721BasicToken.sol +++ b/contracts/token/ERC721/ERC721BasicToken.sol @@ -16,7 +16,7 @@ contract ERC721BasicToken is ERC721Basic { // Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))` // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` - bytes4 constant ERC721_RECEIVED = 0xf0b9e5ba; + bytes4 private constant ERC721_RECEIVED = 0xf0b9e5ba; // Mapping from token ID to owner mapping (uint256 => address) internal tokenOwner; diff --git a/contracts/token/ERC721/ERC721Receiver.sol b/contracts/token/ERC721/ERC721Receiver.sol index 851fe7270..851bcb9c4 100644 --- a/contracts/token/ERC721/ERC721Receiver.sol +++ b/contracts/token/ERC721/ERC721Receiver.sol @@ -12,7 +12,7 @@ contract ERC721Receiver { * Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`, * which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` */ - bytes4 constant ERC721_RECEIVED = 0xf0b9e5ba; + bytes4 internal constant ERC721_RECEIVED = 0xf0b9e5ba; /** * @notice Handle the receipt of an NFT diff --git a/contracts/token/ERC721/ERC721Token.sol b/contracts/token/ERC721/ERC721Token.sol index dc0876c51..cee28ce2c 100644 --- a/contracts/token/ERC721/ERC721Token.sol +++ b/contracts/token/ERC721/ERC721Token.sol @@ -44,7 +44,7 @@ contract ERC721Token is ERC721, ERC721BasicToken { * @dev Gets the token name * @return string representing the token name */ - function name() public view returns (string) { + function name() external view returns (string) { return name_; } @@ -52,7 +52,7 @@ contract ERC721Token is ERC721, ERC721BasicToken { * @dev Gets the token symbol * @return string representing the token symbol */ - function symbol() public view returns (string) { + function symbol() external view returns (string) { return symbol_; } diff --git a/package-lock.json b/package-lock.json index 1247f54f1..d920e6d5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,33 @@ "xtend": "~4.0.0" } }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "2.1.18", + "negotiator": "0.6.1" + }, + "dependencies": { + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "requires": { + "mime-db": "1.33.0" + } + } + } + }, "acorn-jsx": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", @@ -163,6 +190,12 @@ "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", "dev": true }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -246,6 +279,12 @@ } } }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1171,6 +1210,50 @@ "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", "dev": true }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "1.0.4", + "debug": "2.6.9", + "depd": "1.1.2", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "1.6.16" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": "2.1.2" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + } + } + }, "boom": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", @@ -1260,6 +1343,12 @@ "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", "dev": true }, + "buffer-to-arraybuffer": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", + "integrity": "sha1-YGSkD6dutDxyOrqe+PbhIW0QURo=", + "dev": true + }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -1272,6 +1361,12 @@ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, "cacheable-request": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", @@ -1673,12 +1768,36 @@ "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", "dev": true }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, "convert-source-map": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=", "dev": true }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, "core-js": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.0.tgz", @@ -1691,6 +1810,16 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "cors": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", + "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "dev": true, + "requires": { + "object-assign": "4.1.1", + "vary": "1.1.2" + } + }, "coveralls": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-2.13.1.tgz", @@ -1942,6 +2071,18 @@ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, "detect-conflict": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/detect-conflict/-/detect-conflict-1.0.1.tgz", @@ -2017,6 +2158,12 @@ "integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==", "dev": true }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, "ejs": { "version": "2.5.8", "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.8.tgz", @@ -2050,6 +2197,12 @@ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", "dev": true }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, "encoding": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", @@ -2134,6 +2287,12 @@ "is-symbol": "^1.0.1" } }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -2545,6 +2704,27 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eth-lib": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.27.tgz", + "integrity": "sha512-B8czsfkJYzn2UIEMwjc7Mbj+Cy72V+/OXH/tb44LV8jhrjizQJJ325xMOMyk3+ETa6r6oi0jsUY14+om8mQMWA==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "elliptic": "6.4.0", + "keccakjs": "0.2.1", + "nano-json-stream-parser": "0.1.2", + "servify": "0.1.12", + "ws": "3.3.3", + "xhr-request-promise": "0.1.2" + } + }, "ethereum-common": { "version": "0.0.16", "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.16.tgz", @@ -2740,6 +2920,24 @@ } } }, + "ethjs-unit": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", + "integrity": "sha1-xmWSHkduh7ziqdWIpv4EBbLEFpk=", + "dev": true, + "requires": { + "bn.js": "4.11.6", + "number-to-bn": "1.7.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=", + "dev": true + } + } + }, "ethjs-util": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.4.tgz", @@ -2813,6 +3011,129 @@ "homedir-polyfill": "^1.0.1" } }, + "express": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", + "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "dev": true, + "requires": { + "accepts": "1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "1.1.2", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "1.1.2", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "2.0.3", + "qs": "6.5.1", + "range-parser": "1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "1.4.0", + "type-is": "1.6.16", + "utils-merge": "1.0.1", + "vary": "1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "1.0.4", + "debug": "2.6.9", + "depd": "1.1.2", + "http-errors": "1.6.3", + "iconv-lite": "0.4.19", + "on-finished": "2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "1.6.16" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "dev": true, + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": "1.4.0" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true + } + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + } + } + }, "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", @@ -2910,6 +3231,38 @@ "repeat-string": "^1.5.2" } }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.4.0", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + } + } + }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -2994,6 +3347,18 @@ "mime-types": "^2.1.12" } }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -4098,6 +4463,18 @@ "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", "dev": true }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": "1.5.0" + } + }, "http-signature": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", @@ -4315,6 +4692,12 @@ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "dev": true }, + "ipaddr.js": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", + "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=", + "dev": true + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -5485,6 +5868,12 @@ } } }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, "mem": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", @@ -5604,6 +5993,12 @@ "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", "dev": true }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, "merkle-patricia-tree": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-2.1.2.tgz", @@ -5635,6 +6030,12 @@ } } }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, "micromatch": { "version": "2.3.11", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", @@ -5656,6 +6057,12 @@ "regex-cache": "^0.4.2" } }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true + }, "mime-db": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz", @@ -5831,12 +6238,24 @@ "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", "dev": true }, + "nano-json-stream-parser": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz", + "integrity": "sha1-DMj20OK2IrR5xA1JnEbWS3Vcb18=", + "dev": true + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, "neo-async": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.0.tgz", @@ -6032,6 +6451,15 @@ "is-extendable": "^0.1.1" } }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6254,6 +6682,12 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", @@ -6287,6 +6721,12 @@ "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", "dev": true }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", @@ -6452,6 +6892,16 @@ "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", "dev": true }, + "proxy-addr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", + "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", + "dev": true, + "requires": { + "forwarded": "0.1.2", + "ipaddr.js": "1.6.0" + } + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -6547,6 +6997,41 @@ "safe-buffer": "^5.1.0" } }, + "randomhex": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/randomhex/-/randomhex-0.1.5.tgz", + "integrity": "sha1-us7vmCMpCRQA8qKRLGzQLxCU9YU=", + "dev": true + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": "2.1.2" + } + } + } + }, "rc": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", @@ -6992,6 +7477,12 @@ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "scoped-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-1.0.0.tgz", @@ -7055,6 +7546,69 @@ "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", "dev": true }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "1.1.2", + "destroy": "1.0.4", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "fresh": "0.5.2", + "http-errors": "1.6.3", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "2.3.0", + "range-parser": "1.2.0", + "statuses": "1.4.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dev": true, + "requires": { + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "parseurl": "1.3.2", + "send": "0.16.2" + } + }, + "servify": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/servify/-/servify-0.1.12.tgz", + "integrity": "sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw==", + "dev": true, + "requires": { + "body-parser": "1.18.3", + "cors": "2.8.4", + "express": "4.16.3", + "request": "2.79.0", + "xhr": "2.4.0" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -7067,6 +7621,12 @@ "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", "dev": true }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, "sha.js": { "version": "2.4.8", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.8.tgz", @@ -7123,6 +7683,12 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "simple-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=", + "dev": true + }, "simple-get": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-1.4.3.tgz", @@ -7549,6 +8115,12 @@ } } }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, "stream-to-observable": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.2.0.tgz", @@ -8197,6 +8769,33 @@ "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=", "dev": true }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.18" + }, + "dependencies": { + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "requires": { + "mime-db": "1.33.0" + } + } + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -8222,6 +8821,12 @@ "dev": true, "optional": true }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "dev": true + }, "underscore": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", @@ -8234,6 +8839,12 @@ "integrity": "sha1-NkIA1fE2RsqLzURJAnEzVhR5IwA=", "dev": true }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, "untildify": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.2.tgz", @@ -8255,6 +8866,12 @@ "prepend-http": "^2.0.0" } }, + "url-set-query": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-set-query/-/url-set-query-1.0.0.tgz", + "integrity": "sha1-AW6M/Xwg7gXK/neV6JK9BwL6ozk=", + "dev": true + }, "url-to-options": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", @@ -8273,6 +8890,12 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, "uuid": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", @@ -8295,6 +8918,12 @@ "spdx-expression-parse": "~1.0.0" } }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -8360,6 +8989,41 @@ "xmlhttprequest": "*" } }, + "web3-utils": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.0.0-beta.34.tgz", + "integrity": "sha1-lBH8OarvOcpOBhafdiKX2f8CCXA=", + "dev": true, + "requires": { + "bn.js": "4.11.6", + "eth-lib": "0.1.27", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randomhex": "0.1.5", + "underscore": "1.8.3", + "utf8": "2.1.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=", + "dev": true + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + }, + "utf8": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.1.tgz", + "integrity": "sha1-LgHbAvfY0JRPdxBPFgnrDDBM92g=", + "dev": true + } + } + }, "webpack-addons": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/webpack-addons/-/webpack-addons-1.1.5.tgz", @@ -8752,6 +9416,17 @@ "slide": "^1.1.5" } }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "1.0.0", + "safe-buffer": "5.1.1", + "ultron": "1.1.1" + } + }, "xhr": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.4.0.tgz", @@ -8764,6 +9439,43 @@ "xtend": "^4.0.0" } }, + "xhr-request": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xhr-request/-/xhr-request-1.1.0.tgz", + "integrity": "sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA==", + "dev": true, + "requires": { + "buffer-to-arraybuffer": "0.0.5", + "object-assign": "4.1.1", + "query-string": "5.1.1", + "simple-get": "2.8.1", + "timed-out": "4.0.1", + "url-set-query": "1.0.0", + "xhr": "2.4.0" + }, + "dependencies": { + "simple-get": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", + "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", + "dev": true, + "requires": { + "decompress-response": "3.3.0", + "once": "1.4.0", + "simple-concat": "1.0.0" + } + } + } + }, + "xhr-request-promise": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/xhr-request-promise/-/xhr-request-promise-0.1.2.tgz", + "integrity": "sha1-NDxE0e53JrhkgGloLQ+EDIO0Jh0=", + "dev": true, + "requires": { + "xhr-request": "1.1.0" + } + }, "xhr2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.1.4.tgz", diff --git a/package.json b/package.json index b3212694c..d014c9aea 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "solidity-coverage": "^0.5.0", "solium": "^1.1.7", "truffle": "^4.1.8", - "truffle-hdwallet-provider": "0.0.3" + "truffle-hdwallet-provider": "0.0.3", + "web3-utils": "^1.0.0-beta.34" } } diff --git a/test/helpers/makeInterfaceId.js b/test/helpers/makeInterfaceId.js new file mode 100644 index 000000000..e87db67b9 --- /dev/null +++ b/test/helpers/makeInterfaceId.js @@ -0,0 +1,21 @@ +import { soliditySha3 } from 'web3-utils'; + +const INTERFACE_ID_LENGTH = 4; + +export default (interfaces = []) => { + const interfaceIdBuffer = interfaces + .map(methodSignature => soliditySha3(methodSignature)) // keccak256 + .map(h => + Buffer + .from(h.substring(2), 'hex') + .slice(0, 4) // bytes4() + ) + .reduce((memo, bytes) => { + for (let i = 0; i < INTERFACE_ID_LENGTH; i++) { + memo[i] = memo[i] ^ bytes[i]; // xor + } + return memo; + }, Buffer.alloc(INTERFACE_ID_LENGTH)); + + return `0x${interfaceIdBuffer.toString('hex')}`; +}; diff --git a/test/introspection/SupportsInterface.behavior.js b/test/introspection/SupportsInterface.behavior.js new file mode 100644 index 000000000..6e49456db --- /dev/null +++ b/test/introspection/SupportsInterface.behavior.js @@ -0,0 +1,54 @@ +import makeInterfaceId from '../helpers/makeInterfaceId'; + +const INTERFACE_IDS = { + ERC165: makeInterfaceId([ + 'supportsInterface(bytes4)', + ]), + ERC721: makeInterfaceId([ + 'balanceOf(address)', + 'ownerOf(uint256)', + 'approve(address,uint256)', + 'getApproved(uint256)', + 'setApprovalForAll(address,bool)', + 'isApprovedForAll(address,address)', + 'transferFrom(address,address,uint256)', + 'safeTransferFrom(address,address,uint256)', + 'safeTransferFrom(address,address,uint256,bytes)', + ]), + ERC721Enumerable: makeInterfaceId([ + 'totalSupply()', + 'tokenOfOwnerByIndex(address,uint256)', + 'tokenByIndex(uint256)', + ]), + ERC721Metadata: makeInterfaceId([ + 'name()', + 'symbol()', + 'tokenURI(uint256)', + ]), + ERC721Exists: makeInterfaceId([ + 'exists(uint256)', + ]), +}; + +export default function (interfaces = []) { + describe('ERC165\'s supportsInterface(bytes4)', function () { + beforeEach(function () { + this.thing = this.mock || this.token; + }); + + for (let k of interfaces) { + const interfaceId = INTERFACE_IDS[k]; + describe(k, function () { + it('should use less than 30k gas', async function () { + const gasEstimate = await this.thing.supportsInterface.estimateGas(interfaceId); + gasEstimate.should.be.lte(30000); + }); + + it('is supported', async function () { + const isSupported = await this.thing.supportsInterface(interfaceId); + isSupported.should.eq(true); + }); + }); + } + }); +} diff --git a/test/introspection/SupportsInterfaceWithLookup.test.js b/test/introspection/SupportsInterfaceWithLookup.test.js new file mode 100644 index 000000000..be017a0c2 --- /dev/null +++ b/test/introspection/SupportsInterfaceWithLookup.test.js @@ -0,0 +1,24 @@ +import shouldSupportInterfaces from './SupportsInterface.behavior'; +import assertRevert from '../helpers/assertRevert'; + +const SupportsInterfaceWithLookup = artifacts.require('SupportsInterfaceWithLookupMock'); + +require('chai') + .use(require('chai-as-promised')) + .should(); + +contract('SupportsInterfaceWithLookup', function (accounts) { + before(async function () { + this.mock = await SupportsInterfaceWithLookup.new(); + }); + + it('does not allow 0xffffffff', async function () { + await assertRevert( + this.mock.registerInterface(0xffffffff) + ); + }); + + shouldSupportInterfaces([ + 'ERC165', + ]); +}); diff --git a/test/token/ERC721/ERC721BasicToken.behaviour.js b/test/token/ERC721/ERC721BasicToken.behaviour.js index 82df92542..a10d1a018 100644 --- a/test/token/ERC721/ERC721BasicToken.behaviour.js +++ b/test/token/ERC721/ERC721BasicToken.behaviour.js @@ -1,3 +1,4 @@ +import shouldSupportInterfaces from '../../introspection/SupportsInterface.behavior'; import assertRevert from '../../helpers/assertRevert'; import decodeLogs from '../../helpers/decodeLogs'; import sendTransaction from '../../helpers/sendTransaction'; @@ -19,28 +20,28 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; const RECEIVER_MAGIC_VALUE = '0xf0b9e5ba'; - describe('like a ERC721BasicToken', function () { + describe('like an ERC721BasicToken', function () { beforeEach(async function () { await this.token.mint(creator, firstTokenId, { from: creator }); await this.token.mint(creator, secondTokenId, { from: creator }); }); describe('balanceOf', function () { - describe('when the given address owns some tokens', function () { + context('when the given address owns some tokens', function () { it('returns the amount of tokens owned by the given address', async function () { const balance = await this.token.balanceOf(creator); balance.should.be.bignumber.equal(2); }); }); - describe('when the given address does not own any tokens', function () { + context('when the given address does not own any tokens', function () { it('returns 0', async function () { const balance = await this.token.balanceOf(accounts[1]); balance.should.be.bignumber.equal(0); }); }); - describe('when querying the zero address', function () { + context('when querying the zero address', function () { it('throws', async function () { await assertRevert(this.token.balanceOf(0)); }); @@ -48,7 +49,7 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { }); describe('exists', function () { - describe('when the token exists', function () { + context('when the token exists', function () { const tokenId = firstTokenId; it('should return true', async function () { @@ -57,7 +58,7 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { }); }); - describe('when the token does not exist', function () { + context('when the token does not exist', function () { const tokenId = unknownTokenId; it('should return false', async function () { @@ -68,7 +69,7 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { }); describe('ownerOf', function () { - describe('when the given token ID was tracked by this token', function () { + context('when the given token ID was tracked by this token', function () { const tokenId = firstTokenId; it('returns the owner of the given token ID', async function () { @@ -77,7 +78,7 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { }); }); - describe('when the given token ID was not tracked by this token', function () { + context('when the given token ID was not tracked by this token', function () { const tokenId = unknownTokenId; it('reverts', async function () { @@ -93,7 +94,7 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { const unauthorized = accounts[4]; const tokenId = firstTokenId; const data = '0x42'; - + let logs = null; beforeEach(async function () { @@ -120,7 +121,7 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { logs[0].args._owner.should.be.equal(owner); logs[0].args._approved.should.be.equal(ZERO_ADDRESS); logs[0].args._tokenId.should.be.bignumber.equal(tokenId); - + logs[1].event.should.be.eq('Transfer'); logs[1].args._from.should.be.equal(owner); logs[1].args._to.should.be.equal(this.to); @@ -149,35 +150,35 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { const newOwnerToken = await this.token.tokenOfOwnerByIndex(this.to, 0); newOwnerToken.toNumber().should.be.equal(tokenId); - + const previousOwnerToken = await this.token.tokenOfOwnerByIndex(owner, 0); previousOwnerToken.toNumber().should.not.be.equal(tokenId); }); }; const shouldTransferTokensByUsers = function (transferFunction) { - describe('when called by the owner', function () { + context('when called by the owner', function () { beforeEach(async function () { ({ logs } = await transferFunction.call(this, owner, this.to, tokenId, { from: owner })); }); transferWasSuccessful({ owner, tokenId, approved }); }); - describe('when called by the approved individual', function () { + context('when called by the approved individual', function () { beforeEach(async function () { ({ logs } = await transferFunction.call(this, owner, this.to, tokenId, { from: approved })); }); transferWasSuccessful({ owner, tokenId, approved }); }); - describe('when called by the operator', function () { + context('when called by the operator', function () { beforeEach(async function () { ({ logs } = await transferFunction.call(this, owner, this.to, tokenId, { from: operator })); }); transferWasSuccessful({ owner, tokenId, approved }); }); - describe('when called by the owner without an approved user', function () { + context('when called by the owner without an approved user', function () { beforeEach(async function () { await this.token.approve(ZERO_ADDRESS, tokenId, { from: owner }); ({ logs } = await transferFunction.call(this, owner, this.to, tokenId, { from: operator })); @@ -185,7 +186,7 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { transferWasSuccessful({ owner, tokenId, approved: null }); }); - describe('when sent to the owner', function () { + context('when sent to the owner', function () { beforeEach(async function () { ({ logs } = await transferFunction.call(this, owner, owner, tokenId, { from: owner })); }); @@ -194,56 +195,56 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { const newOwner = await this.token.ownerOf(tokenId); newOwner.should.be.equal(owner); }); - + it('clears the approval for the token ID', async function () { const approvedAccount = await this.token.getApproved(tokenId); approvedAccount.should.be.equal(ZERO_ADDRESS); }); - + it('emits an approval and transfer events', async function () { logs.length.should.be.equal(2); logs[0].event.should.be.eq('Approval'); logs[0].args._owner.should.be.equal(owner); logs[0].args._approved.should.be.equal(ZERO_ADDRESS); logs[0].args._tokenId.should.be.bignumber.equal(tokenId); - + logs[1].event.should.be.eq('Transfer'); logs[1].args._from.should.be.equal(owner); logs[1].args._to.should.be.equal(owner); logs[1].args._tokenId.should.be.bignumber.equal(tokenId); }); - + it('keeps the owner balance', async function () { const ownerBalance = await this.token.balanceOf(owner); ownerBalance.should.be.bignumber.equal(2); }); - + it('keeps same tokens by index', async function () { if (!this.token.tokenOfOwnerByIndex) return; const tokensListed = await Promise.all(_.range(2).map(i => this.token.tokenOfOwnerByIndex(owner, i))); tokensListed.map(t => t.toNumber()).should.have.members([firstTokenId, secondTokenId]); }); }); - - describe('when the address of the previous owner is incorrect', function () { + + context('when the address of the previous owner is incorrect', function () { it('reverts', async function () { await assertRevert(transferFunction.call(this, unauthorized, this.to, tokenId, { from: owner })); }); }); - describe('when the sender is not authorized for the token id', function () { + context('when the sender is not authorized for the token id', function () { it('reverts', async function () { await assertRevert(transferFunction.call(this, owner, this.to, tokenId, { from: unauthorized })); }); }); - describe('when the given token ID does not exist', function () { + context('when the given token ID does not exist', function () { it('reverts', async function () { await assertRevert(transferFunction.call(this, owner, this.to, unknownTokenId, { from: owner })); }); }); - describe('when the address to transfer the token to is the zero address', function () { + context('when the address to transfer the token to is the zero address', function () { it('reverts', async function () { await assertRevert(transferFunction.call(this, owner, ZERO_ADDRESS, tokenId, { from: owner })); }); @@ -275,15 +276,15 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { describe('to a user account', function () { shouldTransferTokensByUsers(transferFun); }); - + describe('to a valid receiver contract', function () { beforeEach(async function () { this.receiver = await ERC721Receiver.new(RECEIVER_MAGIC_VALUE, false); this.to = this.receiver.address; }); - + shouldTransferTokensByUsers(transferFun); - + it('should call onERC721Received', async function () { const result = await transferFun.call(this, owner, this.to, tokenId, { from: owner }); result.receipt.logs.length.should.be.equal(3); @@ -357,9 +358,9 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { logs[0].args._tokenId.should.be.bignumber.equal(tokenId); }); }; - - describe('when clearing approval', function () { - describe('when there was no prior approval', function () { + + context('when clearing approval', function () { + context('when there was no prior approval', function () { beforeEach(async function () { ({ logs } = await this.token.approve(ZERO_ADDRESS, tokenId, { from: sender })); }); @@ -370,8 +371,8 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { logs.length.should.be.equal(0); }); }); - - describe('when there was a prior approval', function () { + + context('when there was a prior approval', function () { beforeEach(async function () { await this.token.approve(to, tokenId, { from: sender }); ({ logs } = await this.token.approve(ZERO_ADDRESS, tokenId, { from: sender })); @@ -382,8 +383,8 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { }); }); - describe('when approving a non-zero address', function () { - describe('when there was no prior approval', function () { + context('when approving a non-zero address', function () { + context('when there was no prior approval', function () { beforeEach(async function () { ({ logs } = await this.token.approve(to, tokenId, { from: sender })); }); @@ -392,7 +393,7 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { itEmitsApprovalEvent(to); }); - describe('when there was a prior approval to the same address', function () { + context('when there was a prior approval to the same address', function () { beforeEach(async function () { await this.token.approve(to, tokenId, { from: sender }); ({ logs } = await this.token.approve(to, tokenId, { from: sender })); @@ -402,7 +403,7 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { itEmitsApprovalEvent(to); }); - describe('when there was a prior approval to a different address', function () { + context('when there was a prior approval to a different address', function () { beforeEach(async function () { await this.token.approve(accounts[2], tokenId, { from: sender }); ({ logs } = await this.token.approve(to, tokenId, { from: sender })); @@ -413,26 +414,26 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { }); }); - describe('when the address that receives the approval is the owner', function () { + context('when the address that receives the approval is the owner', function () { it('reverts', async function () { await assertRevert(this.token.approve(sender, tokenId, { from: sender })); }); }); - - describe('when the sender does not own the given token ID', function () { + + context('when the sender does not own the given token ID', function () { it('reverts', async function () { await assertRevert(this.token.approve(to, tokenId, { from: accounts[2] })); }); }); - describe('when the sender is approved for the given token ID', function () { + context('when the sender is approved for the given token ID', function () { it('reverts', async function () { await this.token.approve(accounts[2], tokenId, { from: sender }); await assertRevert(this.token.approve(to, tokenId, { from: accounts[2] })); }); }); - describe('when the sender is an operator', function () { + context('when the sender is an operator', function () { const operator = accounts[2]; beforeEach(async function () { await this.token.setApprovalForAll(operator, true, { from: sender }); @@ -443,7 +444,7 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { itEmitsApprovalEvent(to); }); - describe('when the given token ID does not exist', function () { + context('when the given token ID does not exist', function () { it('reverts', async function () { await assertRevert(this.token.approve(to, unknownTokenId, { from: sender })); }); @@ -453,10 +454,10 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { describe('setApprovalForAll', function () { const sender = creator; - describe('when the operator willing to approve is not the owner', function () { + context('when the operator willing to approve is not the owner', function () { const operator = accounts[1]; - describe('when there is no operator approval set by the sender', function () { + context('when there is no operator approval set by the sender', function () { it('approves the operator', async function () { await this.token.setApprovalForAll(operator, true, { from: sender }); @@ -475,7 +476,7 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { }); }); - describe('when the operator was set as not approved', function () { + context('when the operator was set as not approved', function () { beforeEach(async function () { await this.token.setApprovalForAll(operator, false, { from: sender }); }); @@ -505,7 +506,7 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { }); }); - describe('when the operator was already approved', function () { + context('when the operator was already approved', function () { beforeEach(async function () { await this.token.setApprovalForAll(operator, true, { from: sender }); }); @@ -529,7 +530,7 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { }); }); - describe('when the operator is the owner', function () { + context('when the operator is the owner', function () { const operator = creator; it('reverts', async function () { @@ -537,5 +538,11 @@ export default function shouldBehaveLikeERC721BasicToken (accounts) { }); }); }); + + shouldSupportInterfaces([ + 'ERC165', + 'ERC721', + 'ERC721Exists', + ]); }); }; diff --git a/test/token/ERC721/ERC721Token.test.js b/test/token/ERC721/ERC721Token.test.js index 46e0cfc68..0f50d90e0 100644 --- a/test/token/ERC721/ERC721Token.test.js +++ b/test/token/ERC721/ERC721Token.test.js @@ -1,6 +1,7 @@ import assertRevert from '../../helpers/assertRevert'; import shouldBehaveLikeERC721BasicToken from './ERC721BasicToken.behaviour'; import shouldMintAndBurnERC721Token from './ERC721MintBurn.behaviour'; +import shouldSupportInterfaces from '../../introspection/SupportsInterface.behavior'; import _ from 'lodash'; const BigNumber = web3.BigNumber; @@ -75,7 +76,7 @@ contract('ERC721Token', function (accounts) { await assertRevert(this.token.tokenByIndex(0)); }); }); - + describe('metadata', function () { const sampleUri = 'mock://mytoken'; @@ -122,7 +123,7 @@ contract('ERC721Token', function (accounts) { describe('tokenOfOwnerByIndex', function () { const owner = creator; const another = accounts[1]; - + describe('when the given index is lower than the amount of tokens owned by the given address', function () { it('returns the token ID placed at the given index', async function () { const tokenId = await this.token.tokenOfOwnerByIndex(owner, 0); @@ -178,14 +179,14 @@ contract('ERC721Token', function (accounts) { const owner = accounts[0]; const newTokenId = 300; const anotherNewTokenId = 400; - + await this.token.burn(tokenId, { from: owner }); await this.token.mint(owner, newTokenId, { from: owner }); await this.token.mint(owner, anotherNewTokenId, { from: owner }); - + const count = await this.token.totalSupply(); count.toNumber().should.be.equal(3); - + const tokensListed = await Promise.all(_.range(3).map(i => this.token.tokenByIndex(i))); const expectedTokens = _.filter( [firstTokenId, secondTokenId, newTokenId, anotherNewTokenId], @@ -196,4 +197,12 @@ contract('ERC721Token', function (accounts) { }); }); }); + + shouldSupportInterfaces([ + 'ERC165', + 'ERC721', + 'ERC721Exists', + 'ERC721Enumerable', + 'ERC721Metadata', + ]); });