diff --git a/build/.keep b/build/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/contracts/exchange/Exchange.sol b/contracts/exchange/Exchange.sol new file mode 100644 index 0000000..353f904 --- /dev/null +++ b/contracts/exchange/Exchange.sol @@ -0,0 +1,475 @@ +pragma solidity ^0.4.19; + + +import "../math/SafeMath.sol"; +import "../tokens/ERC20.sol"; +import "../tokens/ERC721.sol"; +import "./TokenTransferProxy.sol"; +import "./NFTokenTransferProxy.sol"; + +/* + * @dev based on: https://github.com/0xProject/contracts/blob/master/contracts/Exchange.sol + */ +contract Exchange { + + using SafeMath for uint256; + + /* + * @dev Enum of possible errors. + */ + enum Errors { + TRANSFER_ALREADY_PERFORMED, // Transfer has already beed performed. + TRANSFER_CANCELLED, // Transfer was cancelled. + INSUFFICIENT_BALANCE_OR_ALLOWANCE, // Insufficient balance or allowance for XCT transfer. + NOT_XCERT_OWNER // Is not the owner of Xcert. + } + + /* + * @dev contract addresses + */ + address public XCT_TOKEN_CONTRACT; + address public TOKEN_TRANSFER_PROXY_CONTRACT; + address public NFTOKEN_TRANSFER_PROXY_CONTRACT; + + /* + * @dev Changes to state require at least 5000 gas. + */ + uint16 constant public EXTERNAL_QUERY_GAS_LIMIT = 4999; + + /* + * @dev Mapping of all canceled transfers. + */ + mapping(bytes32 => bool) public transferCancelled; + + /* + * @dev Mapping of all performed transfers. + */ + mapping(bytes32 => bool) public transferPerformed; + + /* + * @dev This event emmits when xcert changes ownership. + */ + event LogPerformTransfer(address _from, + address _to, + address _xcert, + uint256 _xcertId, + address[] _feeAddresses, + uint256[] _feeAmounts, + uint256 _timestamp, + bytes32 _xcertTransferClaim); + + /* + * @dev This event emmits when xcert transfer order is canceled. + */ + event LogCancelTransfer(address _from, + address _to, + address _xcert, + uint256 _xcertId, + address[] _feeAddresses, + uint256[] _feeAmounts, + uint256 _timestamp, + bytes32 _xcertTransferClaim); + + /* + * @dev This event emmits when an error occurs. + */ + event LogError(uint8 indexed errorId, + bytes32 indexed claim); + + + /* + * @dev Sets XCT token address, Token proxy address and xcert Proxy address. + * @param _xcertToken Address pointing to XCT Token contract. + * @param _tokenTransferProxy Address pointing to TokenTransferProxy contract. + * @param _nfTokenTransferProxy Address pointing to none-fungible token transfer proxy contract. + */ + function Exchange(address _xctToken, + address _tokenTransferProxy, + address _nfTokenTransferProxy) + public + { + XCT_TOKEN_CONTRACT = _xctToken; + TOKEN_TRANSFER_PROXY_CONTRACT = _tokenTransferProxy; + NFTOKEN_TRANSFER_PROXY_CONTRACT = _nfTokenTransferProxy; + } + + /* + * @dev Get addresses to all associated contracts (token, tokenTransferProxy, xcertProxy) . + * @return Array of addresses (token, tokenTransferProxy, nfTokenTransferProxy) + */ + function getAddresses() + external + view + returns (address[]) + { + address[] memory addresses; + addresses[0] = XCT_TOKEN_CONTRACT; + addresses[1] = TOKEN_TRANSFER_PROXY_CONTRACT; + addresses[2] = NFTOKEN_TRANSFER_PROXY_CONTRACT; + return addresses; + } + + + /* + * @dev Performs the Xcert transfer. + * @param _from Address of Xcert sender. + * @param _to Address of Xcert reciever. + * @param _xcert Address of Xcert contract. + * @param _xcertId Id of Xcert (hashed certificate data that is transformed into uint256). + * @param _feeAddresses Addresses of all parties that need to get feeAmounts paid. + * @param _feeAmounts Fee amounts of all the _feeAddresses (length of both have to be the same). + * @param _timestamp Timestamp that represents the salt. + * @param _v ECDSA signature parameter v. + * @param _r ECDSA signature parameters r. + * @param _s ECDSA signature parameters s. + * @param _s _throwIfNotTransferable Test the transfer before performing. + */ + function performTransfer(address _from, + address _to, + address _xcert, + uint256 _xcertId, + address[] _feeAddresses, + uint256[] _feeAmounts, + uint256 _timestamp, + uint8 _v, + bytes32 _r, + bytes32 _s, + bool _throwIfNotTransferable) + public + returns (bool) + { + require(_feeAddresses.length == _feeAmounts.length); + require(_to == msg.sender); + require(_from != _to); + + bytes32 claim = getTransferDataClaim( + _from, + _to, + _xcert, + _xcertId, + _feeAddresses, + _feeAmounts, + _timestamp + ); + + require(isValidSignature( + _from, + claim, + _v, + _r, + _s + )); + + if(transferPerformed[claim]) + { + LogError(uint8(Errors.TRANSFER_ALREADY_PERFORMED), claim); + return false; + } + + if(transferCancelled[claim]) + { + LogError(uint8(Errors.TRANSFER_CANCELLED), claim); + return false; + } + + if (_throwIfNotTransferable) + { + if(!_canPayFee(_to, _feeAmounts)) + { + LogError(uint8(Errors.INSUFFICIENT_BALANCE_OR_ALLOWANCE), claim); + return false; + } + + if(!_isAllowed(_from, _xcert, _xcertId)) + { + LogError(uint8(Errors.NOT_XCERT_OWNER), claim); + return false; + } + } + + transferPerformed[claim] = true; + + _transferViaNFTokenTransferProxy(_xcert, _from, _to, _xcertId); + + _payfeeAmounts(_feeAddresses, _feeAmounts, _to); + + LogPerformTransfer( + _from, + _to, + _xcert, + _xcertId, + _feeAddresses, + _feeAmounts, + _timestamp, + claim + ); + + return true; + } + + /* + * @dev Cancels xcert transfer. + * @param _from Address of Xcert sender. + * @param _to Address of Xcert reciever. + * @param _xcert Address of Xcert contract. + * @param _xcertId Id of Xcert (hashed certificate data that is transformed into uint256). + * @param _feeAddresses Addresses of all parties that need to get feeAmounts paid. + * @param _feeAmounts Fee amounts of all the _feeAddresses (length of both have to be the same). + * @param _timestamp Timestamp that represents the salt. + */ + function cancelTransfer(address _from, + address _to, + address _xcert, + uint256 _xcertId, + address[] _feeAddresses, + uint256[] _feeAmounts, + uint256 _timestamp) + public + { + require(msg.sender == _from); + + bytes32 claim = getTransferDataClaim( + _from, + _to, + _xcert, + _xcertId, + _feeAddresses, + _feeAmounts, + _timestamp + ); + + require(!transferPerformed[claim]); + + transferCancelled[claim] = true; + + LogCancelTransfer( + _from, + _to, + _xcert, + _xcertId, + _feeAddresses, + _feeAmounts, + _timestamp, + claim + ); + } + + /* + * @dev Calculates keccak-256 hlaim of mint data from parameters. + * @param _from Address of Xcert sender. + * @param _to Address of Xcert reciever. + * @param _xcert Address of Xcert contract. + * @param _xcertId Id of Xcert (hashed certificate data that is transformed into uint256). + * @param _feeAddresses Addresses of all parties that need to get feeAmounts paid. + * @param _feeAmounts Fee amounts of all the _feeAddresses (length of both have to be the same). + * @param _timestamp Timestamp that represents the salt. + * @returns keccak-hash of transfer data. + */ + function getTransferDataClaim(address _from, + address _to, + address _xcert, + uint256 _xcertId, + address[] _feeAddresses, + uint256[] _feeAmounts, + uint256 _timestamp) + public + constant + returns (bytes32) + { + return keccak256( + address(this), + _from, + _to, + _xcert, + _xcertId, + _feeAddresses, + _feeAmounts, + _timestamp + ); + } + + /* + * @dev Verifies if xcert signature is valid. + * @param _signer address of signer. + * @param _claim Signed Keccak-256 hash. + * @param _v ECDSA signature parameter v. + * @param _r ECDSA signature parameters r. + * @param _s ECDSA signature parameters s. + * @return Validity of signature. + */ + function isValidSignature(address _signer, + bytes32 _claim, + uint8 _v, + bytes32 _r, + bytes32 _s) + public + pure + returns (bool) + { + return _signer == ecrecover( + keccak256("\x19Ethereum Signed Message:\n32", _claim), + _v, + _r, + _s + ); + } + + /* + * @dev Check is payer can pay the feeAmounts. + * @param _to Address of the payer. + * @param_ feeAmounts All the feeAmounts to be payed. + * @return Confirmation if feeAmounts can be payed. + */ + function _canPayFee(address _to, + uint256[] _feeAmounts) + internal + constant + returns (bool) + { + uint256 feeAmountsum = 0; + + for(uint256 i; i < _feeAmounts.length; i++) + { + feeAmountsum = feeAmountsum.add(_feeAmounts[i]); + } + + if(_getBalance(XCT_TOKEN_CONTRACT, _to) < feeAmountsum + || _getAllowance(XCT_TOKEN_CONTRACT, _to) < feeAmountsum ) + { + return false; + } + return true; + } + + /* + * @dev Transfers XCT tokens via TokenTransferProxy using transferFrom function. + * @param _token Address of token to transferFrom. + * @param _from Address transfering token. + * @param _to Address receiving token. + * @param _value Amount of token to transfer. + * @return Success of token transfer. + */ + function _transferViaTokenTransferProxy(address _token, + address _from, + address _to, + uint _value) + internal + returns (bool) + { + return TokenTransferProxy(TOKEN_TRANSFER_PROXY_CONTRACT).transferFrom( + _token, + _from, + _to, + _value + ); + } + + + /* + * @dev Transfers Xcert via XcertProxy using transfer function. + * @param _xcert Address of Xcert to transfer. + * @param _from Address sending Xcert. + * @param _to Address receiving Xcert. + * @param _id Id of transfering Xcert. + * @return Success of Xcert transfer. + */ + function _transferViaNFTokenTransferProxy(address _xcert, + address _from, + address _to, + uint256 _id) + internal + { + NFTokenTransferProxy(NFTOKEN_TRANSFER_PROXY_CONTRACT) + .transferFrom(_xcert, _from, _to, _id); + } + + /* + * @dev Get token balance of an address. + * The called token contract may attempt to change state, but will not be able to due to an added + * gas limit. Gas is limited to prevent reentrancy. + * @param _token Address of token. + * @param _owner Address of owner. + * @return Token balance of owner. + */ + function _getBalance(address _token, + address _owner) + internal + constant + returns (uint) + { + return ERC20(_token).balanceOf.gas(EXTERNAL_QUERY_GAS_LIMIT)(_owner); + } + + /* + * @dev Get allowance of token given to TokenTransferProxy by an address. + * The called token contract may attempt to change state, but will not be able to due to an added + * gas limit. Gas is limited to prevent reentrancy. + * @param _token Address of token. + * @param _owner Address of owner. + * @return Allowance of token given to TokenTransferProxy by owner. + */ + function _getAllowance(address _token, + address _owner) + internal + constant + returns (uint) + { + return ERC20(_token).allowance.gas(EXTERNAL_QUERY_GAS_LIMIT)( + _owner, + TOKEN_TRANSFER_PROXY_CONTRACT + ); + } + + /* + * @dev Checks if we can transfer xcert. + * @param _from Address of Xcert sender. + * @param _xcert Address of Xcert contract. + * @param _xcertId Id of Xcert (hashed certificate data that is transformed into uint256). + + @return Permission if we can transfer xcert. + */ + function _isAllowed(address _from, + address _xcert, + uint256 _xcertId) + internal + constant + returns (bool) + { + if(ERC721(_xcert).getApproved(_xcertId) == NFTOKEN_TRANSFER_PROXY_CONTRACT) + { + return true; + } + + if(ERC721(_xcert).isApprovedForAll(_from, NFTOKEN_TRANSFER_PROXY_CONTRACT)) + { + return true; + } + + return false; + } + + /** + * @dev Helper function that pays all the feeAmounts. + * @param _feeAddresses Addresses of all parties that need to get feeAmounts paid. + * @param _feeAmounts Fee amounts of all the _feeAddresses (length of both have to be the same). + * @param _to Address of the fee payer. + * @return Success of payments. + */ + function _payfeeAmounts(address[] _feeAddresses, + uint256[] _feeAmounts, + address _to) + internal + { + for(uint256 i; i < _feeAddresses.length; i++) + { + if(_feeAddresses[i] != address(0) && _feeAmounts[i] > 0) + { + require(_transferViaTokenTransferProxy( + XCT_TOKEN_CONTRACT, + _to, + _feeAddresses[i], + _feeAmounts[i] + )); + } + } + } +} diff --git a/contracts/exchange/MintableExchange.sol b/contracts/exchange/MintableExchange.sol new file mode 100644 index 0000000..96b7cd3 --- /dev/null +++ b/contracts/exchange/MintableExchange.sol @@ -0,0 +1,344 @@ +pragma solidity ^0.4.19; + + +import "../math/SafeMath.sol"; +import "../tokens/Xct.sol"; +import "../tokens/Xcert.sol"; +import "./TokenTransferProxy.sol"; +import "./XcertMintProxy.sol"; +import "./Exchange.sol"; + +/* + * @dev based on: https://github.com/0xProject/contracts/blob/master/contracts/Exchange.sol + */ +contract MintableExchange is Exchange{ + + /* + * @dev Enum of possible errors. + */ + enum ErrorsMint { + MINT_ALREADY_PERFORMED, // Mint has already beed performed. + MINT_CANCELLED // Mint was cancelled. + } + + /* + * @dev contract addresses + */ + address public XCERT_MINT_PROXY_CONTRACT; + + /* + * @dev Mapping of all canceled mints. + */ + mapping(bytes32 => bool) public mintCancelled; + + /* + * @dev Mapping of all performed mints. + */ + mapping(bytes32 => bool) public mintPerformed; + + /* + * @dev This event emmits when xcert gets mint directly to the taker. + */ + event LogPerformMint(address _to, + address _xcert, + uint256 _xcertId, + string _xcertUri, + address[] _feeAddresses, + uint256[] _feeAmounts, + uint256 _timestamp, + bytes32 _xcertMintClaim); + + /* + * @dev This event emmits when xcert mint order is canceled. + */ + event LogCancelMint(address _to, + address _xcert, + uint256 _xcertId, + string _xcertUri, + address[] _feeAddresses, + uint256[] _feeAmounts, + uint256 _timestamp, + bytes32 _xcertMintClaim); + + /* + * @dev Structure of data needed for mint. + */ + struct MintData{ + address owner; + address to; + address xcert; + uint256 xcertId; + string xcertUri; + address[] feeAddresses; + uint256[] feeAmounts; + uint256 timestamp; + bytes32 claim; + } + + /* + * @dev Sets XCT token address, Token proxy address and xcert Proxy address. + * @param _xcertToken Address pointing to XCT Token contract. + * @param _tokenTransferProxy Address pointing to TokenTransferProxy contract. + * @param _XcertProxy Address pointing to XcertProxy contract. + */ + function MintableExchange(address _xctToken, + address _tokenTransferProxy, + address _nfTokenTransferProxy, + address _xcertMintProxy) + Exchange (_xctToken, + _tokenTransferProxy, + _nfTokenTransferProxy) + public + { + XCERT_MINT_PROXY_CONTRACT = _xcertMintProxy; + } + + /* + * @dev Get addresses to all associated contracts (token, tokenTransferProxy, + * NFtokenTransferProxy, xcertMintProxy) . + * @return Array of addresses (token, tokenTransferProxy, nfTokenTransferProxy, xcertMintProxy) + */ + function getAddresses() + external + view + returns (address[]) + { + address[] memory addresses; + addresses[0] = XCT_TOKEN_CONTRACT; + addresses[1] = TOKEN_TRANSFER_PROXY_CONTRACT; + addresses[2] = NFTOKEN_TRANSFER_PROXY_CONTRACT; + addresses[3] = XCERT_MINT_PROXY_CONTRACT; + return addresses; + } + + + /* + * @dev Performs Xcert mint directly to the taker. + * @param _to Address of Xcert reciever. + * @param _xcert Address of Xcert contract. + * @param _xcertId Id of Xcert (hashed certificate data that is transformed into uint256). + * @param _xcertId Uri of Xcert (metadata uri). + * @param _feeAddresses Addresses of all parties that need to get feeAmounts paid. + * @param _feeAmounts Fee amounts of all the _feeAddresses (length of both have to be the same). + * @param _timestamp Timestamp that represents the salt. + * @param _v ECDSA signature parameter v. + * @param _r ECDSA signature parameters r. + * @param _s ECDSA signature parameters s. + * @param _s _throwIfNotMintable Test the mint before performing. + */ + function performMint(address _to, + address _xcert, + uint256 _xcertId, + string _xcertUri, + address[] _feeAddresses, + uint256[] _feeAmounts, + uint256 _timestamp, + uint8 _v, + bytes32 _r, + bytes32 _s, + bool _throwIfNotMintable) + public + returns (bool) + { + + MintData memory mintData = MintData({ + owner: _getOwner(_xcert), + to: _to, + xcert: _xcert, + xcertId: _xcertId, + xcertUri: _xcertUri, + feeAddresses: _feeAddresses, + feeAmounts: _feeAmounts, + timestamp: _timestamp, + claim: getMintDataClaim( + _to, + _xcert, + _xcertId, + _xcertUri, + _feeAddresses, + _feeAmounts, + _timestamp + ) + }); + + require(_feeAddresses.length == _feeAmounts.length); + require(_to == msg.sender); + require(mintData.owner != _to); + + require(isValidSignature( + mintData.owner, + mintData.claim, + _v, + _r, + _s + )); + + if(mintPerformed[mintData.claim]) + { + LogError(uint8(ErrorsMint.MINT_ALREADY_PERFORMED), mintData.claim); + return false; + } + + if(mintCancelled[mintData.claim]) + { + LogError(uint8(ErrorsMint.MINT_CANCELLED), mintData.claim); + return false; + } + + if (_throwIfNotMintable) + { + if(!_canPayFee(_to, _feeAmounts)) + { + LogError(uint8(Errors.INSUFFICIENT_BALANCE_OR_ALLOWANCE), mintData.claim); + return false; + } + + if(!_canMint(_xcert)) + { + LogError(uint8(Errors.NOT_XCERT_OWNER), mintData.claim); + return false; + } + } + + mintPerformed[mintData.claim] = true; + + require(_mintViaXcertMintProxy(mintData)); + + _payfeeAmounts(_feeAddresses, _feeAmounts, _to); + + LogPerformMint( + _to, + _xcert, + _xcertId, + _xcertUri, + _feeAddresses, + _feeAmounts, + _timestamp, + mintData.claim + ); + + return true; + } + + + /* + * @dev Cancels xcert mint. + * @param _to Address of Xcert reciever. + * @param _xcert Address of Xcert contract. + * @param _xcertId Id of Xcert (hashed certificate data that is transformed into uint256). + * @param _xcertId Uri of Xcert (metadata uri). + * @param _feeAddresses Addresses of all parties that need to get feeAmounts paid. + * @param _feeAmounts Fee amounts of all the _feeAddresses (length of both have to be the same). + * @param _timestamp Timestamp that represents the salt. + */ + function cancelMint(address _to, + address _xcert, + uint256 _xcertId, + string _xcertUri, + address[] _feeAddresses, + uint256[] _feeAmounts, + uint256 _timestamp) + public + { + require(msg.sender == _getOwner(_xcert)); + + bytes32 claim = getMintDataClaim( + _to, + _xcert, + _xcertId, + _xcertUri, + _feeAddresses, + _feeAmounts, + _timestamp + ); + + require(!mintPerformed[claim]); + + mintCancelled[claim] = true; + + LogCancelMint( + _to, + _xcert, + _xcertId, + _xcertUri, + _feeAddresses, + _feeAmounts, + _timestamp, + claim + ); + } + + /* + * @dev Calculates keccak-256 hash of mint data from parameters. + * @param _to Address of Xcert reciever. + * @param _xcert Address of Xcert contract. + * @param _xcertId Id of Xcert (claimed certificate data that is transformed into uint256). + * @param _xcertUri Uri poiting to Xcert metadata. + * @param _feeAddresses Addresses of all parties that need to get feeAmounts paid. + * @param _feeAmounts Fee amounts of all the _feeAddresses (length of both have to be the same). + * @param _timestamp Timestamp that represents the salt. + * @returns keccak-hash of mint data. + */ + function getMintDataClaim(address _to, + address _xcert, + uint256 _xcertId, + string _xcertUri, + address[] _feeAddresses, + uint256[] _feeAmounts, + uint256 _timestamp) + public + constant + returns (bytes32) + { + return keccak256( + address(this), + _to, + _xcert, + _xcertId, + _xcertUri, + _feeAddresses, + _feeAmounts, + _timestamp + ); + } + + /* + * @dev Mints new Xcert via XcertProxy using mint function. + * @param _xcert Address of Xcert to mint. + * @param _id Id of Xcert to mint. + * @param _uri Uri of Xcert to mint. + * @param _to Address receiving Xcert. + * @return Success of Xcert mint. + */ + function _mintViaXcertMintProxy(MintData _mintData) + internal + returns (bool) + { + return XcertMintProxy(XCERT_MINT_PROXY_CONTRACT) + .mint(_mintData.xcert, _mintData.xcertId, _mintData.xcertUri, _mintData.to); + } + + + /** + * @dev Checks if XcertMintProxy can mint specific _xcert. + */ + function _canMint(address _xcert) + internal + constant + returns (bool) + { + return Xcert(_xcert).isMintAuthorizedAddress(XCERT_MINT_PROXY_CONTRACT); + } + + /* + * @dev Gets xcert contract owner. + * @param _xcert Contract address. + */ + function _getOwner(address _xcert) + internal + constant + returns (address) + { + return Xcert(_xcert).owner(); + } +} diff --git a/contracts/exchange/NFTokenTransferProxy.sol b/contracts/exchange/NFTokenTransferProxy.sol new file mode 100644 index 0000000..564fd88 --- /dev/null +++ b/contracts/exchange/NFTokenTransferProxy.sol @@ -0,0 +1,95 @@ + +pragma solidity 0.4.19; + +/* + * @dev based od: https://github.com/0xProject/contracts/blob/master/contracts/TokenTransferProxy.sol + */ + + +import "../tokens/ERC721.sol"; +import "../ownership/Ownable.sol"; + +/// @title NFTokenTransferProxy - Transfers none-fundgible tokens on behalf of contracts that have been approved via decentralized governance. +contract NFTokenTransferProxy is Ownable { + + /// @dev Only authorized addresses can invoke functions with this modifier. + modifier onlyAuthorized { + require(authorized[msg.sender]); + _; + } + + modifier targetAuthorized(address target) { + require(authorized[target]); + _; + } + + modifier targetNotAuthorized(address target) { + require(!authorized[target]); + _; + } + + mapping (address => bool) public authorized; + address[] public authorities; + + event LogAuthorizedAddressAdded(address indexed target, address indexed caller); + event LogAuthorizedAddressRemoved(address indexed target, address indexed caller); + + /* + * Public functions + */ + + /// @dev Authorizes an address. + /// @param target Address to authorize. + function addAuthorizedAddress(address target) + public + onlyOwner + targetNotAuthorized(target) + { + authorized[target] = true; + authorities.push(target); + LogAuthorizedAddressAdded(target, msg.sender); + } + + /// @dev Removes authorizion of an address. + /// @param target Address to remove authorization from. + function removeAuthorizedAddress(address target) + public + onlyOwner + targetAuthorized(target) + { + delete authorized[target]; + for (uint i = 0; i < authorities.length; i++) { + if (authorities[i] == target) { + authorities[i] = authorities[authorities.length - 1]; + authorities.length -= 1; + break; + } + } + LogAuthorizedAddressRemoved(target, msg.sender); + } + + function transferFrom( + address _xcert, + address _from, + address _to, + uint256 _id) + external + onlyAuthorized + { + ERC721(_xcert).transferFrom(_from, _to, _id); + } + + /* + * Public constant functions + */ + + /// @dev Gets all authorized addresses. + /// @return Array of authorized addresses. + function getAuthorizedAddresses() + public + constant + returns (address[]) + { + return authorities; + } +} \ No newline at end of file diff --git a/contracts/exchange/TokenTransferProxy.sol b/contracts/exchange/TokenTransferProxy.sol new file mode 100644 index 0000000..96fbfa3 --- /dev/null +++ b/contracts/exchange/TokenTransferProxy.sol @@ -0,0 +1,114 @@ +/* + + Copyright 2017 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.19; + +import "../tokens/ERC20.sol"; +import "../ownership/Ownable.sol"; + +/// @title TokenTransferProxy - Transfers tokens on behalf of contracts that have been approved via decentralized governance. +/// @author Amir Bandeali - , Will Warren - +contract TokenTransferProxy is Ownable { + + /// @dev Only authorized addresses can invoke functions with this modifier. + modifier onlyAuthorized { + require(authorized[msg.sender]); + _; + } + + modifier targetAuthorized(address target) { + require(authorized[target]); + _; + } + + modifier targetNotAuthorized(address target) { + require(!authorized[target]); + _; + } + + mapping (address => bool) public authorized; + address[] public authorities; + + event LogAuthorizedAddressAdded(address indexed target, address indexed caller); + event LogAuthorizedAddressRemoved(address indexed target, address indexed caller); + + /* + * Public functions + */ + + /// @dev Authorizes an address. + /// @param target Address to authorize. + function addAuthorizedAddress(address target) + public + onlyOwner + targetNotAuthorized(target) + { + authorized[target] = true; + authorities.push(target); + LogAuthorizedAddressAdded(target, msg.sender); + } + + /// @dev Removes authorizion of an address. + /// @param target Address to remove authorization from. + function removeAuthorizedAddress(address target) + public + onlyOwner + targetAuthorized(target) + { + delete authorized[target]; + for (uint i = 0; i < authorities.length; i++) { + if (authorities[i] == target) { + authorities[i] = authorities[authorities.length - 1]; + authorities.length -= 1; + break; + } + } + LogAuthorizedAddressRemoved(target, msg.sender); + } + + /// @dev Calls into ERC20 Token contract, invoking transferFrom. + /// @param token Address of token to transfer. + /// @param from Address to transfer token from. + /// @param to Address to transfer token to. + /// @param value Amount of token to transfer. + /// @return Success of transfer. + function transferFrom(address token, + address from, + address to, + uint value) + public + onlyAuthorized + returns (bool) + { + return ERC20(token).transferFrom(from, to, value); + } + + /* + * Public constant functions + */ + + /// @dev Gets all authorized addresses. + /// @return Array of authorized addresses. + function getAuthorizedAddresses() + public + constant + returns (address[]) + { + return authorities; + } +} \ No newline at end of file diff --git a/contracts/exchange/XcertMintProxy.sol b/contracts/exchange/XcertMintProxy.sol new file mode 100644 index 0000000..4a8a97b --- /dev/null +++ b/contracts/exchange/XcertMintProxy.sol @@ -0,0 +1,95 @@ + +pragma solidity 0.4.19; + +/* + * @dev based od: https://github.com/0xProject/contracts/blob/master/contracts/TokenTransferProxy.sol + */ + + +import "../tokens/Xcert.sol"; +import "../ownership/Ownable.sol"; + +/// @title TokenTransferProxy - Transfers tokens on behalf of contracts that have been approved via decentralized governance. +contract XcertMintProxy is Ownable { + + /// @dev Only authorized addresses can invoke functions with this modifier. + modifier onlyAuthorized { + require(authorized[msg.sender]); + _; + } + + modifier targetAuthorized(address target) { + require(authorized[target]); + _; + } + + modifier targetNotAuthorized(address target) { + require(!authorized[target]); + _; + } + + mapping (address => bool) public authorized; + address[] public authorities; + + event LogAuthorizedAddressAdded(address indexed target, address indexed caller); + event LogAuthorizedAddressRemoved(address indexed target, address indexed caller); + + /* + * Public functions + */ + + /// @dev Authorizes an address. + /// @param target Address to authorize. + function addAuthorizedAddress(address target) + public + onlyOwner + targetNotAuthorized(target) + { + authorized[target] = true; + authorities.push(target); + LogAuthorizedAddressAdded(target, msg.sender); + } + + /// @dev Removes authorizion of an address. + /// @param target Address to remove authorization from. + function removeAuthorizedAddress(address target) + public + onlyOwner + targetAuthorized(target) + { + delete authorized[target]; + for (uint i = 0; i < authorities.length; i++) { + if (authorities[i] == target) { + authorities[i] = authorities[authorities.length - 1]; + authorities.length -= 1; + break; + } + } + LogAuthorizedAddressRemoved(target, msg.sender); + } + + function mint(address _xcert, + uint256 _id, + string _uri, + address _to) + external + onlyAuthorized + returns (bool) + { + return Xcert(_xcert).mint(_to, _id, _uri); + } + + /* + * Public constant functions + */ + + /// @dev Gets all authorized addresses. + /// @return Array of authorized addresses. + function getAuthorizedAddresses() + public + constant + returns (address[]) + { + return authorities; + } +} \ No newline at end of file diff --git a/contracts/tokens/ERC165.sol b/contracts/tokens/ERC165.sol new file mode 100644 index 0000000..7bb0a81 --- /dev/null +++ b/contracts/tokens/ERC165.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.19; + +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 + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} \ No newline at end of file diff --git a/contracts/tokens/ERC721.sol b/contracts/tokens/ERC721.sol new file mode 100644 index 0000000..70b518b --- /dev/null +++ b/contracts/tokens/ERC721.sol @@ -0,0 +1,98 @@ +pragma solidity ^0.4.19; + +/// @title ERC-721 Non-Fungible Token Standard +/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md +/// Note: the ERC-165 identifier for this interface is 0x6466353c +interface ERC721 /* is ERC165 */ { + /// @dev This emits when ownership of any NFT changes by any mechanism. + /// This event emits when NFTs are created (`from` == 0) and destroyed + /// (`to` == 0). Exception: during contract creation, any number of NFTs + /// may be created and assigned without emitting Transfer. At the time of + /// any transfer, the approved address for that NFT (if any) is reset to none. + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + + /// @dev This emits when the approved address for an NFT is changed or + /// reaffirmed. The zero address indicates there is no approved address. + /// When a Transfer event emits, this also indicates that the approved + /// address for that NFT (if any) is reset to none. + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + /// @dev This emits when an operator is enabled or disabled for an owner. + /// The operator can manage all NFTs of the owner. + event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); + + /// @notice Count all NFTs assigned to an owner + /// @dev NFTs assigned to the zero address are considered invalid, and this + /// function throws for queries about the zero address. + /// @param _owner An address for whom to query the balance + /// @return The number of NFTs owned by `_owner`, possibly zero + function balanceOf(address _owner) external view returns (uint256); + + /// @notice Find the owner of an NFT + /// @param _tokenId The identifier for an NFT + /// @dev NFTs assigned to zero address are considered invalid, and queries + /// about them do throw. + /// @return The address of the owner of the NFT + function ownerOf(uint256 _tokenId) external view returns (address); + + /// @notice Transfers the ownership of an NFT from one address to another address + /// @dev Throws unless `msg.sender` is the current owner, an authorized + /// operator, or the approved address for this NFT. Throws if `_from` is + /// not the current owner. Throws if `_to` is the zero address. Throws if + /// `_tokenId` is not a valid NFT. When transfer is complete, this function + /// checks if `_to` is a smart contract (code size > 0). If so, it calls + /// `onERC721Received` on `_to` and throws if the return value is not + /// `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`. + /// @param _from The current owner of the NFT + /// @param _to The new owner + /// @param _tokenId The NFT to transfer + /// @param data Additional data with no specified format, sent in call to `_to` + function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external; + + /// @notice Transfers the ownership of an NFT from one address to another address + /// @dev This works identically to the other function with an extra data parameter, + /// except this function just sets data to [] + /// @param _from The current owner of the NFT + /// @param _to The new owner + /// @param _tokenId The NFT to transfer + function safeTransferFrom(address _from, address _to, uint256 _tokenId) external; + + /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE + /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE + /// THEY MAY BE PERMANENTLY LOST + /// @dev Throws unless `msg.sender` is the current owner, an authorized + /// operator, or the approved address for this NFT. Throws if `_from` is + /// not the current owner. Throws if `_to` is the zero address. Throws if + /// `_tokenId` is not a valid NFT. + /// @param _from The current owner of the NFT + /// @param _to The new owner + /// @param _tokenId The NFT to transfer + function transferFrom(address _from, address _to, uint256 _tokenId) external; + + /// @notice Set or reaffirm the approved address for an NFT + /// @dev The zero address indicates there is no approved address. + /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized + /// operator of the current owner. + /// @param _approved The new approved NFT controller + /// @param _tokenId The NFT to approve + function approve(address _approved, uint256 _tokenId) external; + + /// @notice Enable or disable approval for a third party ("operator") to manage + /// all your asset. + /// @dev Emits the ApprovalForAll event + /// @param _operator Address to add to the set of authorized operators. + /// @param _approved True if the operators is approved, false to revoke approval + function setApprovalForAll(address _operator, bool _approved) external; + + /// @notice Get the approved address for a single NFT + /// @dev Throws if `_tokenId` is not a valid NFT + /// @param _tokenId The NFT to find the approved address for + /// @return The approved address for this NFT, or the zero address if there is none + function getApproved(uint256 _tokenId) public view returns (address); + + /// @notice Query if an address is an authorized operator for another address + /// @param _owner The address that owns the NFTs + /// @param _operator The address that acts on behalf of the owner + /// @return True if `_operator` is an approved operator for `_owner`, false otherwise + function isApprovedForAll(address _owner, address _operator) external view returns (bool); +} \ No newline at end of file diff --git a/contracts/tokens/ERC721Metadata.sol b/contracts/tokens/ERC721Metadata.sol new file mode 100644 index 0000000..1affe11 --- /dev/null +++ b/contracts/tokens/ERC721Metadata.sol @@ -0,0 +1,18 @@ +pragma solidity ^0.4.10; + +/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension +/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md +/// Note: the ERC-165 identifier for this interface is 0xTODO_ADD_THIS +interface ERC721Metadata /* is ERC721 */ { + /// @notice A descriptive name for a collection of NFTs in this contract + function name() external view returns (string _name); + + /// @notice An abbreviated name for NFTs in this contract + function symbol() external view returns (string _symbol); + + /// @notice A distinct Uniform Resource Identifier (URI) for a given asset. + /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC + /// 3986. The URI may point to a JSON file that conforms to the "ERC721 + /// Metadata JSON Schema". + function tokenURI(uint256 _tokenId) external view returns (string); +} diff --git a/contracts/tokens/ERC721TokenReceiver.sol b/contracts/tokens/ERC721TokenReceiver.sol new file mode 100644 index 0000000..e0e7282 --- /dev/null +++ b/contracts/tokens/ERC721TokenReceiver.sol @@ -0,0 +1,17 @@ +pragma solidity ^0.4.19; + +/// @dev Note: the ERC-165 identifier for this interface is 0xf0b9e5ba +interface ERC721TokenReceiver { + /// @notice Handle the receipt of an NFT + /// @dev The ERC721 smart contract calls this function on the recipient + /// after a `transfer`. This function MAY throw to revert and reject the + /// transfer. This function MUST use 50,000 gas or less. Return of other + /// than the magic value MUST result in the transaction being reverted. + /// Note: the contract address is always the message sender. + /// @param _from The sending address + /// @param _tokenId The NFT identifier which is being transfered + /// @param data Additional data with no specified format + /// @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))` + /// unless throwing + function onERC721Received(address _from, uint256 _tokenId, bytes data) external returns(bytes4); +} \ No newline at end of file diff --git a/contracts/tokens/Xcert.sol b/contracts/tokens/Xcert.sol new file mode 100644 index 0000000..9da2847 --- /dev/null +++ b/contracts/tokens/Xcert.sol @@ -0,0 +1,558 @@ +pragma solidity ^0.4.19; + +import "../math/SafeMath.sol"; +import "../ownership/Ownable.sol"; +import "./ERC721.sol"; +import "./ERC721Metadata.sol"; +import "./ERC165.sol"; +import "./ERC721TokenReceiver.sol"; + +/* + * @title None-fungable token. + * @dev Xcert is an implementation of EIP721 and EIP721Metadata. This contract follows + * the implementation at goo.gl/FLaJc9. + */ +contract Xcert is Ownable, ERC721, ERC721Metadata, ERC165 { + using SafeMath for uint256; + + /* + * @dev NFToken issuer name. + */ + string private issuerName; + + /* + * @dev NFToken issuer symbol. + */ + string private issuerSymbol; + + /* + * @dev A mapping from NFToken ID to the address that owns it. + */ + mapping (uint256 => address) private idToOwner; + + /* + * @dev Mapping from NFToken ID to approved address. + */ + mapping (uint256 => address) private idToApprovals; + + /* + * @dev Mapping from owner address to count of his tokens. + */ + mapping (address => uint256) private ownerToNFTokenCount; + + /* + * @dev Mapping from owner address to mapping of operator addresses. + */ + mapping (address => mapping (address => bool)) private ownerToOperators; + + /* + * @dev Mapping from NFToken ID to metadata uri. + */ + mapping (uint256 => string) private idToUri; + + /* + * @dev Mapping of supported intefraces. + * You must not set element 0xffffffff to true. + */ + mapping(bytes4 => bool) internal supportedInterfaces; + + /* + * @dev Mapping of addresses authorized to mint new NFTokens. + */ + mapping (address => bool) private addressToMintAuthorized; + + /* + * @dev Check for recieved transfer to a smart contract. + */ + bytes4 private constant MAGIC_ONERC721RECEIVED = bytes4( + keccak256("onERC721Received(address,uint256,bytes)") + ); + + /* + * @dev This emits when ownership of any NFT changes by any mechanism. + * This event emits when NFTs are created (`from` == 0) and destroyed + * (`to` == 0). Exception: during contract creation, any number of NFTs + * may be created and assigned without emitting Transfer. At the time of + * any transfer, the approved address for that NFT (if any) is reset to none. + */ + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + + /* + * @dev This emits when the approved address for an NFT is changed or + * reaffirmed. The zero address indicates there is no approved address. + * When a Transfer event emits, this also indicates that the approved + * address for that NFT (if any) is reset to none. + */ + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + /* + * @dev This emits when an operator is enabled or disabled for an owner. + * The operator can manage all NFTs of the owner. + */ + event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); + + /* + * @dev This emits when an address is given authorization to mint new NFTokens or the + * authorization is revoked. + * The _target can mint new NFTokens. + */ + event MintAuthorizedAddress(address indexed _target, bool _authorized); + + /* + * @dev Guarantees that the msg.sender is an owner or operator of the given NFToken. + * @param _tokenId ID of the NFToken to validate. + */ + modifier canOperate(uint256 _tokenId) { + address owner = idToOwner[_tokenId]; + require(owner == msg.sender || ownerToOperators[owner][msg.sender]); + _; + } + + /* + * @dev Guarantees that the msg.sender is allowed to transfer NFToken. + * @param _tokenId ID of the NFToken to transfer. + */ + modifier canTransfer(uint256 _tokenId) { + address owner = idToOwner[_tokenId]; + require( + owner == msg.sender + || getApproved(_tokenId) == msg.sender + || ownerToOperators[owner][msg.sender] + ); + + _; + } + + /* + * @dev Guarantees that _tokenId is a valid Token. + * @param _tokenId ID of the NFToken to validate. + */ + modifier validNFToken(uint256 _tokenId) { + require(idToOwner[_tokenId] != address(0)); + _; + } + + /* + * @dev Guarantees that msg.sender is allowed to mint a new NFToken. + */ + modifier canMint() { + require(msg.sender == owner || addressToMintAuthorized[msg.sender]); + _; + } + + /* + * @dev Contract constructor. + * @param _name Name of the NFToken issuer. + * @param _symbol Symbol of the NFToken issuer. + */ + function Xcert(string _name, string _symbol) + public + { + issuerName = _name; + issuerSymbol = _symbol; + supportedInterfaces[0x01ffc9a7] = true; // ERC165 + supportedInterfaces[0x6466353c] = true; // ERC721 + supportedInterfaces[0x5b5e139f] = true; // ERC721Metadata + } + + /* + * @dev Returns the count of all NFTokens assigent to owner. + * @param _owner Address where we are interested in NFTokens owned by them. + */ + function balanceOf(address _owner) + external + view + returns (uint256) + { + require(_owner != address(0)); + return ownerToNFTokenCount[_owner]; + } + + /* + * @notice Find the owner of a NFToken. + * @param _tokenId The identifier for a NFToken we are inspecting. + */ + function ownerOf(uint256 _tokenId) + external + view + returns (address _owner) + { + _owner = idToOwner[_tokenId]; + require(_owner != address(0)); + } + + /* + * @notice Transfers the ownership of an NFT from one address to another address + * @dev Throws unless `msg.sender` is the current owner, an authorized + * operator, or the approved address for this NFT. Throws if `_from` is + * not the current owner. Throws if `_to` is the zero address. Throws if + * `_tokenId` is not a valid NFT. When transfer is complete, this function + * checks if `_to` is a smart contract (code size > 0). If so, it calls + * `onERC721Received` on `_to` and throws if the return value is not + * `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`. + * @param _from The current owner of the NFT + * @param _to The new owner + * @param _tokenId The NFT to transfer + * @param data Additional data with no specified format, sent in call to `_to` + */ + function safeTransferFrom(address _from, + address _to, + uint256 _tokenId, + bytes data) + external + { + _safeTransferFrom(_from, _to, _tokenId, data); + } + + /* + * @notice Transfers the ownership of an NFT from one address to another address + * @dev This works identically to the other function with an extra data parameter, + * except this function just sets data to [] + * @param _from The current owner of the NFT + * @param _to The new owner + * @param _tokenId The NFT to transfer + */ + function safeTransferFrom(address _from, + address _to, + uint256 _tokenId) + external + { + _safeTransferFrom(_from, _to, _tokenId, ""); + } + + /* + * @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE + * TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE + * THEY MAY BE PERMANENTLY LOST + * @dev Throws unless `msg.sender` is the current owner, an authorized + * operator, or the approved address for this NFT. Throws if `_from` is + * not the current owner. Throws if `_to` is the zero address. Throws if + * `_tokenId` is not a valid NFT. + * @param _from The current owner of the NFT + * @param _to The new owner + * @param _tokenId The NFT to transfer + */ + function transferFrom(address _from, + address _to, + uint256 _tokenId) + external + canTransfer(_tokenId) + validNFToken(_tokenId) + { + address owner = idToOwner[_tokenId]; + require(owner == _from); + require(_to != address(0)); + + _transfer(_to, _tokenId); + } + + /* + * @dev Approves another address to claim for the ownership of the given NFToken ID. + * @param _to Address to be approved for the given NFToken ID. + * @param _tokenId ID of the token to be approved. + */ + function approve(address _approved, uint256 _tokenId) + external + canOperate(_tokenId) + validNFToken(_tokenId) + { + address owner = idToOwner[_tokenId]; + require(_approved != owner); + require(!(getApproved(_tokenId) == address(0) && _approved == address(0))); + + idToApprovals[_tokenId] = _approved; + Approval(owner, _approved, _tokenId); + } + + /* + * @notice Enable or disable approval for a third party ("operator") to manage + * all your asset. + * @dev Emits the ApprovalForAll event + * @param _operator Address to add to the set of authorized operators. + * @param _approved True if the operators is approved, false to revoke approval + */ + function setApprovalForAll(address _operator, + bool _approved) + external + { + require(_operator != address(0)); + ownerToOperators[msg.sender][_operator] = _approved; + ApprovalForAll(msg.sender, _operator, _approved); + } + + /* + * @dev Returns an address currently approved to take ownership of the given NFToken ID. + * @param _tokenId ID of the NFToken to query the approval of. + */ + function getApproved(uint256 _tokenId) + public + view + validNFToken(_tokenId) + returns (address) + { + return idToApprovals[_tokenId]; + } + + /* + * @notice Query if an address is an authorized operator for another address + * @param _owner The address that owns the NFTs + * @param _operator The address that acts on behalf of the owner + * @return True if `_operator` is an approved operator for `_owner`, false otherwise + */ + function isApprovedForAll(address _owner, + address _operator) + external + view + returns (bool) + { + require(_owner != address(0)); + require(_operator != address(0)); + return ownerToOperators[_owner][_operator]; + } + + /* + * @dev Actually perform the safeTransferFrom. + * @param _from The current owner of the NFT + * @param _to The new owner + * @param _tokenId The NFT to transfer + * @param data Additional data with no specified format, sent in call to `_to` + */ + function _safeTransferFrom(address _from, + address _to, + uint256 _tokenId, + bytes _data) + internal + canTransfer(_tokenId) + validNFToken(_tokenId) + { + address owner = idToOwner[_tokenId]; + require(owner == _from); + require(_to != address(0)); + + _transfer(_to, _tokenId); + + // Do the callback after everything is done to avoid reentrancy attack + uint256 codeSize; + assembly { codeSize := extcodesize(_to) } + if (codeSize == 0) { + return; + } + bytes4 retval = ERC721TokenReceiver(_to).onERC721Received(_from, _tokenId, _data); + require(retval == MAGIC_ONERC721RECEIVED); + } + + /* + * @dev Actually preforms the transfer. Does NO checks. + * @param _to Address of a new owner. + * @param _tokenId The NFToken that is being transferred. + */ + function _transfer(address _to, uint256 _tokenId) + private + { + address from = idToOwner[_tokenId]; + + clearApproval(from, _tokenId); + removeNFToken(from, _tokenId); + addNFToken(_to, _tokenId); + + Transfer(from, _to, _tokenId); + } + + /* + * @dev Mints a new NFToken. + * @param _to The address that will own the minted NFToken. + * @param _id of the NFToken to be minted by the msg.sender. + * @param _uri that points to NFToken metadata (optional, max length 2083). + */ + function mint(address _to, + uint256 _id, + string _uri) + external + canMint() + returns (bool) + { + require(_to != address(0)); + require(_id != 0); + require(idToOwner[_id] == address(0)); + require(utfStringLength(_uri) <= 2083); + + idToUri[_id] = _uri; + addNFToken(_to, _id); + + Transfer(address(0), _to, _id); + return true; + } + + /* + * @dev Burns a specified NFToken. + * @param _tokenId Id of the NFToken we want to burn. + */ + function burn(uint256 _tokenId) + canOperate(_tokenId) + validNFToken(_tokenId) + external + { + if (getApproved(_tokenId) != 0) { + clearApproval(msg.sender, _tokenId); + } + + removeNFToken(msg.sender, _tokenId); + delete idToUri[_tokenId]; + + Transfer(msg.sender, address(0), _tokenId); + } + + /* + * @dev Clears the current approval of a given NFToken ID. + * @param _tokenId ID of the NFToken to be transferred. + */ + function clearApproval(address _owner, uint256 _tokenId) + private + { + require(idToOwner[_tokenId] == _owner); + delete idToApprovals[_tokenId]; + Approval(_owner, 0, _tokenId); + } + + /* + * @dev Removes a NFToken from owner. + * @param _from Address from wich we want to remove the NFToken. + * @param _tokenId Which NFToken we want to remove. + */ + function removeNFToken(address _from, uint256 _tokenId) + private + { + require(idToOwner[_tokenId] == _from); + + ownerToNFTokenCount[_from] = ownerToNFTokenCount[_from].sub(1); + delete idToOwner[_tokenId]; + } + + /* + * @dev Assignes a new NFToken to owner. + * @param _To Address to wich we want to add the NFToken. + * @param _tokenId Which NFToken we want to add. + */ + function addNFToken(address _to, uint256 _tokenId) + private + { + require(idToOwner[_tokenId] == address(0)); + + idToOwner[_tokenId] = _to; + ownerToNFTokenCount[_to] = ownerToNFTokenCount[_to].add(1); + } + + /* + * @dev Calculates string length. This function is taken from https://goo.gl/dLgN7k. + * A string is basically identical to bytes only that it is assumed to hold the UTF-8 encoding + * of a real string. Since string stores the data in UTF-8 encoding it is quite expensive to + * compute the number of characters in the string (the encoding of some characters takes more than + * a single byte). Because of that, string s; s.length is not yet supported and not even index + * access s[2]. But if you want to access the low-level byte encoding of the string, you can use + * bytes(s).length and bytes(s)[2] which will result in the number of bytes in the UTF-8 encoding + * of the string (not the number of characters) and the second byte (not character) of the UTF-8 + * encoded string, respectively. + * This function takes the bytes and shifts them to check value and calculate te appropriate + * length. Details can be found at https://goo.gl/MzagzL. + * @param str UTF string we want the length of. + */ + function utfStringLength(string _str) + private + pure + returns (uint256 length) + { + uint256 i = 0; + bytes memory stringRep = bytes(_str); + + while (i < stringRep.length) { + if (stringRep[i] >> 7 == 0) { + i += 1; + } else if (stringRep[i] >> 5 == 0x6) { + i += 2; + } else if (stringRep[i] >> 4 == 0xE) { + i += 3; + } else if (stringRep[i] >> 3 == 0x1E) { + i += 4; + } else { + i += 1; + } + length++; + } + } + + /* + * @dev Returns a descriptive name for a collection of NFTokens. + */ + function name() + external + view + returns (string _name) + { + _name = issuerName; + } + + /* + * @notice Returns nn abbreviated name for NFTokens. + */ + function symbol() + external + view + returns (string _symbol) + { + _symbol = issuerSymbol; + } + + /* + * @dev A distinct URI (RFC 3986) for a given NFToken. + * @param _tokenId Id for which we want uri. + */ + function tokenURI(uint256 _tokenId) + external + view + returns (string) + { + require(idToOwner[_tokenId] != address(0)); + return idToUri[_tokenId]; + } + + /* + * @dev Function to check which interfaces are suported by this contract. + * @param interfaceID If of the interface. + */ + function supportsInterface(bytes4 interfaceID) + external + view + returns (bool) + { + return supportedInterfaces[interfaceID]; + } + + /* + * @dev Sets mint authorised address. + * @param _target Address to set authorized state. + * @patam _authorized True if the _target is authorised, false to revoke authorization. + */ + function setMintAuthorizedAddress(address _target, + bool _authorized) + external + onlyOwner + { + require(_target != address(0)); + addressToMintAuthorized[_target] = _authorized; + MintAuthorizedAddress(_target, _authorized); + } + + /* + * @dev Sets mint authorised address. + * @param _target Address for which we want to check if it is authorized. + * @return Is authorized or not. + */ + function isMintAuthorizedAddress(address _target) + external + view + returns (bool) + { + require(_target != address(0)); + return addressToMintAuthorized[_target]; + } +} diff --git a/contracts/tokens/Xct.sol b/contracts/tokens/Xct.sol new file mode 100644 index 0000000..8a703bb --- /dev/null +++ b/contracts/tokens/Xct.sol @@ -0,0 +1,238 @@ +pragma solidity ^0.4.19; + +import "../math/SafeMath.sol"; +import "../ownership/Ownable.sol"; + +/* + * @title XCT protocol token. + * @dev Standard ERC20 token used by the protocol. This contract follows the + * implementation at https://goo.gl/64yCkF. + */ +contract Xct is Ownable { + using SafeMath for uint256; + + /** + * Token name. + */ + string public name; + + /** + * Token symbol. + */ + string public symbol; + + /** + * Nubber of decimals. + */ + uint8 public decimals; + + /** + * Ballance information map. + */ + mapping (address => uint256) internal balances; + + /** + * Number of tokens in circulation. + */ + uint256 internal currentSupply; + + /** + * Allowance information map. + */ + mapping (address => mapping (address => uint256)) internal allowed; + + /** + * Transfer feature state. + */ + bool public transferEnabled; + + /** + * @dev An event which is triggered when funds are transfered. + * @param _from The address sending tokens. + * @param _to The address recieving tokens. + * @param _value The amount of transferred tokens. + */ + event Transfer(address indexed _from, address indexed _to, uint256 _value); + + /** + * @dev An event which is triggered when an address to spend the specified amount of + * tokens on behalf is approved. + * @param _owner The address of an owner. + * @param _spender The address which spent the funds. + * @param _value The amount of spent tokens. + */ + event Approval(address indexed _owner, address indexed _spender, uint256 _value); + + /** + * @dev An event which is triggered when tokens are burned. + * @param _burner The address which burns tokens. + * @param _value The amount of burned tokens. + */ + event Burn(address indexed _burner, uint256 _value); + + /** + * @dev Assures that the provided address is a valid destination to transfer tokens to. + * @param _to Target address. + */ + modifier validDestination(address _to) { + require(_to != address(0x0)); + require(_to != address(this)); + _; + } + + /** + * @dev Assures that tokens can be transfered. + */ + modifier onlyWhenTransferAllowed() { + require(transferEnabled); + _; + } + + /** + * @dev Contract constructor. + */ + function Xct() + public + { + name = "0xcert Protocol Token"; + symbol = "XCT"; + decimals = 18; + currentSupply = 400000000000000000000000000; + transferEnabled = false; + + balances[owner] = currentSupply; + Transfer(address(0x0), owner, currentSupply); + } + + /** + * @dev Returns the total number of tokens in circulation. This function is based on BasicToken + * implementation at goo.gl/GZEhaq. + */ + function totalSupply() + public + view + returns (uint256) + { + return currentSupply; + } + + /** + * @dev transfer token for a specified address + * @param _to The address to transfer to. + * @param _value The amount to be transferred. + */ + function transfer(address _to, uint256 _value) + onlyWhenTransferAllowed() + validDestination(_to) + public + returns (bool) + { + require(_value <= balances[msg.sender]); + + balances[msg.sender] = balances[msg.sender].sub(_value); // will fail on insufficient funds + balances[_to] = balances[_to].add(_value); + + Transfer(msg.sender, _to, _value); + return true; + } + + /** + * @dev Transfer tokens from one address to another. + * @param _from address The address which you want to send tokens from. + * @param _to address The address which you want to transfer to. + * @param _value uint256 the amount of tokens to be transferred. + */ + function transferFrom(address _from, address _to, uint256 _value) + onlyWhenTransferAllowed() + validDestination(_to) + public + returns (bool) + { + require(_value <= balances[_from]); + require(_value <= allowed[_from][msg.sender]); + + balances[_from] = balances[_from].sub(_value); // will fail on insufficient funds + balances[_to] = balances[_to].add(_value); + allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); + + Transfer(_from, _to, _value); + return true; + } + + /** + * @dev Gets the balance of the specified address. + * @param _owner The address to query the the balance of. + */ + function balanceOf(address _owner) + public + view + returns (uint256) + { + return balances[_owner]; + } + + /** + * @dev Approves the passed address to spend the specified amount of tokens on behalf + * of the msg.sender. This function is based on StandardToken implementation at goo.gl/GZEhaq + * and goo.gl/fG8R4i. + * To change the approve amount you first have to reduce the spender's allowance to zero by + * calling `approve(_spender, 0)` if it is not already 0 to mitigate the race condition described + * here https://goo.gl/7n9A4J. + * @param _spender The address which will spend the funds. + * @param _value The allowed amount of tokens to be spent. + */ + function approve(address _spender, uint256 _value) + public + returns (bool) + { + require((_value == 0) || (allowed[msg.sender][_spender] == 0)); + + allowed[msg.sender][_spender] = _value; + + Approval(msg.sender, _spender, _value); + return true; + } + + /** + * @dev Returns the amount of tokens that a spender can transfer on behalf of an owner. This + * function is based on StandardToken implementation at goo.gl/GZEhaq. + * @param _owner The address which owns the funds. + * @param _spender The address which will spend the funds. + */ + function allowance(address _owner, address _spender) + public + view + returns (uint256) + { + return allowed[_owner][_spender]; + } + + /** + * @dev Enables token transfers. + */ + function enableTransfer() + onlyOwner() + external + { + transferEnabled = true; + } + + /** + * @dev Burns a specific amount of tokens. Only owner is allowed to perform this operation. This + * function is based on BurnableToken implementation at goo.gl/GZEhaq. + * @param _value The amount of token to be burned. + */ + function burn(uint256 _value) + onlyOwner() + public + { + require(_value <= balances[msg.sender]); + + balances[owner] = balances[owner].sub(_value); + currentSupply = currentSupply.sub(_value); + + Burn(owner, _value); + Transfer(owner, address(0x0), _value); + } + +} diff --git a/package-lock.json b/package-lock.json index 9c10eb9..6e2d44d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,16 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "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" + } + }, "ajv": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", @@ -22,6 +32,24 @@ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "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 + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, "async": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", @@ -31,12 +59,46 @@ "lodash": "4.17.5" } }, + "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", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, "bignumber.js": { "version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", "dev": true @@ -47,6 +109,50 @@ "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=", "dev": true }, + "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.2", + "iconv-lite": "0.4.19", + "on-finished": "2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "dev": true, + "requires": { + "hoek": "4.2.1" + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -57,24 +163,65 @@ "concat-map": "0.0.1" } }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, "browser-stdout": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", "dev": true }, + "browserify-sha3": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/browserify-sha3/-/browserify-sha3-0.0.1.tgz", + "integrity": "sha1-P/NKMAbvFcD7NWflQbkaI0ASPRE=", + "dev": true, + "requires": { + "js-sha3": "0.3.1" + }, + "dependencies": { + "js-sha3": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.3.1.tgz", + "integrity": "sha1-hhIoAhQvCChQKg0d7h2V4lO7AkM=", + "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 + }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", "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 + }, "camelcase": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", "dev": true }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -98,6 +245,15 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, "commander": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", @@ -110,12 +266,81 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "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 + }, + "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-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "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" + } + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "dev": true, + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "dev": true, + "requires": { + "hoek": "4.2.1" + } + } + } + }, "crypto-js": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.8.tgz", "integrity": "sha1-cV8HC/YBTyrpkqmLOSkli3E/CNU=", "dev": true }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, "debug": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", @@ -131,12 +356,88 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "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 + }, "diff": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", "dev": true }, + "dom-walk": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "elliptic": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "dev": true, + "requires": { + "bn.js": "4.11.6", + "brorand": "1.1.0", + "hash.js": "1.1.3", + "hmac-drbg": "1.0.1", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0", + "minimalistic-crypto-utils": "1.0.1" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, "error-ex": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", @@ -146,12 +447,39 @@ "is-arrayish": "0.2.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.2", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", "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.6", + "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" + } + }, "ethjs-abi": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.1.8.tgz", @@ -163,6 +491,16 @@ "number-to-bn": "1.7.0" } }, + "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" + } + }, "ethjs-util": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.4.tgz", @@ -173,6 +511,85 @@ "strip-hex-prefix": "1.0.0" } }, + "express": { + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", + "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", + "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.0", + "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.1", + "serve-static": "1.13.1", + "setprototypeof": "1.1.0", + "statuses": "1.3.1", + "type-is": "1.6.16", + "utils-merge": "1.0.1", + "vary": "1.1.2" + }, + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "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 + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + } + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, "fast-deep-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", @@ -185,6 +602,44 @@ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", "dev": true }, + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "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.3.1", + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + } + } + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -194,6 +649,44 @@ "locate-path": "2.0.0" } }, + "for-each": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz", + "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=", + "dev": true, + "requires": { + "is-function": "1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "dev": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "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 + }, "fs-extra": { "version": "0.30.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", @@ -219,6 +712,15 @@ "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", "dev": true }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, "glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", @@ -229,6 +731,16 @@ "minimatch": "0.3.0" } }, + "global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "dev": true, + "requires": { + "min-document": "2.19.0", + "process": "0.5.2" + } + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", @@ -247,24 +759,116 @@ "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", "dev": true }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "dev": true, + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, "has-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", "dev": true }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0" + } + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "dev": true, + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.1", + "sntp": "2.1.0" + } + }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "1.1.3", + "minimalistic-assert": "1.0.0", + "minimalistic-crypto-utils": "1.0.1" + } + }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", + "dev": true + }, "hosted-git-info": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", "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" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "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 + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -287,6 +891,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", @@ -311,18 +921,36 @@ "number-is-nan": "1.0.1" } }, + "is-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", + "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=", + "dev": true + }, "is-hex-prefixed": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", "integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=", "dev": true }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", "dev": true }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, "jade": { "version": "0.26.3", "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", @@ -353,12 +981,31 @@ "integrity": "sha1-uvDA6MVK1ZA0R9+Wreekobynmko=", "dev": true }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", "dev": true }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, "json3": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", @@ -374,6 +1021,28 @@ "graceful-fs": "4.1.11" } }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "keccakjs": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/keccakjs/-/keccakjs-0.2.1.tgz", + "integrity": "sha1-HWM6+QfvMFu/ny+mFtVsRFYd+k0=", + "dev": true, + "requires": { + "browserify-sha3": "0.0.1", + "sha3": "1.2.0" + } + }, "klaw": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", @@ -501,12 +1170,78 @@ "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", "dev": true }, + "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 + }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", "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 + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "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.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" + } + }, + "mimic-response": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz", + "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=", + "dev": true + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "dev": true, + "requires": { + "dom-walk": "0.1.1" + } + }, + "minimalistic-assert": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", + "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, "minimatch": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", @@ -556,6 +1291,24 @@ "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", "dev": true }, + "nan": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.9.2.tgz", + "integrity": "sha512-ltW65co7f3PQWBDbqVvaU1WtFJUsNW7sWWm4HINhbMQIyVyzIeyZ8toX5TC5eeooE6piZoaEh4cZkueSKG3KYw==", + "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 + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -584,6 +1337,27 @@ "strip-hex-prefix": "1.0.0" } }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "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", @@ -632,6 +1406,16 @@ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, + "parse-headers": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.1.tgz", + "integrity": "sha1-aug6eqJanZtwCswoaYzR8e1+lTY=", + "dev": true, + "requires": { + "for-each": "0.3.2", + "trim": "0.0.1" + } + }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -641,6 +1425,12 @@ "error-ex": "1.3.1" } }, + "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": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -653,6 +1443,12 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "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", @@ -670,6 +1466,12 @@ "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=", "dev": true }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -691,6 +1493,69 @@ "pinkie": "2.0.4" } }, + "process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=", + "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" + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "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 + }, + "query-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.0.tgz", + "integrity": "sha512-F3DkxxlY0AqD/rwe4YAwjRE2HjOkKW7TxsuteyrS/Jbwrxw887PqYBL4sWUJ9D/V1hmFns0SCD6FDyvlwo9RCQ==", + "dev": true, + "requires": { + "decode-uri-component": "0.2.0", + "object-assign": "4.1.1", + "strict-uri-encode": "1.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.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" + } + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -733,6 +1598,36 @@ } } }, + "request": { + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", + "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "dev": true, + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -785,24 +1680,140 @@ } } }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "dev": true }, + "send": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", + "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", + "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.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "2.3.0", + "range-parser": "1.2.0", + "statuses": "1.3.1" + }, + "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" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + } + } + }, + "serve-static": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", + "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", + "dev": true, + "requires": { + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "parseurl": "1.3.2", + "send": "0.16.1" + } + }, + "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.2", + "cors": "2.8.4", + "express": "4.16.2", + "request": "2.83.0", + "xhr": "2.4.1" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true + }, + "sha3": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sha3/-/sha3-1.2.0.tgz", + "integrity": "sha1-aYnxtwpJhwWHajc+LGKs6WqpOZo=", + "dev": true, + "requires": { + "nan": "2.9.2" + } + }, "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", "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": "2.7.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.7.0.tgz", + "integrity": "sha512-RkE9rGPHcxYZ/baYmgJtOSM63vH0Vyq+ma5TijBcLla41SWlh8t6XYIGMR/oeZcmr+/G8k+zrClkkVrtnQ0esg==", + "dev": true, + "requires": { + "decompress-response": "3.3.0", + "once": "1.4.0", + "simple-concat": "1.0.0" + } + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "dev": true, + "requires": { + "hoek": "4.2.1" + } + }, "solc": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.19.tgz", @@ -848,6 +1859,34 @@ "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", "dev": true }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dev": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -859,6 +1898,12 @@ "strip-ansi": "3.0.1" } }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -892,16 +1937,37 @@ "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", "dev": true }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true + }, "to-iso-string": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", "dev": true }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "dev": true, + "requires": { + "punycode": "1.4.1" + } + }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", + "dev": true + }, "truffle": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/truffle/-/truffle-4.0.6.tgz", - "integrity": "sha512-E4u1dZr2IGY4liulO/nGMtavx4jVLXIJp48lxFq54N+gMRGhmBQp5kf1etA3bYhHVtO9IO76qRiHMMVuId7cRg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/truffle/-/truffle-4.1.0.tgz", + "integrity": "sha512-6uArrTfq3Pe/pq2qjtsOUDtG4O4JEucnHjcHBPRYIY5Ra7bjFhYjqNFZ+66mZECLgLwLmrObviJRchXX0dkSSQ==", "dev": true, "requires": { "mocha": "3.5.3", @@ -1112,12 +2178,74 @@ "integrity": "sha1-4igPXoF/i/QnVlf9D5rr1E9aJ4Y=", "dev": true }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": 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" + } + }, + "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.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "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 + }, "utf8": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz", "integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY=", "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.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "dev": true + }, "validate-npm-package-license": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", @@ -1128,6 +2256,23 @@ "spdx-expression-parse": "1.0.4" } }, + "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", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, "web3": { "version": "0.20.5", "resolved": "https://registry.npmjs.org/web3/-/web3-0.20.5.tgz", @@ -1141,6 +2286,29 @@ "xmlhttprequest": "1.8.0" } }, + "web3-utils": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.0.0-beta.30.tgz", + "integrity": "sha1-6uQIzI1tb+zI1Ql8/q1Rdz8jH/k=", + "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": { + "utf8": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.1.tgz", + "integrity": "sha1-LgHbAvfY0JRPdxBPFgnrDDBM92g=", + "dev": true + } + } + }, "which-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", @@ -1169,6 +2337,53 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "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.1", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.4.1.tgz", + "integrity": "sha512-pAIU5vBr9Hiy5cpFIbPnwf0C18ZF86DBsZKrlsf87N5De/JbA6RJ83UP/cv+aljl4S40iRVMqP4pr4sF9Dnj0A==", + "dev": true, + "requires": { + "global": "4.3.2", + "is-function": "1.0.1", + "parse-headers": "2.0.1", + "xtend": "4.0.1" + } + }, + "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.0", + "simple-get": "2.7.0", + "timed-out": "4.0.1", + "url-set-query": "1.0.0", + "xhr": "2.4.1" + } + }, + "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", @@ -1181,6 +2396,12 @@ "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", "dev": true }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", diff --git a/package.json b/package.json index 9a9a1a0..fac4868 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "license": "MIT", "devDependencies": { "ethjs-util": "0.1.4", - "truffle": "4.0.6", - "truffle-flattener": "1.2.0" + "truffle": "^4.1.0", + "truffle-flattener": "1.2.0", + "web3-utils": "^1.0.0-beta.30" } } diff --git a/test/exchange/Exchange.test.js b/test/exchange/Exchange.test.js new file mode 100644 index 0000000..ae1c3db --- /dev/null +++ b/test/exchange/Exchange.test.js @@ -0,0 +1,260 @@ +const Exchange = artifacts.require('Exchange'); +const NFTokenTransferProxy = artifacts.require('NFTokenTransferProxy'); +const TokenTransferProxy = artifacts.require('TokenTransferProxy'); +const Xcert = artifacts.require('Xcert'); +const Xct = artifacts.require('Xct'); +const util = require('ethjs-util'); +const web3Util = require('web3-utils'); +const assertRevert = require('../helpers/assertRevert'); + +contract('Exchange', (accounts) => { + let exchange; + let nfTokenProxy; + let tokenProxy; + let token; + let xcert + let id1 = web3.sha3('test1'); + let id2 = web3.sha3('test2'); + let id3 = web3.sha3('test3'); + + beforeEach(async () => { + nfTokenProxy = await NFTokenTransferProxy.new(); + tokenProxy = await TokenTransferProxy.new(); + token = await Xct.new(); + xcert = await Xcert.new('Foo', 'F'); + + await token.enableTransfer(); + await token.transfer(accounts[1], 200); + await token.transfer(accounts[2], 200); + await token.transfer(accounts[3], 200); + + await xcert.mint(accounts[1], id1, 'url'); + await xcert.mint(accounts[2], id2, 'url2'); + await xcert.mint(accounts[3], id3, 'url3'); + + exchange = await Exchange.new(token.address, tokenProxy.address, nfTokenProxy.address); + nfTokenProxy.addAuthorizedAddress(exchange.address); + tokenProxy.addAuthorizedAddress(exchange.address); + }); + + describe('hashing', function () { + var testArrayAccount = [accounts[3], accounts[5]]; + var testArrayAmount = [1, 10]; + it('compares the same local and contract hash', async () => { + var contractHash = await exchange.getTransferDataClaim(accounts[0], accounts[1], accounts[2], 1, testArrayAccount, testArrayAmount, 123); + var localHash = web3Util.soliditySha3(exchange.address,accounts[0], accounts[1], accounts[2], 1, {t: 'address[]', v:testArrayAccount}, {t: 'uint256[]', v:testArrayAmount}, 123); + assert.equal(contractHash, localHash); + }); + + it('compares different local and contract hash', async () => { + var contractHash = await exchange.getTransferDataClaim(accounts[0], accounts[1], accounts[2], 1, testArrayAccount, testArrayAmount, 123); + var localHash = web3Util.soliditySha3(exchange.address, accounts[0], accounts[1], accounts[2], 1, {t: 'address[]', v:testArrayAccount}, {t: 'uint256[]', v:testArrayAmount}, 124); + assert.notEqual(contractHash, localHash); + }); + }); + + describe('signature', function () { + var testArray = [1,2]; + var hash; + var r; + var s; + var v; + + beforeEach(async () => { + hash = await exchange.getTransferDataClaim(accounts[0], accounts[1], accounts[2], 1, testArray, testArray, 123); + var signature = web3.eth.sign(accounts[0], hash); + + r = signature.substr(0, 66); + s = '0x' + signature.substr(66, 64); + v = parseInt('0x' + signature.substr(130, 2)) + 27; + }); + + it('correctly validates correct signer', async () => { + var valid = await exchange.isValidSignature(accounts[0], hash, v, r, s); + assert.equal(valid, true); + }); + + it('correctly validates wrong signer', async () => { + var valid = await exchange.isValidSignature(accounts[1], hash, v, r, s); + assert.equal(valid, false); + }); + + it('correctly validates wrong signature data', async () => { + var valid = await exchange.isValidSignature(accounts[0], hash, 1, 2, 3); + assert.equal(valid, false); + }); + + it('correctly validates signature data from another accout', async () => { + var signature = web3.eth.sign(accounts[1], hash); + + r = signature.substr(0, 66); + s = '0x' + signature.substr(66, 64); + v = parseInt('0x' + signature.substr(130, 2)) + 27; + + var valid = await exchange.isValidSignature(accounts[0],hash,v,r,s); + assert.equal(valid, false); + + var valid = await exchange.isValidSignature(accounts[1],hash,v,r,s); + assert.equal(valid, true); + }); + }); + + + describe('transfer', function () { + + describe('valid signature', function () { + var r; + var s; + var v; + var timestamp; + var addressArray = [accounts[1]]; + var amountArray = [20]; + + var from = accounts[1]; + var to = accounts[2]; + var thirdParty = accounts[3]; + + beforeEach(async () => { + timestamp = 234235345325; + var hash = web3Util.soliditySha3(exchange.address, from, to, xcert.address, id1, {t: 'address[]', v:addressArray}, {t: 'uint256[]', v:amountArray}, timestamp); + var signature = web3.eth.sign(from, hash); + + r = signature.substr(0, 66); + s = '0x' + signature.substr(66, 64); + v = parseInt('0x' + signature.substr(130, 2)) + 27; + }); + + describe('cancel', function () { + + it('successfuly cancels transfer', async () => { + var { logs } = await exchange.cancelTransfer(from, to, xcert.address, id1, addressArray, amountArray, timestamp, {from: from}); + + let cancelEvent = logs.find(e => e.event === 'LogCancelTransfer'); + assert.notEqual(cancelEvent, undefined); + }); + + it('throws when someone else then the transfer sender tries to cancel it', async () => { + await assertRevert(exchange.cancelTransfer(from, to, xcert.address, id1, addressArray, amountArray, timestamp, {from: thirdParty})); + }); + + it('throws when trying to cancel an already performed transfer', async () => { + + await token.approve(tokenProxy.address, 20, {from: to}); + await xcert.approve(nfTokenProxy.address, id1, {from: from}); + + let { logs } = await exchange.performTransfer(from, to, xcert.address, id1, addressArray, amountArray, timestamp, v, r, s, false, {from: to}); + + let event = logs.find(e => e.event === 'LogPerformTransfer'); + assert.notEqual(event, undefined); + + await assertRevert(exchange.cancelTransfer(from, to, xcert.address, id1, addressArray, amountArray, timestamp, {from: to})); + }); + + }); + + describe('perform', function () { + + describe('checks enabled', function () { + + it('should transfer successfuly', async () => { + + await token.approve(tokenProxy.address, 20, {from: to}); + await xcert.approve(nfTokenProxy.address, id1, {from: from}); + + let { logs } = await exchange.performTransfer(from, to, xcert.address, id1, addressArray, amountArray, timestamp, v, r, s, true, {from: to}); + + let event = logs.find(e => e.event === 'LogPerformTransfer'); + assert.notEqual(event, undefined); + + var owner = await xcert.ownerOf(id1); + var tokenAmountAcc1 = await token.balanceOf(from); + var tokenAmountAcc2 = await token.balanceOf(to); + + assert.equal(owner, to); + assert.equal(tokenAmountAcc1, 220); + assert.equal(tokenAmountAcc2, 180); + + }); + + it('should fail with unsofficient allowence', async () => { + + await token.approve(tokenProxy.address, 10, {from: to}); + await xcert.approve(nfTokenProxy.address, id1, {from: from}); + + let { logs } = await exchange.performTransfer(from, to, xcert.address, id1, addressArray, amountArray, timestamp, v, r, s, true, {from: to}); + + let event = logs.find(e => e.event === 'LogError'); + assert.notEqual(event, undefined); + + }); + + it('should fail when not allowed to transfer NFToken', async () => { + await token.approve(tokenProxy.address, 10, {from: to}); + + let { logs } = await exchange.performTransfer(from, to, xcert.address, id1, addressArray, amountArray, timestamp, v, r, s, true, {from: to}); + + let event = logs.find(e => e.event === 'LogError'); + assert.notEqual(event, undefined); + }); + + it('throws when fee amount array is no the same length then feeRecipient', async () => { + await assertRevert(exchange.performTransfer(from, to, xcert.address, id1, addressArray, [20,10], timestamp, v, r, s, true, {from: to})); + }); + + it('throws when _to address is not the one performing transfer', async () => { + await assertRevert(exchange.performTransfer(from, to, xcert.address, id1, addressArray, amountArray, timestamp, v, r, s, true, {from: thirdParty})); + }); + + it('throws when _to and _from addresses are the same', async () => { + await assertRevert(exchange.performTransfer(to, to, xcert.address, id1, addressArray, amountArray, timestamp, v, r, s, true, {from: to})); + }); + + it('fails trying to perfom an already performed transfer', async () => { + + await token.approve(tokenProxy.address, 20, {from: to}); + await xcert.approve(nfTokenProxy.address, id1, {from: from}); + + var { logs } = await exchange.performTransfer(from, to, xcert.address, id1, addressArray, amountArray, timestamp, v, r, s, true, {from: to}); + + let transferEvent = logs.find(e => e.event === 'LogPerformTransfer'); + assert.notEqual(transferEvent, undefined); + + var { logs } = await exchange.performTransfer(from, to, xcert.address, id1, addressArray, amountArray, timestamp, v, r, s, true, {from: to}); + let errorEvent = logs.find(e => e.event === 'LogError'); + assert.notEqual(errorEvent, undefined); + }); + + it('fails trying to perform canceled transfer', async () => { + + var { logs } = await exchange.cancelTransfer(from, to, xcert.address, id1, addressArray, amountArray, timestamp, {from: from}); + + let cancelEvent = logs.find(e => e.event === 'LogCancelTransfer'); + assert.notEqual(cancelEvent, undefined); + + var { logs } = await exchange.performTransfer(from, to, xcert.address, id1, addressArray, amountArray, timestamp, v, r, s, true, {from: to}); + + let errorEvent = logs.find(e => e.event === 'LogError'); + assert.notEqual(errorEvent, undefined); + }); + + }); + + describe('checks disabled', function () { + it('throws because of unsofficient allowence', async () => { + await token.approve(tokenProxy.address, 10, {from: to}); + await xcert.approve(nfTokenProxy.address, id1, {from: from}); + + await assertRevert(exchange.performTransfer(from, to, xcert.address, id1, addressArray, amountArray, timestamp, v, r, s, false, {from: to})); + }); + + it('throws when not allowed to transfer NFToken', async () => { + await token.approve(tokenProxy.address, 10, {from: to}); + + await assertRevert(exchange.performTransfer(from, to, xcert.address, id1, addressArray, amountArray, timestamp, v, r, s, false, {from: to})); + }); + + }); + }); + }); + }); +}); diff --git a/test/exchange/MintableExchange.test.js b/test/exchange/MintableExchange.test.js new file mode 100644 index 0000000..3047f71 --- /dev/null +++ b/test/exchange/MintableExchange.test.js @@ -0,0 +1,200 @@ +const Exchange = artifacts.require('MintableExchange'); +const NFTokenTransferProxy = artifacts.require('NFTokenTransferProxy'); +const TokenTransferProxy = artifacts.require('TokenTransferProxy'); +const XcertMintProxy = artifacts.require('XcertMintProxy'); +const Xcert = artifacts.require('Xcert'); +const Xct = artifacts.require('Xct'); +const util = require('ethjs-util'); +const web3Util = require('web3-utils'); +const assertRevert = require('../helpers/assertRevert'); + +contract('MintableExchange', (accounts) => { + let exchange; + let nfTokenProxy; + let tokenProxy; + let mintProxy; + let token; + let xcert + let id1 = web3.sha3('test1'); + let id2 = web3.sha3('test2'); + let id3 = web3.sha3('test3'); + let uri = "http://url.com" + + beforeEach(async () => { + nfTokenProxy = await NFTokenTransferProxy.new(); + tokenProxy = await TokenTransferProxy.new(); + mintProxy = await XcertMintProxy.new(); + token = await Xct.new(); + xcert = await Xcert.new('Foo', 'F'); + + await token.enableTransfer(); + await token.transfer(accounts[1], 200); + await token.transfer(accounts[2], 200); + await token.transfer(accounts[3], 200); + + exchange = await Exchange.new(token.address, tokenProxy.address, nfTokenProxy.address, mintProxy.address); + nfTokenProxy.addAuthorizedAddress(exchange.address); + tokenProxy.addAuthorizedAddress(exchange.address); + mintProxy.addAuthorizedAddress(exchange.address); + }); + + describe('hashing', function () { + var testArrayAccount = [accounts[3], accounts[5]]; + var testArrayAmount = [1, 10]; + it('compares the same local and contract hash', async () => { + var contractHash = await exchange.getMintDataClaim(accounts[1], accounts[2], id1, uri, testArrayAccount, testArrayAmount, 123); + var localHash = web3Util.soliditySha3(exchange.address, accounts[1], accounts[2], id1, uri, {t: 'address[]', v:testArrayAccount}, {t: 'uint256[]', v:testArrayAmount}, 123); + assert.equal(contractHash, localHash); + }); + + it('compares different local and contract hash', async () => { + var contractHash = await exchange.getMintDataClaim(accounts[1], accounts[2], id1, uri, testArrayAccount, testArrayAmount, 123); + var localHash = web3Util.soliditySha3(exchange.address, accounts[1], accounts[2], id1, uri, {t: 'address[]', v:testArrayAccount}, {t: 'uint256[]', v:testArrayAmount}, 124); + assert.notEqual(contractHash, localHash); + }); + }); + + + describe('mint', function () { + + describe('valid signature', function () { + var r; + var s; + var v; + var timestamp; + var addressArray = [accounts[1]]; + var amountArray = [20]; + + var owner = accounts[0]; + var to = accounts[2]; + var thirdParty = accounts[3]; + + beforeEach(async () => { + timestamp = 234235345325; + var hash = web3Util.soliditySha3(exchange.address, to, xcert.address, id1, uri, {t: 'address[]', v:addressArray}, {t: 'uint256[]', v:amountArray}, timestamp); + var signature = web3.eth.sign(owner, hash); + + r = signature.substr(0, 66); + s = '0x' + signature.substr(66, 64); + v = parseInt('0x' + signature.substr(130, 2)) + 27; + }); + + describe('cancel', function () { + + it('successfuly cancels mint', async () => { + var { logs } = await exchange.cancelMint(to, xcert.address, id1, uri, addressArray, amountArray, timestamp, {from: owner}); + + let cancelEvent = logs.find(e => e.event === 'LogCancelMint'); + assert.notEqual(cancelEvent, undefined); + }); + + it('throws when someone else then the minter tries to cancel it', async () => { + await assertRevert(exchange.cancelMint(to, xcert.address, id1, uri, addressArray, amountArray, timestamp, {from: thirdParty})); + }); + + it('throws when trying to cancel an already performed mint', async () => { + + await token.approve(tokenProxy.address, 20, {from: to}); + await xcert.setMintAuthorizedAddress(mintProxy.address, true, {from: owner}); + + let { logs } = await exchange.performMint(to, xcert.address, id1, uri, addressArray, amountArray, timestamp, v, r, s, false, {from: to}); + + let event = logs.find(e => e.event === 'LogPerformMint'); + assert.notEqual(event, undefined); + + await assertRevert(exchange.cancelMint(to, xcert.address, id1, uri, addressArray, amountArray, timestamp, {from: owner})); + }); + + }); + + describe('perform', function () { + + describe('checks enabled', function () { + + it('mints correctly', async () => { + await token.approve(tokenProxy.address, 20, {from: to}); + await xcert.setMintAuthorizedAddress(mintProxy.address, true, {from: owner}); + + let { logs } = await exchange.performMint(to, xcert.address, id1, uri, addressArray, amountArray, timestamp, v, r, s, true, {from: to}); + + let event = logs.find(e => e.event === 'LogPerformMint'); + assert.notEqual(event, undefined); + + var tokenOwner = await xcert.ownerOf(id1); + assert.equal(tokenOwner, to); + + var tokenAmountAcc1 = await token.balanceOf(accounts[1]); + var tokenAmountAcc2 = await token.balanceOf(to); + + assert.equal(tokenAmountAcc1, 220); + assert.equal(tokenAmountAcc2, 180); + }); + + it('throws if msg.sender is not the receiver', async () => { + await token.approve(tokenProxy.address, 20, {from: to}); + await xcert.setMintAuthorizedAddress(mintProxy.address, true, {from: owner}); + + await assertRevert(exchange.performMint(to, xcert.address, id1, uri, addressArray, amountArray, timestamp, v, r, s, true, {from: thirdParty})); + }); + + it('fails when trying to perform canceled mint', async () => { + await exchange.cancelMint(to, xcert.address, id1, uri, addressArray, amountArray, timestamp, {from: owner}); + await token.approve(tokenProxy.address, 20, {from: to}); + await xcert.setMintAuthorizedAddress(mintProxy.address, true, {from: owner}); + + let { logs } = await exchange.performMint(to, xcert.address, id1, uri, addressArray, amountArray, timestamp, v, r, s, true, {from: to}); + + let event = logs.find(e => e.event === 'LogError'); + assert.notEqual(event, undefined); + }); + + it('fails when trying to perform already performed mint', async () => { + await token.approve(tokenProxy.address, 20, {from: to}); + await xcert.setMintAuthorizedAddress(mintProxy.address, true, {from: owner}); + await exchange.performMint(to, xcert.address, id1, uri, addressArray, amountArray, timestamp, v, r, s, true, {from: to}); + let { logs } = await exchange.performMint(to, xcert.address, id1, uri, addressArray, amountArray, timestamp, v, r, s, true, {from: to}); + + let event = logs.find(e => e.event === 'LogError'); + assert.notEqual(event, undefined); + }); + + it('fails when approved token amount is not sufficient', async () => { + await token.approve(tokenProxy.address, 10, {from: to}); + await xcert.setMintAuthorizedAddress(mintProxy.address, true, {from: owner}); + let { logs } = await exchange.performMint(to, xcert.address, id1, uri, addressArray, amountArray, timestamp, v, r, s, true, {from: to}); + + let event = logs.find(e => e.event === 'LogError'); + assert.notEqual(event, undefined); + }); + + it('fails when does not have mint rights', async () => { + await token.approve(tokenProxy.address, 20, {from: to}); + let { logs } = await exchange.performMint(to, xcert.address, id1, uri, addressArray, amountArray, timestamp, v, r, s, true, {from: to}); + + let event = logs.find(e => e.event === 'LogError'); + assert.notEqual(event, undefined); + }); + + }); + + describe('checks disabled', function () { + + it('throws when approved token amount is not sufficient', async () => { + await token.approve(tokenProxy.address, 10, {from: to}); + await xcert.setMintAuthorizedAddress(mintProxy.address, true, {from: owner}); + await assertRevert(exchange.performMint(to, xcert.address, id1, uri, addressArray, amountArray, timestamp, v, r, s, false, {from: to})); + }); + + it('throws when does not have mint rights', async () => { + await token.approve(tokenProxy.address, 20, {from: to}); + await assertRevert(exchange.performMint(to, xcert.address, id1, uri, addressArray, amountArray, timestamp, v, r, s, false, {from: to})); + }); + }); + + }); + + }); + + }); + +}); diff --git a/test/tokens/Xcert.test.js b/test/tokens/Xcert.test.js new file mode 100644 index 0000000..3d9f5cf --- /dev/null +++ b/test/tokens/Xcert.test.js @@ -0,0 +1,294 @@ +const Xcert = artifacts.require('Xcert'); +const util = require('ethjs-util'); +const assertRevert = require('../helpers/assertRevert'); + +contract('Xcert', (accounts) => { + let xcert; + let id1 = web3.sha3('test1'); + let id2 = web3.sha3('test2'); + let id3 = web3.sha3('test3'); + let id4 = web3.sha3('test4'); + + beforeEach(async function () { + xcert = await Xcert.new('Foo', 'F'); + }); + + it('returns correct balanceOf after mint', async () => { + await xcert.mint(accounts[0],id1,'url'); + const count = await xcert.balanceOf(accounts[0]); + assert.equal(count.toNumber(), 1); + }); + + it('throws when trying to mint 2 NFTokens with the same claim', async () => { + await xcert.mint(accounts[0], id2, 'url2'); + await assertRevert(xcert.mint(accounts[0], id2, 'url2')); + }); + + it('throws trying to mint NFToken with empty claim', async () => { + await assertRevert(xcert.mint(accounts[0], '', '')); + }); + + it('throws when trying to mint NFToken to 0x0 address ', async () => { + await assertRevert(xcert.mint('0', id3, '')); + }); + + it('throws when trying to mint NFToken from non owner ot authorized address', async () => { + await assertRevert(xcert.mint('0', id3, '', { from: accounts[1] })); + }); + + it('throws when trying to mint NFToken with uri length more then 2083 chars', async () => { + await assertRevert(xcert.mint(accounts[0], id4, 'sdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfddfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfdsdfsdfsdfsdfsdfsdfdsfdsfsdfsdfsdfdsfdsfdsfdsfdsfdsfsdfdsfsdfsdfd')); + }); + + it('correctly authotizes address for minting', async () => { + var { logs } = await xcert.setMintAuthorizedAddress(accounts[1], true); + let mintAuthorizedAddressEvent = logs.find(e => e.event === 'MintAuthorizedAddress'); + assert.notEqual(mintAuthorizedAddressEvent, undefined); + }); + + it('throws when someone else then the owner tries to authotize address ', async () => { + await assertRevert(xcert.setMintAuthorizedAddress(accounts[1], true, {from: accounts[2]})); + }); + + it('throws when trying to authorize zero address', async () => { + await assertRevert(xcert.setMintAuthorizedAddress('0', true)); + }); + + it('correctly mints new NFToken by authorized address', async () => { + var authorized = accounts[1]; + var recipient = accounts[2]; + await xcert.setMintAuthorizedAddress(authorized, true); + await xcert.mint(recipient, id3, 'url3', {from: authorized}); + + const count = await xcert.balanceOf(recipient); + assert.equal(count.toNumber(), 1); + }); + + it('throws trying to ming from address which authorization got revoked', async () => { + var authorized = accounts[1]; + var recipient = accounts[2]; + await xcert.setMintAuthorizedAddress(authorized, true); + await xcert.setMintAuthorizedAddress(authorized, false); + await assertRevert(xcert.mint(recipient, id3, 'url3', {from: authorized})); + }); + + it('finds the correct amount of NFTokens owned by account', async () => { + await xcert.mint(accounts[1], id2, 'url2'); + await xcert.mint(accounts[1], id3, 'url3'); + const count = await xcert.balanceOf(accounts[1]); + assert.equal(count.toNumber(), 2); + }); + + it('throws when trying to get count of NFTokens owned by 0x0 address', async () => { + await assertRevert(xcert.balanceOf('0')); + }); + + it('finds the correct owner of NFToken id', async () => { + await xcert.mint(accounts[1], id2, 'url2'); + const address = await xcert.ownerOf(id2); + assert.equal(address, accounts[1]); + }); + + it('throws when trying to find owner od none existant NFToken id', async () => { + await assertRevert(xcert.ownerOf(id4)); + }); + + it('correctly approves account', async () => { + await xcert.mint(accounts[0], id2, 'url2'); + await xcert.approve(accounts[1], id2); + const address = await xcert.getApproved(id2); + assert.equal(address, accounts[1]); + }); + + it('correctly cancels approval of account[1]', async () => { + await xcert.mint(accounts[0], id2, 'url2'); + await xcert.approve(accounts[1], id2); + await xcert.approve(0, id2); + const address = await xcert.getApproved(id2); + assert.equal(address, 0); + }); + + it('throws when trying to get approval of none existant NFToken id', async () => { + await assertRevert(xcert.getApproved(id4)); + }); + + + it('throws when trying to approve NFToken id that we are not the owner of', async () => { + await xcert.mint(accounts[1], id2, 'url2'); + await assertRevert(xcert.approve(accounts[1], id2)); + const address = await xcert.getApproved(id2); + assert.equal(address, 0); + }); + + it('correctly sets an operator', async () => { + var { logs } = await xcert.setApprovalForAll(accounts[6], true); + let approvalForAllEvent = logs.find(e => e.event === 'ApprovalForAll'); + assert.notEqual(approvalForAllEvent, undefined); + + var isApprovedForAll = await xcert.isApprovedForAll(accounts[0], accounts[6]); + assert.equal(isApprovedForAll, true); + }); + + it('correctly sets than cancels an operator', async () => { + await xcert.setApprovalForAll(accounts[6], true); + await xcert.setApprovalForAll(accounts[6], false); + + var isApprovedForAll = await xcert.isApprovedForAll(accounts[0], accounts[6]); + assert.equal(isApprovedForAll, false); + }); + + it('throws when trying to set a zero address as operator', async () => { + await assertRevert(xcert.setApprovalForAll(0, true)); + }); + + it('corectly transfers NFToken from owner', async () => { + var sender = accounts[1]; + var recipient = accounts[2]; + + await xcert.mint(sender, id2, 'url2'); + var { logs } = await xcert.transferFrom(sender, recipient, id2, {from: sender}); + let transferEvent = logs.find(e => e.event === 'Transfer'); + assert.notEqual(transferEvent, undefined); + + var senderBalance = await xcert.balanceOf(sender); + var recipientBalance = await xcert.balanceOf(recipient); + var ownerOfId2 = await xcert.ownerOf(id2); + + assert.equal(senderBalance, 0); + assert.equal(recipientBalance, 1); + assert.equal(ownerOfId2, recipient); + }); + + it('corectly transfers xcert from approved address', async () => { + var sender = accounts[1]; + var recipient = accounts[2]; + var owner = accounts[3]; + + await xcert.mint(owner, id2, 'url2'); + await xcert.approve(sender, id2, {from: owner}); + var { logs } = await xcert.transferFrom(owner, recipient, id2, {from: sender}); + let transferEvent = logs.find(e => e.event === 'Transfer'); + assert.notEqual(transferEvent, undefined); + + var ownerBalance = await xcert.balanceOf(owner); + var recipientBalance = await xcert.balanceOf(recipient); + var ownerOfId2 = await xcert.ownerOf(id2); + + assert.equal(ownerBalance, 0); + assert.equal(recipientBalance, 1); + assert.equal(ownerOfId2, recipient); + }); + + it('corectly transfers NFToken as operator', async () => { + var sender = accounts[1]; + var recipient = accounts[2]; + var owner = accounts[3]; + + await xcert.mint(owner, id2, 'url2'); + await xcert.setApprovalForAll(sender, true, {from: owner}); + var { logs } = await xcert.transferFrom(owner, recipient, id2, {from: sender}); + let transferEvent = logs.find(e => e.event === 'Transfer'); + assert.notEqual(transferEvent, undefined); + + var ownerBalance = await xcert.balanceOf(owner); + var recipientBalance = await xcert.balanceOf(recipient); + var ownerOfId2 = await xcert.ownerOf(id2); + + assert.equal(ownerBalance, 0); + assert.equal(recipientBalance, 1); + assert.equal(ownerOfId2, recipient); + }); + + it('throws when trying to transfer NFToken as an address that is not owner, approved or operator', async () => { + var sender = accounts[1]; + var recipient = accounts[2]; + var owner = accounts[3]; + + await xcert.mint(owner, id2, 'url2'); + await assertRevert(xcert.transferFrom(owner, recipient, id2, {from: sender})); + }); + + it('throws when trying to transfer NFToken to a zero address', async () => { + var owner = accounts[3]; + + await xcert.mint(owner, id2, 'url2'); + await assertRevert(xcert.transferFrom(owner, 0, id2, {from: owner})); + }); + + it('throws when trying to transfer a non valid xcert', async () => { + var owner = accounts[3]; + var recipient = accounts[2]; + + await xcert.mint(owner, id2, 'url2'); + await assertRevert(xcert.transferFrom(owner, 0, id3, {from: owner})); + }); + + // tests skipped because of trufflesuite/truffle#737 + /*it('corectly safe transfers NFToken from owner', async () => { + var sender = accounts[1]; + var recipient = accounts[2]; + + await xcert.mint(sender, id2, 'url2'); + var { logs } = await xcert.safeTransferFrom(sender, recipient, id2, {from: sender}); + let transferEvent = logs.find(e => e.event === 'Transfer'); + assert.notEqual(transferEvent, undefined); + + var senderBalance = await xcert.balanceOf(sender); + var recipientBalance = await xcert.balanceOf(recipient); + var ownerOfId2 = await xcert.ownerOf(id2); + + assert.equal(senderBalance, 0); + assert.equal(recipientBalance, 1); + assert.equal(ownerOfId2, recipient); + }); + + it('throws when trying to safe transfers NFToken from owner to a smart contract', async () => { + var sender = accounts[1]; + var recipient = xcert.address; + + await xcert.mint(sender, id2, 'url2'); + await assertRevert(xcert.safeTransferFrom(sender, recipient, id2, {from: sender})); + });*/ + + it('returns the correct issuer name', async () => { + const name = await xcert.name(); + assert.equal(name, 'Foo'); + }); + + it('returns the correct issuer symbol', async () => { + const symbol = await xcert.symbol(); + assert.equal(symbol, 'F'); + }); + + it('returns the correct NFToken id 2 url', async () => { + await xcert.mint(accounts[1], id2, 'url2'); + const tokenURI = await xcert.tokenURI(id2); + assert.equal(tokenURI, 'url2'); + }); + + it('throws when trying to get uri of none existant NFToken id', async () => { + await assertRevert(xcert.tokenURI(id4)); + }); + + it('destroys NFToken id 1', async () => { + await xcert.mint(accounts[0], id1, 'url1'); + await xcert.burn(id1); + const count = await xcert.balanceOf(accounts[0]); + assert.equal(count, 0); + }); + + it('throws when trying to destory an already destroyed NFToken id 1', async () => { + await xcert.mint(accounts[0], id1, 'url1'); + await xcert.burn(id1); + await assertRevert(xcert.burn(id1)); + const count = await xcert.balanceOf(accounts[0]); + assert.equal(count, 0); + }); + + it('throws when trying to destory NFToken you are not the owner of', async () => { + await xcert.mint(accounts[1], id2, 'url2'); + await assertRevert(xcert.burn(id2)); + const count = await xcert.balanceOf(accounts[1]); + assert.equal(count, 1); + }); +}); diff --git a/test/tokens/Xct.test.js b/test/tokens/Xct.test.js new file mode 100644 index 0000000..fef1255 --- /dev/null +++ b/test/tokens/Xct.test.js @@ -0,0 +1,158 @@ +const Xct = artifacts.require('./Xct.sol'); +const assertRevert = require('../helpers/assertRevert'); + +contract('erc/Xct', (accounts) => { + let token; + let owner = accounts[0]; + let totalSupply = new web3.BigNumber('4e+26'); + + beforeEach(async () => { + token = await Xct.new(); + }); + + it('has correct totalSupply after construction', async () => { + let actualSupply = await token.totalSupply(); + assert.equal(actualSupply.toString(), totalSupply.toString()); + }); + + it('returns correct balances after transfer', async () => { + await token.enableTransfer(); + await token.transfer(accounts[1], 100); + let firstAccountBalance = await token.balanceOf(owner); + let secondAccountBalance = await token.balanceOf(accounts[1]); + assert.equal(firstAccountBalance.toString(), totalSupply.minus(100).toString()); + assert.equal(secondAccountBalance, 100); + }); + + it('emits Transfer event on transfer', async () => { + await token.enableTransfer(); + let { logs } = await token.transfer(accounts[1], 100); + let event = logs.find(e => e.event === 'Transfer'); + assert.notEqual(event, undefined); + }); + + it('throws when trying to transfer before transfer is enabled', async () => { + await assertRevert(token.transfer(accounts[1], 100)); + }); + + it('throws when trying to transfer more than available balance', async () => { + let moreThanBalance = totalSupply.plus(1); + await token.enableTransfer(); + await assertRevert(token.transfer(accounts[1], moreThanBalance)); + }); + + it('throws when trying to transfer to 0x0', async () => { + await token.enableTransfer(); + await assertRevert(token.transfer(0x0, 100)); + }); + + it('throws when trying to transfer to contract address', async () => { + await token.enableTransfer(); + await assertRevert(token.transfer(token.address, 100)); + }); + + it('returns the correct allowance amount after approval', async () => { + await token.approve(accounts[1], 100); + let allowance = await token.allowance(owner, accounts[1]); + assert.equal(allowance, 100); + }); + + it('emits Approval event after approval', async () => { + let { logs } = await token.approve(accounts[1], 100); + let event = logs.find(e => e.event === 'Approval'); + assert.notEqual(event, undefined); + }); + + it('returns correct balances after transfering from another account', async () => { + await token.enableTransfer(); + await token.approve(accounts[1], 100); + await token.transferFrom(owner, accounts[2], 100, { from: accounts[1] }); + let balance0 = await token.balanceOf(owner); + let balance1 = await token.balanceOf(accounts[2]); + let balance2 = await token.balanceOf(accounts[1]); + assert.equal(balance0.toString(), totalSupply.minus(100).toString()); + assert.equal(balance1, 100); + assert.equal(balance2, 0); + }); + + it('emits Transfer event on transferFrom', async () => { + await token.enableTransfer(); + await token.approve(accounts[1], 100); + let { logs } = await token.transferFrom(owner, accounts[2], 100, { from: accounts[1] }); + let event = logs.find(e => e.event === 'Transfer'); + assert.notEqual(event, undefined); + }); + + it('throws when trying to transfer more than allowed amount', async () => { + await token.enableTransfer(); + await token.approve(accounts[1], 99); + await assertRevert(token.transferFrom(owner, accounts[2], 100, { from: accounts[1] })); + }); + + it('throws an error when trying to transferFrom more than _from has', async () => { + await token.enableTransfer(); + let balance0 = await token.balanceOf(owner); + await token.approve(accounts[1], 99); + await assertRevert(token.transferFrom(owner, accounts[2], balance0.toNumber() + 1, { from: accounts[1] })); + }); + + it('returns 0 allowance by default', async () => { + let preApproved = await token.allowance(owner, accounts[1]); + assert.equal(preApproved, 0); + }); + + it('increases and decreases allowance after approval', async () => { + await token.approve(accounts[1], 50); + let postIncrease = await token.allowance(owner, accounts[1]); + assert.equal(postIncrease.toString(), '50'); + await token.approve(accounts[1], 0); + await token.approve(accounts[1], 40); + let postDecrease = await token.allowance(owner, accounts[1]); + assert.equal(postIncrease.minus(10).toString(), postDecrease.toString()); + }); + + it('throws when approving without setting it to 0 first', async () => { + await token.approve(accounts[1], 50); + await assertRevert(token.approve(accounts[1], 100)); + }); + + it('throws when trying to transferFrom before transfers enabled', async () => { + await token.approve(accounts[1], 100); + await assertRevert(token.transferFrom(owner, accounts[2], 100, { from: accounts[1] })); + }); + + it('throws when trying to transferFrom to 0x0', async () => { + await token.enableTransfer(); + await token.approve(accounts[1], 100); + await assertRevert(token.transferFrom(owner, 0x0, 100, { from: accounts[1] })); + }); + + it('throws when trying to transferFrom to contract address', async () => { + await token.enableTransfer(); + await token.approve(accounts[1], 100); + await assertRevert(token.transferFrom(owner, token.address, 100, { from: accounts[1] })); + }); + + it('allows token burning by the owner', async () => { + await token.enableTransfer(); + let { logs } = await token.burn(1, {from: owner}); + + let balance = await token.balanceOf(owner); + assert.equal(balance.toString(), totalSupply.minus(1).toString()); + + let actualSupply = await token.totalSupply(); + assert.equal(actualSupply.toString(), totalSupply.minus(1).toString()); + + let event = logs.find(e => e.event === 'Burn'); + assert.notEqual(event, undefined); + }); + + it('allows only owner to burn tokens', async () => { + await assertRevert(token.burn(1, { from: accounts[1] })); + }); + + it('does not allow owner to burn more than available balance', async () => { + await assertRevert(token.burn(totalSupply.plus(1), { from: owner })); + }); + +});