diff --git a/contracts/AutoIncrementing.sol b/contracts/AutoIncrementing.sol new file mode 100644 index 00000000000..52494db1a2c --- /dev/null +++ b/contracts/AutoIncrementing.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.4.23; + + +contract AutoIncrementing { + uint256 internal nextId_ = 0; + + function nextId() internal returns (uint256) { + uint256 thisId = nextId_; + nextId_ = nextId_ + 1; + return thisId; + } +} diff --git a/contracts/access/AutoIncrementingERC721Minter.sol b/contracts/access/AutoIncrementingERC721Minter.sol new file mode 100644 index 00000000000..8ed725f4373 --- /dev/null +++ b/contracts/access/AutoIncrementingERC721Minter.sol @@ -0,0 +1,57 @@ +pragma solidity ^0.4.23; + +import "../token/ERC721/MintableERC721Token.sol"; +import "./SignatureBouncer.sol"; +import "../AutoIncrementing.sol"; + + +/** + * @title AutoIncrementingERC721Minter + * @author Matt Condon (@shrugs) + * @dev An ERc721Minter that generates auto-incrementing `tokenId`s. + */ +contract AutoIncrementingERC721Minter is AutoIncrementing, SignatureBouncer { + MintableERC721Token public token; + + constructor(MintableERC721Token _token) + public + { + token = _token; + } + + function mint(bytes _sig, string _tokenURI) + public + returns (uint256) + { + require( + isValidMintSignature( + msg.sender, + _sig, + _tokenURI + ) + ); + + uint256 _tokenId = nextId(); + token.mint(msg.sender, _tokenId, _tokenURI); + return _tokenId; + } + + function isValidMintSignature( + address _address, + bytes _sig, + string _tokenURI + ) + internal + view + returns (bool) + { + return isValidDataHash( + keccak256( + address(this), + _address, + _tokenURI + ), + _sig + ); + } +} diff --git a/contracts/access/ERC721Minter.sol b/contracts/access/ERC721Minter.sol new file mode 100644 index 00000000000..8d78f9d08c3 --- /dev/null +++ b/contracts/access/ERC721Minter.sol @@ -0,0 +1,64 @@ +pragma solidity ^0.4.23; + +import "../token/ERC721/MintableERC721Token.sol"; +import "./SignatureBouncer.sol"; + + +/** + * @title ERC721Minter + * @author Matt Condon (@shrugs) + * @dev A SignatureBouncer that allows users to mint themselves a MintableERC721Token + * @dev iff they have a valid signature from a bouncer. + * @dev 1. Deploy a MintableERC721Token and ERC721Minter. + * @dev 2. Make ERC721Minter a minter of the token using `addMinter`. + * @dev 3. Make your server a bouncer of ERC721Minter using `addBouncer`. + * @dev 4. Generate a valid bouncer signature (address(this) + msg.sender + tokenId + tokenURI) + * @dev and submit it to the ERC721Minter using `mint` + */ +contract ERC721Minter is SignatureBouncer { + MintableERC721Token public token; + + constructor(MintableERC721Token _token) + public + { + token = _token; + } + + function mint(bytes _sig, uint256 _tokenId, string _tokenURI) + public + returns (uint256) + { + require( + isValidMintSignature( + msg.sender, + _sig, + _tokenId, + _tokenURI + ) + ); + + token.mint(msg.sender, _tokenId, _tokenURI); + return _tokenId; + } + + function isValidMintSignature( + address _address, + bytes _sig, + uint256 _tokenId, + string _tokenURI + ) + internal + view + returns (bool) + { + return isValidDataHash( + keccak256( + address(this), + _address, + _tokenId, + _tokenURI + ), + _sig + ); + } +} diff --git a/contracts/access/NonceTracker.sol b/contracts/access/NonceTracker.sol new file mode 100644 index 00000000000..81d8d51fec2 --- /dev/null +++ b/contracts/access/NonceTracker.sol @@ -0,0 +1,54 @@ +pragma solidity ^0.4.23; + + +/** + * @title NonceTracker + * @author Matt Condon (@shrugs) + * @dev A simple way to keep track of nonces and restrict access. + * @dev Use the `withAccess` modifier to restrict access by address. + * @dev Use the `withMaxAccess` modifier to restrict access by address up to a max amount + * @dev For example, withAccess(msg.sender, 1) will only allow once-per-address. + * @dev You can also accept nonces from users (as part of a hash you verify). + */ +contract NonceTracker { + mapping(address => uint256) private nonces; + + modifier withAccess(address _address, uint256 _nonce) + { + access(_address, _nonce); + _; + } + + /** + * @dev use withMaxAccess to restrict access per-address for a maxiumum + * @dev number of times + */ + modifier withMaxAccess(address _address, uint256 _maxNonce) + { + // require that we haven't accessed this resource too much + require(nonce(_address) < _maxNonce); + // access it once more by incrementing the nonce + access(_address, nonce(_address) + 1); + // allow access + _; + } + + function nonce(address _address) + public + view + returns (uint256) + { + return nonces[_address]; + } + + /** + * @dev call this function when accepting a nonce + * @dev throws if nonce is not strictly greater than previous nonce + */ + function access(address _address, uint256 _nonce) + internal + { + require(_nonce > nonces[_address]); + nonces[_address] = _nonce; + } +} diff --git a/contracts/access/SignatureBouncer.sol b/contracts/access/SignatureBouncer.sol index b037b1dedc3..0af801bccae 100644 --- a/contracts/access/SignatureBouncer.sol +++ b/contracts/access/SignatureBouncer.sol @@ -1,6 +1,6 @@ pragma solidity ^0.4.23; -import "../ownership/Ownable.sol"; +import "../ownership/rbac/RBACOwnable.sol"; import "../ownership/rbac/RBAC.sol"; import "../ECRecovery.sol"; @@ -22,7 +22,7 @@ import "../ECRecovery.sol"; * @dev * @dev See the tests Bouncer.test.js for specific usage examples. */ -contract SignatureBouncer is Ownable, RBAC { +contract SignatureBouncer is RBACOwnable { using ECRecovery for bytes32; string public constant ROLE_BOUNCER = "bouncer"; diff --git a/contracts/mocks/DefaultTokenURIMock.sol b/contracts/mocks/DefaultTokenURIMock.sol new file mode 100644 index 00000000000..28aa5f1557c --- /dev/null +++ b/contracts/mocks/DefaultTokenURIMock.sol @@ -0,0 +1,16 @@ +pragma solidity ^0.4.23; + +import "../token/ERC721/MintableERC721Token.sol"; +import "../token/ERC721/DefaultTokenURI.sol"; + + +contract DefaultTokenURIMock is DefaultTokenURI, MintableERC721Token { + + constructor(string _name, string _symbol, string _tokenURI) + MintableERC721Token(_name, _symbol) + DefaultTokenURI(_tokenURI) + public + { + + } +} diff --git a/contracts/mocks/NonceTrackerImpl.sol b/contracts/mocks/NonceTrackerImpl.sol new file mode 100644 index 00000000000..c624b52a8e1 --- /dev/null +++ b/contracts/mocks/NonceTrackerImpl.sol @@ -0,0 +1,42 @@ +pragma solidity ^0.4.23; + +import "../access/NonceTracker.sol"; + + +contract NonceTrackerImpl is NonceTracker { + + modifier onlyValidInputs(uint256 _nonce) + { + require(true); + // ^ you'd implement this using something like SignatureBouncer + _; + } + + function canDoThisOnce() + withMaxAccess(msg.sender, 1) + public + { + + } + + function canDoThisTwice() + withMaxAccess(msg.sender, 2) + public + { + } + + function cantDoThisAtAll() + withMaxAccess(msg.sender, 0) + public + { + require(nonce(msg.sender) <= 0); + } + + function withAcceptedNonce(uint256 _nonce) + onlyValidInputs(_nonce) + withAccess(msg.sender, _nonce) + public + { + + } +} diff --git a/contracts/ownership/rbac/RBACMintable.sol b/contracts/ownership/rbac/RBACMintable.sol new file mode 100644 index 00000000000..defba7ec711 --- /dev/null +++ b/contracts/ownership/rbac/RBACMintable.sol @@ -0,0 +1,20 @@ +pragma solidity ^0.4.23; + +import "./RBAC.sol"; + + +/** + * @title RBACMintable + * @author Matt Condon (@shrugs) + * @dev Mintable logic using RBAC. + * @dev You must add any logic for addMinter/removeMinter yourself because + * @dev security concerns will vary. + */ +contract RBACMintable is RBAC { + string public constant ROLE_MINTER = "minter"; + + modifier onlyMinter() { + checkRole(msg.sender, ROLE_MINTER); + _; + } +} diff --git a/contracts/ownership/rbac/RBACOwnable.sol b/contracts/ownership/rbac/RBACOwnable.sol new file mode 100644 index 00000000000..397ef44e4cc --- /dev/null +++ b/contracts/ownership/rbac/RBACOwnable.sol @@ -0,0 +1,41 @@ +pragma solidity ^0.4.23; + +import "./RBAC.sol"; + + +/** + * @title RBACOwnable + * @author Matt Condon (@shrugs) + * @dev Ownable logic using RBAC. + * @dev Use RBACOwnable if you could have many different owners and you're ok with + * @dev the security profile of any owner being able to add another owner. + * @dev Only difference from Ownable.sol is that the owners are not stored as public variables. + */ +contract RBACOwnable is RBAC { + string public constant ROLE_OWNER = "owner"; + + constructor() + public + { + addRole(msg.sender, ROLE_OWNER); + } + + modifier onlyOwner() { + checkRole(msg.sender, ROLE_OWNER); + _; + } + + function addOwner(address _owner) + onlyOwner + public + { + addRole(_owner, ROLE_OWNER); + } + + function removeOwner(address _owner) + onlyOwner + public + { + removeRole(_owner, ROLE_OWNER); + } +} diff --git a/contracts/token/ERC20/RBACMintableToken.sol b/contracts/token/ERC20/RBACMintableToken.sol index 923d2fe3952..73b4f970200 100644 --- a/contracts/token/ERC20/RBACMintableToken.sol +++ b/contracts/token/ERC20/RBACMintableToken.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.23; import "./MintableToken.sol"; -import "../../ownership/rbac/RBAC.sol"; +import "../../ownership/rbac/RBACMintable.sol"; /** @@ -9,12 +9,7 @@ import "../../ownership/rbac/RBAC.sol"; * @author Vittorio Minacori (@vittominacori) * @dev Mintable Token, with RBAC minter permissions */ -contract RBACMintableToken is MintableToken, RBAC { - /** - * A constant role name for indicating minters. - */ - string public constant ROLE_MINTER = "minter"; - +contract RBACMintableToken is MintableToken, RBACMintable { /** * @dev override the Mintable token modifier to add role based logic */ diff --git a/contracts/token/ERC721/DefaultTokenURI.sol b/contracts/token/ERC721/DefaultTokenURI.sol new file mode 100644 index 00000000000..108da727688 --- /dev/null +++ b/contracts/token/ERC721/DefaultTokenURI.sol @@ -0,0 +1,31 @@ +pragma solidity ^0.4.23; + +import "./ERC721Token.sol"; + + +contract DefaultTokenURI is ERC721Token { + string private tokenURI_ = ""; + + constructor(string _tokenURI) + public + { + require(bytes(_tokenURI).length > 0); + tokenURI_ = _tokenURI; + } + + /** + * @dev Returns a default URI for every tokenId unless a specific URI is set + * @param _tokenId uint256 ID of the token to query + */ + function tokenURI(uint256 _tokenId) + public + view + returns (string) + { + if (bytes(tokenURIs[_tokenId]).length != 0) { + return super.tokenURI(_tokenId); + } + + return tokenURI_; + } +} diff --git a/contracts/token/ERC721/MintableERC721Token.sol b/contracts/token/ERC721/MintableERC721Token.sol new file mode 100644 index 00000000000..4727868c350 --- /dev/null +++ b/contracts/token/ERC721/MintableERC721Token.sol @@ -0,0 +1,48 @@ +pragma solidity ^0.4.23; + +import "../../AutoIncrementing.sol"; +import "../../ownership/rbac/RBACOwnable.sol"; +import "../../ownership/rbac/RBACMintable.sol"; +import "./ERC721Token.sol"; + + +/** + * @title MintableERC721Token + * @author Matt Condon (@shrugs) + * @dev This is an ERC721Token than can be minted by any sender with the role + * @dev ROLE_MINTER. It features an auto-incrementing tokenId. + * @dev This contract is designed to be used with a *Bouncer of some sort that + * @dev provides access control an input verification. + */ +contract MintableERC721Token is AutoIncrementing, RBACOwnable, RBACMintable, ERC721Token { // solium-disable-line max-len + + constructor(string _name, string _symbol) + ERC721Token(_name, _symbol) + public + { + } + + function mint(address _to, uint256 _tokenId, string _tokenURI) + onlyMinter + public + { + _mint(_to, _tokenId); + _setTokenURI(_tokenId, _tokenURI); + } + + /** + * @dev add a minter role to an address + * @param minter address + */ + function addMinter(address minter) onlyOwner public { + addRole(minter, ROLE_MINTER); + } + + /** + * @dev remove a minter role from an address + * @param minter address + */ + function removeMinter(address minter) onlyOwner public { + removeRole(minter, ROLE_MINTER); + } +} diff --git a/package-lock.json b/package-lock.json index 45c21e91076..c3398b196e5 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 6f6929167c9..c86eba40946 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/access/AutoIncrementingERC721Minter.test.js b/test/access/AutoIncrementingERC721Minter.test.js new file mode 100644 index 00000000000..ad2c651083d --- /dev/null +++ b/test/access/AutoIncrementingERC721Minter.test.js @@ -0,0 +1,35 @@ + +import { getBouncerSigner } from '../helpers/sign'; + +const AutoIncrementingERC721Minter = artifacts.require('AutoIncrementingERC721Minter'); +const MintableERC721Token = artifacts.require('MintableERC721Token'); + +const TOKEN_URI = 'https://example.com'; + +require('chai') + .use(require('chai-as-promised')) + .use(require('chai-bignumber')(web3.BigNumber)) + .should(); + +contract('AutoIncrementingERC721Minter', ([_, owner, user, bouncerAddress]) => { + before(async function () { + this.token = await MintableERC721Token.new('Test Token', 'TEST', { from: owner }); + this.minter = await AutoIncrementingERC721Minter.new(this.token.address, { from: owner }); + await this.token.addMinter(this.minter.address, { from: owner }); + await this.minter.addBouncer(bouncerAddress, { from: owner }); + this.genSig = getBouncerSigner(this.minter, bouncerAddress); + }); + + for (let id = 0; id < 3; id++) { + it(`should assign the user tokenId ${id}`, async function () { + const sig = this.genSig(user, [TOKEN_URI]); + await this.minter.mint(sig, TOKEN_URI, { from: user }); + + const balance = await this.token.balanceOf(user); + balance.should.be.bignumber.eq(id + 1); + + const tokenId = await this.token.tokenOfOwnerByIndex(user, id); + tokenId.should.be.bignumber.eq(tokenId); + }); + } +}); diff --git a/test/access/ERC721Minter.test.js b/test/access/ERC721Minter.test.js new file mode 100644 index 00000000000..b19565d589a --- /dev/null +++ b/test/access/ERC721Minter.test.js @@ -0,0 +1,61 @@ + +import assertRevert from '../helpers/assertRevert'; +import { getBouncerSigner } from '../helpers/sign'; + +const ERC721Minter = artifacts.require('ERC721Minter'); +const MintableERC721Token = artifacts.require('MintableERC721Token'); + +const TOKEN_URI = 'https://example.com'; +const INCORRECT_TOKEN_URI = 'https://incorrect.example.com'; +const tokenId = new web3.BigNumber(0); +const incorrectTokenId = new web3.BigNumber(1); + +require('chai') + .use(require('chai-as-promised')) + .use(require('chai-bignumber')(web3.BigNumber)) + .should(); + +contract('ERC721Minter', ([_, owner, user, anyone, bouncerAddress]) => { + before(async function () { + this.token = await MintableERC721Token.new('Test Token', 'TEST', { from: owner }); + this.minter = await ERC721Minter.new(this.token.address, { from: owner }); + await this.token.addMinter(this.minter.address, { from: owner }); + await this.minter.addBouncer(bouncerAddress, { from: owner }); + this.genSig = getBouncerSigner(this.minter, bouncerAddress); + }); + + context('invalid signature', function () { + it('should not allow anyone to mint tokens', async function () { + await assertRevert( + this.minter.mint('0x0', tokenId, TOKEN_URI, { from: anyone }) + ); + }); + + it('should not allow user to mint with the wrong tokenURI', async function () { + const sig = this.genSig(user, [tokenId, TOKEN_URI]); + await assertRevert( + this.minter.mint(sig, tokenId, INCORRECT_TOKEN_URI, { from: user }) + ); + }); + + it('should not allow user to mint with the wrong tokenId', async function () { + const sig = this.genSig(user, [tokenId, TOKEN_URI]); + await assertRevert( + this.minter.mint(sig, incorrectTokenId, TOKEN_URI, { from: user }) + ); + }); + }); + + context('valid signature', function () { + it('should allow user to mint', async function () { + const sig = this.genSig(user, [tokenId, TOKEN_URI]); + await this.minter.mint(sig, tokenId, TOKEN_URI, { from: user }); + + const balance = await this.token.balanceOf(user); + balance.should.be.bignumber.eq(1); + + const newTokenId = await this.token.tokenOfOwnerByIndex(user, 0); + newTokenId.should.be.bignumber.eq(tokenId); + }); + }); +}); diff --git a/test/access/NonceTracker.test.js b/test/access/NonceTracker.test.js new file mode 100644 index 00000000000..08a058ef6b0 --- /dev/null +++ b/test/access/NonceTracker.test.js @@ -0,0 +1,67 @@ + +import assertRevert from '../helpers/assertRevert'; + +const NonceTracker = artifacts.require('NonceTrackerImpl'); + +require('chai') + .use(require('chai-as-promised')) + .use(require('chai-bignumber')(web3.BigNumber)) + .should(); + +contract('NonceTracker', ([_, owner, anyone, another]) => { + context('canDoThisOnce', function () { + before(async function () { + this.tracker = await NonceTracker.new({ from: owner }); + }); + + it('should allow anyone to do thing once', async function () { + await this.tracker.canDoThisOnce({ from: anyone }); + }); + + it('should revert if they try to do it again', async function () { + await assertRevert( + this.tracker.canDoThisOnce({ from: anyone }) + ); + }); + + it('should allow another person to do it once anyway', async function () { + await this.tracker.canDoThisOnce({ from: another }); + }); + }); + + context('canDoThisTwice', function () { + before(async function () { + this.tracker = await NonceTracker.new({ from: owner }); + }); + + it('should allow a person to do things twice', async function () { + await this.tracker.canDoThisTwice({ from: anyone }); + await this.tracker.canDoThisTwice({ from: anyone }); + await assertRevert( + this.tracker.canDoThisTwice({ from: anyone }) + ); + }); + }); + + context('cantDoThisAtAll', function () { + before(async function () { + this.tracker = await NonceTracker.new({ from: owner }); + }); + + it('should not allow anyone', async function () { + await assertRevert( + this.tracker.cantDoThisAtAll({ from: anyone }) + ); + }); + }); + + context('withAcceptedNonce', function () { + before(async function () { + this.tracker = await NonceTracker.new({ from: owner }); + }); + + it('should allow inputs', async function () { + await this.tracker.withAcceptedNonce(1, { from: anyone }); + }); + }); +}); diff --git a/test/access/SignatureBouncer.test.js b/test/access/SignatureBouncer.test.js index 472a4122692..0dbab2458fa 100644 --- a/test/access/SignatureBouncer.test.js +++ b/test/access/SignatureBouncer.test.js @@ -1,30 +1,24 @@ import assertRevert from '../helpers/assertRevert'; -import { signHex } from '../helpers/sign'; +import { getBouncerSigner } from '../helpers/sign'; -const Bouncer = artifacts.require('SignatureBouncerMock'); +const SignatureBouncer = artifacts.require('SignatureBouncerMock'); require('chai') .use(require('chai-as-promised')) .should(); -export const getSigner = (contract, signer, data = '') => (addr) => { - // via: https://github.com/OpenZeppelin/zeppelin-solidity/pull/812/files - const message = contract.address.substr(2) + addr.substr(2) + data; - // ^ substr to remove `0x` because in solidity the address is a set of byes, not a string `0xabcd` - return signHex(signer, message); -}; - -contract('Bouncer', ([_, owner, authorizedUser, anyone, bouncerAddress, newBouncer]) => { +contract('SignatureBouncer', ([_, owner, authorizedUser, anyone, bouncerAddress, newBouncer]) => { before(async function () { - this.bouncer = await Bouncer.new({ from: owner }); + this.bouncer = await SignatureBouncer.new({ from: owner }); this.roleBouncer = await this.bouncer.ROLE_BOUNCER(); - this.genSig = getSigner(this.bouncer, bouncerAddress); + this.roleOwner = await this.bouncer.ROLE_OWNER(); + this.genSig = getBouncerSigner(this.bouncer, bouncerAddress); }); - it('should have a default owner of self', async function () { - const theOwner = await this.bouncer.owner(); - theOwner.should.eq(owner); + it('should have a default owner', async function () { + const hasRole = await this.bouncer.hasRole(owner, this.roleOwner); + hasRole.should.eq(true); }); it('should allow owner to add a bouncer', async function () { diff --git a/test/helpers/sign.js b/test/helpers/sign.js index 30e8943343d..5314e90b372 100644 --- a/test/helpers/sign.js +++ b/test/helpers/sign.js @@ -1,4 +1,5 @@ import utils from 'ethereumjs-util'; +import { soliditySha3 } from 'web3-utils'; /** * Hash and add same prefix to the hash that ganache use. @@ -11,12 +12,19 @@ export const hashMessage = (message) => { return utils.bufferToHex(utils.sha3(Buffer.concat([prefix, messageHex]))); }; -// signs message using web3 (auto-applies prefix) -export const signMessage = (signer, message = '', options = {}) => { - return web3.eth.sign(signer, web3.sha3(message, options)); +// signs message in node (auto-applies prefix) +// message must be in hex already! will not be autoconverted! +export const signMessage = (signer, message = '') => { + return web3.eth.sign(signer, message); }; -// signs hex string using web3 (auto-applies prefix) -export const signHex = (signer, message = '') => { - return signMessage(signer, message, { encoding: 'hex' }); +export const getBouncerSigner = (contract, signer) => (addr, extra = []) => { + const args = [ + contract.address, + addr, + ...extra, + ]; + // ^ substr to remove `0x` because in solidity the address is a set of byes, not a string `0xabcd` + const hashOfMessage = soliditySha3(...args); + return signMessage(signer, hashOfMessage); }; diff --git a/test/library/ECRecovery.test.js b/test/library/ECRecovery.test.js index 7ea55823f49..35985e2bd1b 100644 --- a/test/library/ECRecovery.test.js +++ b/test/library/ECRecovery.test.js @@ -39,7 +39,7 @@ contract('ECRecovery', function (accounts) { it('recover using web3.eth.sign()', async function () { // Create the signature using account[0] - const signature = signMessage(accounts[0], TEST_MESSAGE); + const signature = signMessage(accounts[0], web3.sha3(TEST_MESSAGE)); // Recover the signer address from the generated message and signature. const addrRecovered = await ecrecovery.recover( @@ -51,7 +51,7 @@ contract('ECRecovery', function (accounts) { it('recover using web3.eth.sign() should return wrong signer', async function () { // Create the signature using account[0] - const signature = signMessage(accounts[0], TEST_MESSAGE); + const signature = signMessage(accounts[0], web3.sha3(TEST_MESSAGE)); // Recover the signer address from the generated message and wrong signature. const addrRecovered = await ecrecovery.recover(hashMessage('Test'), signature); @@ -60,7 +60,7 @@ contract('ECRecovery', function (accounts) { it('recover should fail when a wrong hash is sent', async function () { // Create the signature using account[0] - let signature = signMessage(accounts[0], TEST_MESSAGE); + let signature = signMessage(accounts[0], web3.sha3(TEST_MESSAGE)); // Recover the signer address from the generated message and wrong signature. const addrRecovered = await ecrecovery.recover(hashMessage(TEST_MESSAGE).substring(2), signature); diff --git a/test/ownership/rbac/RBACOwnable.test.js b/test/ownership/rbac/RBACOwnable.test.js new file mode 100644 index 00000000000..3174623703c --- /dev/null +++ b/test/ownership/rbac/RBACOwnable.test.js @@ -0,0 +1,30 @@ +import assertRevert from '../../helpers/assertRevert'; + +const RBACOwnable = artifacts.require('RBACOwnable'); + +require('chai') + .use(require('chai-as-promised')) + .should(); + +contract('RBAC', function ([_, owner, newOwner, notOwner]) { + before(async function () { + this.ownable = await RBACOwnable.new({ from: owner }); + this.roleOwner = await this.ownable.ROLE_OWNER(); + }); + + it('should not allow notOwner to add or remove owners', async function () { + await assertRevert( + this.ownable.addOwner(newOwner, { from: notOwner }) + ); + + await assertRevert( + this.ownable.removeOwner(owner, { from: notOwner }) + ); + }); + + it('should allow existing owner to add a new owner', async function () { + await this.ownable.addOwner(newOwner, { from: owner }); + const hasRole = await this.ownable.hasRole(newOwner, this.roleOwner); + hasRole.should.eq(true); + }); +}); diff --git a/test/token/ERC721/DefaultTokenURI.test.js b/test/token/ERC721/DefaultTokenURI.test.js new file mode 100644 index 00000000000..63a6eb90e60 --- /dev/null +++ b/test/token/ERC721/DefaultTokenURI.test.js @@ -0,0 +1,48 @@ +const MintableERC721Token = artifacts.require('DefaultTokenURIMock'); + +const NO_TOKEN_URI = ''; +const DEFAULT_TOKEN_URI = 'https://default.example.com'; +const SPECIFIC_TOKEN_URI = 'https://specific.example.com'; + +const tokenId = new web3.BigNumber(0); + +require('chai') + .use(require('chai-as-promised')) + .use(require('chai-bignumber')(web3.BigNumber)) + .should(); + +contract('DefaultTokenURI', ([_, owner, minter]) => { + beforeEach(async function () { + this.token = await MintableERC721Token.new( + 'Test Token', + 'TEST', + DEFAULT_TOKEN_URI, + { from: owner } + ); + await this.token.addMinter(minter, { from: owner }); + }); + + context('when token has no specific URI', function () { + it('should return default uri', async function () { + await this.token.mint(owner, tokenId, NO_TOKEN_URI, { from: minter }); + const balance = await this.token.balanceOf(owner); + balance.should.be.bignumber.eq(1); + + const tokenURI = await this.token.tokenURI(tokenId); + tokenURI.should.not.eq(NO_TOKEN_URI); + tokenURI.should.eq(DEFAULT_TOKEN_URI); + }); + }); + + context('with specific URI', function () { + it('should return specific URI', async function () { + await this.token.mint(owner, tokenId, SPECIFIC_TOKEN_URI, { from: minter }); + const balance = await this.token.balanceOf(owner); + balance.should.be.bignumber.eq(1); + + const tokenURI = await this.token.tokenURI(tokenId); + tokenURI.should.not.eq(DEFAULT_TOKEN_URI); + tokenURI.should.eq(SPECIFIC_TOKEN_URI); + }); + }); +});