From 07c1a0121f7be48dfe1a70ba0dae08f78e7a8b02 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 26 Nov 2018 14:32:55 -0800 Subject: Untested - Compliant Forwarder with Wyre "Yes Compliance" Token --- .../CompliantForwarder/CompliantForwarder.sol | 101 +++++++++++++++++ .../YesComplianceToken/YesComplianceToken.sol | 119 +++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol new file mode 100644 index 000000000..646a9f3b8 --- /dev/null +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -0,0 +1,101 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/Exchange/interfaces/IExchange.sol"; +import "../../tokens/ERC721Token/IERC721Token.sol"; +import "../../utils/LibBytes/LibBytes.sol"; + +contract CompliantForwarder { + + using LibBytes for bytes; + + bytes4 constant internal EXCHANGE_FILL_ORDER_SELECTOR = bytes4(keccak256("fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)")); + IExchange internal EXCHANGE; + IERC721Token internal COMPLIANCE_TOKEN; + + constructor(address exchange, address complianceToken) + public + { + EXCHANGE = IExchange(exchange); + COMPLIANCE_TOKEN = IERC721Token(complianceToken); + } + + function fillOrder( + uint256 salt, + address signerAddress, + bytes signedFillOrderTransaction, + bytes signature + ) + public + { + // Validate `signedFillOrderTransaction` + bytes4 selector = signedFillOrderTransaction.readBytes4(0); + if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { + revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); + } + + // Extract maker address from fill order transaction + // Below is the table of calldata offsets into a fillOrder transaction. + /** + ### parameters + 0x00 ptr + 0x20 takerAssetFillAmount + 0x40 ptr + ### order + 0x60 makerAddress + 0x80 takerAddress + 0xa0 feeRecipientAddress + 0xc0 senderAddress + 0xe0 makerAssetAmount + 0x100 takerAssetAmount + 0x120 makerFee + 0x140 takerFee + 0x160 expirationTimeSeconds + 0x180 salt + 0x1a0 ptr + 0x1c0 ptr + 0x1e0 makerAssetData + * takerAssetData + * signature + ------------------------------ + * Context-dependent offsets, unknown at compile time. + */ + // Add 0x4 to a given offset to account for the fillOrder selector prepended to `signedFillOrderTransaction`. + // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. + // Putting this together: makerAddress = 0x60 + 0x4 + 0xc = 0x70 + address makerAddress = signedFillOrderTransaction.readAddress(0x70); + + // Verify maker/taker have been verified by the compliance token. + if (COMPLIANCE_TOKEN.balanceOf(makerAddress) == 0) { + revert("MAKER_UNVERIFIED"); + } else if (COMPLIANCE_TOKEN.balanceOf(signerAddress) == 0) { + revert("TAKER_UNVERIFIED"); + } + + // All entities are verified. Execute fillOrder. + EXCHANGE.executeTransaction( + salt, + signerAddress, + signedFillOrderTransaction, + signature + ); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol new file mode 100644 index 000000000..abd04219d --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol @@ -0,0 +1,119 @@ +pragma solidity ^0.4.24; + +import "../ERC721Token/ERC721Token.sol"; + +/** + * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific + * compliance-related queries with YES. (attestations) + * + * primarily ERC721 is useful for the self-management of claiming addresses. a single token is more useful + * than a non-ERC721 interface because of interop with other 721-supporting systems/ui; it allows users to + * manage their financial stamp with flexibility using a well-established simple concept of non-fungible tokens. + * this interface is for anyone needing to carry around and otherwise manage their proof of compliance. + * + * the financial systems these users authenticate against have a different set of API requirements. they need + * more contextualization ability than a balance check to support distinctions of attestations, as well as geographic + * distinction. these integrations are made simpler as the language of the query more closely match the language of compliance. + * + * this interface describes, beyond 721, these simple compliance-specific interfaces (and their management tools) + * + * notes: + * - no address can be associated with more than one identity (though addresses may have more than token). issuance + * in this circumstance will fail + * - one person or business = one entity + * - one entity may have many tokens across many addresses; they can mint and burn tokens tied to their identity at will + * - two token types: control & non-control. both carry compliance proof + * - control tokens let their holders mint and burn (within the same entity) + * - non-control tokens are solely for compliance queries + * - a lock on the entity is used instead of token revocation to remove the cash burden assumed by a customer to + * redistribute a fleet of coins + * - all country codes should be via ISO-3166-1 + * + * any (non-view) methods not explicitly marked idempotent are not idempotent. + */ +contract YesComplianceTokenV1 is ERC721Token /*, ERC165 :should: */ { + + uint256 public constant OWNER_ENTITY_ID = 1; + + uint8 public constant YESMARK_OWNER = 128; + uint8 public constant YESMARK_VALIDATOR = 129; + + /* + todo events: entity updated, destroyed, ???? + Finalized + Attested + + */ + + /** + * @notice query api: returns true if the specified address has the given country/yes attestation. this + * is the primary method partners will use to query the active qualifications of any particular + * address. + */ + function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) ; + + /** @notice same as isYes except as an imperative */ + function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view ; + + /** + * @notice retrieve all YES marks for an address in a particular country + * @param _validatorEntityId the validator ID to consider. or, use 0 for any of them + * @param _address the validator ID to consider, or 0 for any of them + * @param _countryCode the ISO-3166-1 country code + * @return (non-duplicate) array of YES marks present + */ + function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] /* memory */); + + // function getCountries(uint256 _validatorEntityId, address _address) external view returns(uint16[] /* memory */); + + /** + * @notice create new tokens. fail if _to already + * belongs to a different entity and caller is not validator + * @param _control true if the new token is a control token (can mint, burn). aka NOT limited. + * @param _entityId the entity to mint for, supply 0 to use the entity tied to the caller + * @return the newly created token ID + */ + function mint(address _to, uint256 _entityId, bool _control) external returns (uint256); + + /** @notice shortcut to mint() + setYes() in one call, for a single country */ + function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256); + + /** @notice destroys a specific token */ + function burn(uint256 _tokenId) external; + + /** @notice destroys the entire entity and all tokens */ + function burnEntity(uint256 _entityId) external; + + /** + * @notice adds a specific attestations (yes) to an entity. idempotent: will return normally even if the mark + * was already set by this validator + */ + function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + + /** + * @notice removes a attestation(s) from a specific validator for an entity. idempotent + */ + function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + + /** @notice removes all attestations in a given country for a particular entity. idempotent */ + function clearYes(uint256 _entityId, uint16 _countryCode) external; + + /** @notice removes all attestations for a particular entity. idempotent */ + function clearYes(uint256 _entityId) external; + + /** @notice assigns a lock to an entity, rendering all isYes queries false. idempotent */ + function setLocked(uint256 _entityId, bool _lock) external; + + /** @notice checks whether or not a particular entity is locked */ + function isLocked(uint256 _entityId) external view returns(bool); + + /** @notice returns true if the specified token has been finalized (cannot be moved) */ + function isFinalized(uint256 _tokenId) external view returns(bool); + + /** @notice finalizes a token by ID preventing it from getting moved. idempotent */ + function finalize(uint256 _tokenId) external; + + /** @return the entity ID associated with an address (or fail if there is not one) */ + function getEntityId(address _address) external view returns(uint256); + +} \ No newline at end of file -- cgit v1.2.3 From f104d91595397a4e7ce82a36de92b09e76d9d507 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 26 Nov 2018 17:29:32 -0800 Subject: Broken commit -- saving current state of getting wyre compliance token contract setup --- .../YesComplianceToken/IYesComplianceToken.sol | 118 ++++ .../YesComplianceToken/WyreERC721Token/ERC721.sol | 40 ++ .../WyreERC721Token/ERC721Basic.sol | 47 ++ .../WyreERC721Token/ERC721BasicToken.sol | 353 +++++++++++ .../WyreERC721Token/ERC721Token.sol | 218 +++++++ .../YesComplianceToken/YesComplianceToken.sol | 693 ++++++++++++++++++--- .../test/extensions/compliant_forwarder.ts | 191 ++++++ 7 files changed, 1579 insertions(+), 81 deletions(-) create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol create mode 100644 packages/contracts/test/extensions/compliant_forwarder.ts (limited to 'packages') diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol new file mode 100644 index 000000000..1573c6bac --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol @@ -0,0 +1,118 @@ +pragma solidity ^0.4.24; + + +/** + * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific + * compliance-related queries with YES. (attestations) + * + * primarily ERC721 is useful for the self-management of claiming addresses. a single token is more useful + * than a non-ERC721 interface because of interop with other 721-supporting systems/ui; it allows users to + * manage their financial stamp with flexibility using a well-established simple concept of non-fungible tokens. + * this interface is for anyone needing to carry around and otherwise manage their proof of compliance. + * + * the financial systems these users authenticate against have a different set of API requirements. they need + * more contextualization ability than a balance check to support distinctions of attestations, as well as geographic + * distinction. these integrations are made simpler as the language of the query more closely match the language of compliance. + * + * this interface describes, beyond 721, these simple compliance-specific interfaces (and their management tools) + * + * notes: + * - no address can be associated with more than one identity (though addresses may have more than token). issuance + * in this circumstance will fail + * - one person or business = one entity + * - one entity may have many tokens across many addresses; they can mint and burn tokens tied to their identity at will + * - two token types: control & non-control. both carry compliance proof + * - control tokens let their holders mint and burn (within the same entity) + * - non-control tokens are solely for compliance queries + * - a lock on the entity is used instead of token revocation to remove the cash burden assumed by a customer to + * redistribute a fleet of coins + * - all country codes should be via ISO-3166-1 + * + * any (non-view) methods not explicitly marked idempotent are not idempotent. + */ +contract YesComplianceTokenV1 /*is ERC721Token*/ /*, ERC165 :should: */ { + + uint256 public constant OWNER_ENTITY_ID = 1; + + uint8 public constant YESMARK_OWNER = 128; + uint8 public constant YESMARK_VALIDATOR = 129; + + /* + todo events: entity updated, destroyed, ???? + Finalized + Attested + + */ + + /** + * @notice query api: returns true if the specified address has the given country/yes attestation. this + * is the primary method partners will use to query the active qualifications of any particular + * address. + */ + function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) ; + + /** @notice same as isYes except as an imperative */ + function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view ; + + /** + * @notice retrieve all YES marks for an address in a particular country + * @param _validatorEntityId the validator ID to consider. or, use 0 for any of them + * @param _address the validator ID to consider, or 0 for any of them + * @param _countryCode the ISO-3166-1 country code + * @return (non-duplicate) array of YES marks present + */ + function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] /* memory */); + + // function getCountries(uint256 _validatorEntityId, address _address) external view returns(uint16[] /* memory */); + + /** + * @notice create new tokens. fail if _to already + * belongs to a different entity and caller is not validator + * @param _control true if the new token is a control token (can mint, burn). aka NOT limited. + * @param _entityId the entity to mint for, supply 0 to use the entity tied to the caller + * @return the newly created token ID + */ + function mint(address _to, uint256 _entityId, bool _control) external returns (uint256); + + /** @notice shortcut to mint() + setYes() in one call, for a single country */ + function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256); + + /** @notice destroys a specific token */ + function burn(uint256 _tokenId) external; + + /** @notice destroys the entire entity and all tokens */ + function burnEntity(uint256 _entityId) external; + + /** + * @notice adds a specific attestations (yes) to an entity. idempotent: will return normally even if the mark + * was already set by this validator + */ + function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + + /** + * @notice removes a attestation(s) from a specific validator for an entity. idempotent + */ + function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + + /** @notice removes all attestations in a given country for a particular entity. idempotent */ + function clearYes(uint256 _entityId, uint16 _countryCode) external; + + /** @notice removes all attestations for a particular entity. idempotent */ + function clearYes(uint256 _entityId) external; + + /** @notice assigns a lock to an entity, rendering all isYes queries false. idempotent */ + function setLocked(uint256 _entityId, bool _lock) external; + + /** @notice checks whether or not a particular entity is locked */ + function isLocked(uint256 _entityId) external view returns(bool); + + /** @notice returns true if the specified token has been finalized (cannot be moved) */ + function isFinalized(uint256 _tokenId) external view returns(bool); + + /** @notice finalizes a token by ID preventing it from getting moved. idempotent */ + function finalize(uint256 _tokenId) external; + + /** @return the entity ID associated with an address (or fail if there is not one) */ + function getEntityId(address _address) external view returns(uint256); + +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol new file mode 100644 index 000000000..5b4907f13 --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol @@ -0,0 +1,40 @@ +pragma solidity ^0.4.21; + +import "./ERC721Basic.sol"; + + +/** + * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension + * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721Enumerable is ERC721Basic { + function totalSupply() public view returns (uint256); + function tokenOfOwnerByIndex( + address _owner, + uint256 _index + ) + public + view + returns (uint256 _tokenId); + + function tokenByIndex(uint256 _index) public view returns (uint256); +} + + +/** + * @title ERC-721 Non-Fungible Token Standard, optional metadata extension + * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721Metadata is ERC721Basic { + function name() external view returns (string _name); + function symbol() external view returns (string _symbol); + function tokenURI(uint256 _tokenId) public view returns (string); +} + + +/** + * @title ERC-721 Non-Fungible Token Standard, full implementation interface + * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721 is ERC721Basic, ERC721Enumerable, ERC721Metadata { +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol new file mode 100644 index 000000000..20f3c5812 --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol @@ -0,0 +1,47 @@ +pragma solidity ^0.4.21; + +/** + * @title ERC721 Non-Fungible Token Standard basic interface + * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721Basic { + event Transfer( + address indexed _from, + address indexed _to, + uint256 indexed _tokenId + ); + event Approval( + address indexed _owner, + address indexed _approved, + uint256 indexed _tokenId + ); + event ApprovalForAll( + address indexed _owner, + address indexed _operator, + bool _approved + ); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function exists(uint256 _tokenId) public view returns (bool _exists); + + function approve(address _to, uint256 _tokenId) public; + function getApproved(uint256 _tokenId) + public view returns (address _operator); + + function setApprovalForAll(address _operator, bool _approved) public; + function isApprovedForAll(address _owner, address _operator) + public view returns (bool); + + function transferFrom(address _from, address _to, uint256 _tokenId) public; + function safeTransferFrom(address _from, address _to, uint256 _tokenId) + public; + + function safeTransferFrom( + address _from, + address _to, + uint256 _tokenId, + bytes _data + ) + public; +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol new file mode 100644 index 000000000..1d3fa37b8 --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol @@ -0,0 +1,353 @@ +pragma solidity ^0.4.21; + +import "./ERC721Basic.sol"; +import "./ERC721Receiver.sol"; +import "../../math/SafeMath.sol"; +import "../../AddressUtils.sol"; +import "../../introspection/ERC165Support.sol"; + + +/** + * @title ERC721 Non-Fungible Token Standard basic implementation + * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721BasicToken is ERC165Support, ERC721Basic { + + bytes4 private constant InterfaceId_ERC721 = 0x80ac58cd; + /* + * 0x80ac58cd === + * bytes4(keccak256('balanceOf(address)')) ^ + * bytes4(keccak256('ownerOf(uint256)')) ^ + * bytes4(keccak256('approve(address,uint256)')) ^ + * bytes4(keccak256('getApproved(uint256)')) ^ + * bytes4(keccak256('setApprovalForAll(address,bool)')) ^ + * bytes4(keccak256('isApprovedForAll(address,address)')) ^ + * bytes4(keccak256('transferFrom(address,address,uint256)')) ^ + * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) ^ + * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) + */ + + bytes4 private constant InterfaceId_ERC721Exists = 0x4f558e79; + /* + * 0x4f558e79 === + * bytes4(keccak256('exists(uint256)')) + */ + + using SafeMath for uint256; + using AddressUtils for address; + + // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` + bytes4 private constant ERC721_RECEIVED = 0x150b7a02; + + // Mapping from token ID to owner + mapping (uint256 => address) internal tokenOwner; + + // Mapping from token ID to approved address + mapping (uint256 => address) internal tokenApprovals; + + // Mapping from owner to number of owned token + mapping (address => uint256) internal ownedTokensCount; + + // Mapping from owner to operator approvals + mapping (address => mapping (address => bool)) internal operatorApprovals; + + /** + * @dev Guarantees msg.sender is owner of the given token + * @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender + */ + modifier onlyOwnerOf(uint256 _tokenId) { + require(ownerOf(_tokenId) == msg.sender); + _; + } + + /** + * @dev Checks msg.sender can transfer a token, by being owner, approved, or operator + * @param _tokenId uint256 ID of the token to validate + */ + modifier canTransfer(uint256 _tokenId) { + require(isApprovedOrOwner(msg.sender, _tokenId)); + _; + } + + function _supportsInterface(bytes4 _interfaceId) + internal + view + returns (bool) + { + return super._supportsInterface(_interfaceId) || + _interfaceId == InterfaceId_ERC721 || _interfaceId == InterfaceId_ERC721Exists; + } + + /** + * @dev Gets the balance of the specified address + * @param _owner address to query the balance of + * @return uint256 representing the amount owned by the passed address + */ + function balanceOf(address _owner) public view returns (uint256) { + require(_owner != address(0)); + return ownedTokensCount[_owner]; + } + + /** + * @dev Gets the owner of the specified token ID + * @param _tokenId uint256 ID of the token to query the owner of + * @return owner address currently marked as the owner of the given token ID + */ + function ownerOf(uint256 _tokenId) public view returns (address) { + address owner = tokenOwner[_tokenId]; + require(owner != address(0)); + return owner; + } + + /** + * @dev Returns whether the specified token exists + * @param _tokenId uint256 ID of the token to query the existence of + * @return whether the token exists + */ + function exists(uint256 _tokenId) public view returns (bool) { + address owner = tokenOwner[_tokenId]; + return owner != address(0); + } + + /** + * @dev Approves another address to transfer the given token ID + * The zero address indicates there is no approved address. + * There can only be one approved address per token at a given time. + * Can only be called by the token owner or an approved operator. + * @param _to address to be approved for the given token ID + * @param _tokenId uint256 ID of the token to be approved + */ + function approve(address _to, uint256 _tokenId) public { + address owner = ownerOf(_tokenId); + require(_to != owner); + require(msg.sender == owner || isApprovedForAll(owner, msg.sender)); + + tokenApprovals[_tokenId] = _to; + emit Approval(owner, _to, _tokenId); + } + + /** + * @dev Gets the approved address for a token ID, or zero if no address set + * @param _tokenId uint256 ID of the token to query the approval of + * @return address currently approved for the given token ID + */ + function getApproved(uint256 _tokenId) public view returns (address) { + return tokenApprovals[_tokenId]; + } + + /** + * @dev Sets or unsets the approval of a given operator + * An operator is allowed to transfer all tokens of the sender on their behalf + * @param _to operator address to set the approval + * @param _approved representing the status of the approval to be set + */ + function setApprovalForAll(address _to, bool _approved) public { + require(_to != msg.sender); + operatorApprovals[msg.sender][_to] = _approved; + emit ApprovalForAll(msg.sender, _to, _approved); + } + + /** + * @dev Tells whether an operator is approved by a given owner + * @param _owner owner address which you want to query the approval of + * @param _operator operator address which you want to query the approval of + * @return bool whether the given operator is approved by the given owner + */ + function isApprovedForAll( + address _owner, + address _operator + ) + public + view + returns (bool) + { + return operatorApprovals[_owner][_operator]; + } + + /** + * @dev Transfers the ownership of a given token ID to another address + * Usage of this method is discouraged, use `safeTransferFrom` whenever possible + * Requires the msg sender to be the owner, approved, or operator + * @param _from current owner of the token + * @param _to address to receive the ownership of the given token ID + * @param _tokenId uint256 ID of the token to be transferred + */ + function transferFrom( + address _from, + address _to, + uint256 _tokenId + ) + public + canTransfer(_tokenId) + { + require(_from != address(0)); + require(_to != address(0)); + + clearApproval(_from, _tokenId); + removeTokenFrom(_from, _tokenId); + addTokenTo(_to, _tokenId); + + emit Transfer(_from, _to, _tokenId); + } + + /** + * @dev Safely transfers the ownership of a given token ID to another address + * If the target address is a contract, it must implement `onERC721Received`, + * which is called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, + * the transfer is reverted. + * + * Requires the msg sender to be the owner, approved, or operator + * @param _from current owner of the token + * @param _to address to receive the ownership of the given token ID + * @param _tokenId uint256 ID of the token to be transferred + */ + function safeTransferFrom( + address _from, + address _to, + uint256 _tokenId + ) + public + canTransfer(_tokenId) + { + // solium-disable-next-line arg-overflow + safeTransferFrom(_from, _to, _tokenId, ""); + } + + /** + * @dev Safely transfers the ownership of a given token ID to another address + * If the target address is a contract, it must implement `onERC721Received`, + * which is called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, + * the transfer is reverted. + * Requires the msg sender to be the owner, approved, or operator + * @param _from current owner of the token + * @param _to address to receive the ownership of the given token ID + * @param _tokenId uint256 ID of the token to be transferred + * @param _data bytes data to send along with a safe transfer check + */ + function safeTransferFrom( + address _from, + address _to, + uint256 _tokenId, + bytes _data + ) + public + canTransfer(_tokenId) + { + transferFrom(_from, _to, _tokenId); + // solium-disable-next-line arg-overflow + require(checkAndCallSafeTransfer(_from, _to, _tokenId, _data)); + } + + /** + * @dev Returns whether the given spender can transfer a given token ID + * @param _spender address of the spender to query + * @param _tokenId uint256 ID of the token to be transferred + * @return bool whether the msg.sender is approved for the given token ID, + * is an operator of the owner, or is the owner of the token + */ + function isApprovedOrOwner( + address _spender, + uint256 _tokenId + ) + internal + view + returns (bool) + { + address owner = ownerOf(_tokenId); + // Disable solium check because of + // https://github.com/duaraghav8/Solium/issues/175 + // solium-disable-next-line operator-whitespace + return ( + _spender == owner || + getApproved(_tokenId) == _spender || + isApprovedForAll(owner, _spender) + ); + } + + /** + * @dev Internal function to mint a new token + * Reverts if the given token ID already exists + * @param _to The address that will own the minted token + * @param _tokenId uint256 ID of the token to be minted by the msg.sender + */ + function _mint(address _to, uint256 _tokenId) internal { + require(_to != address(0)); + addTokenTo(_to, _tokenId); + emit Transfer(address(0), _to, _tokenId); + } + + /** + * @dev Internal function to burn a specific token + * Reverts if the token does not exist + * @param _tokenId uint256 ID of the token being burned by the msg.sender + */ + function _burn(address _owner, uint256 _tokenId) internal { + clearApproval(_owner, _tokenId); + removeTokenFrom(_owner, _tokenId); + emit Transfer(_owner, address(0), _tokenId); + } + + /** + * @dev Internal function to clear current approval of a given token ID + * Reverts if the given address is not indeed the owner of the token + * @param _owner owner of the token + * @param _tokenId uint256 ID of the token to be transferred + */ + function clearApproval(address _owner, uint256 _tokenId) internal { + require(ownerOf(_tokenId) == _owner); + if (tokenApprovals[_tokenId] != address(0)) { + tokenApprovals[_tokenId] = address(0); + } + } + + /** + * @dev Internal function to add a token ID to the list of a given address + * @param _to address representing the new owner of the given token ID + * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address + */ + function addTokenTo(address _to, uint256 _tokenId) internal { + require(tokenOwner[_tokenId] == address(0)); + tokenOwner[_tokenId] = _to; + ownedTokensCount[_to] = ownedTokensCount[_to].add(1); + } + + /** + * @dev Internal function to remove a token ID from the list of a given address + * @param _from address representing the previous owner of the given token ID + * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function removeTokenFrom(address _from, uint256 _tokenId) internal { + require(ownerOf(_tokenId) == _from); + ownedTokensCount[_from] = ownedTokensCount[_from].sub(1); + tokenOwner[_tokenId] = address(0); + } + + /** + * @dev Internal function to invoke `onERC721Received` on a target address + * The call is not executed if the target address is not a contract + * @param _from address representing the previous owner of the given token ID + * @param _to target address that will receive the tokens + * @param _tokenId uint256 ID of the token to be transferred + * @param _data bytes optional data to send along with the call + * @return whether the call correctly returned the expected magic value + */ + function checkAndCallSafeTransfer( + address _from, + address _to, + uint256 _tokenId, + bytes _data + ) + internal + returns (bool) + { + if (!_to.isContract()) { + return true; + } + bytes4 retval = ERC721Receiver(_to).onERC721Received( + msg.sender, _from, _tokenId, _data); + return (retval == ERC721_RECEIVED); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol new file mode 100644 index 000000000..c7acee6df --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol @@ -0,0 +1,218 @@ +pragma solidity ^0.4.21; + +import "./ERC721.sol"; +import "./ERC721BasicToken.sol"; + + +/** + * @title Full ERC721 Token + * This implementation includes all the required and some optional functionality of the ERC721 standard + * Moreover, it includes approve all functionality using operator terminology + * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721Token is ERC721BasicToken, ERC721 { + + bytes4 private constant InterfaceId_ERC721Enumerable = 0x780e9d63; + /** + * 0x780e9d63 === + * bytes4(keccak256('totalSupply()')) ^ + * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) ^ + * bytes4(keccak256('tokenByIndex(uint256)')) + */ + + bytes4 private constant InterfaceId_ERC721Metadata = 0x5b5e139f; + /** + * 0x5b5e139f === + * bytes4(keccak256('name()')) ^ + * bytes4(keccak256('symbol()')) ^ + * bytes4(keccak256('tokenURI(uint256)')) + */ + + // Token name + string internal name_; + + // Token symbol + string internal symbol_; + + // Mapping from owner to list of owned token IDs + mapping(address => uint256[]) internal ownedTokens; + + // Mapping from token ID to index of the owner tokens list + mapping(uint256 => uint256) internal ownedTokensIndex; + + // Array with all token ids, used for enumeration + uint256[] internal allTokens; + + // Mapping from token id to position in the allTokens array + mapping(uint256 => uint256) internal allTokensIndex; + + // Optional mapping for token URIs + mapping(uint256 => string) internal tokenURIs; + + /** + * @dev Constructor function + */ + function initialize(string _name, string _symbol) public isInitializer("ERC721Token", "1.9.0") { + name_ = _name; + symbol_ = _symbol; + } + + function _supportsInterface(bytes4 _interfaceId) + internal + view + returns (bool) + { + return super._supportsInterface(_interfaceId) || + _interfaceId == InterfaceId_ERC721Enumerable || _interfaceId == InterfaceId_ERC721Metadata; + } + + /** + * @dev Gets the token name + * @return string representing the token name + */ + function name() external view returns (string) { + return name_; + } + + /** + * @dev Gets the token symbol + * @return string representing the token symbol + */ + function symbol() external view returns (string) { + return symbol_; + } + + /** + * @dev Returns an URI for a given token ID + * Throws if the token ID does not exist. May return an empty string. + * @param _tokenId uint256 ID of the token to query + */ + function tokenURI(uint256 _tokenId) public view returns (string) { + require(exists(_tokenId)); + return tokenURIs[_tokenId]; + } + + /** + * @dev Gets the token ID at a given index of the tokens list of the requested owner + * @param _owner address owning the tokens list to be accessed + * @param _index uint256 representing the index to be accessed of the requested tokens list + * @return uint256 token ID at the given index of the tokens list owned by the requested address + */ + function tokenOfOwnerByIndex( + address _owner, + uint256 _index + ) + public + view + returns (uint256) + { + require(_index < balanceOf(_owner)); + return ownedTokens[_owner][_index]; + } + + /** + * @dev Gets the total amount of tokens stored by the contract + * @return uint256 representing the total amount of tokens + */ + function totalSupply() public view returns (uint256) { + return allTokens.length; + } + + /** + * @dev Gets the token ID at a given index of all the tokens in this contract + * Reverts if the index is greater or equal to the total number of tokens + * @param _index uint256 representing the index to be accessed of the tokens list + * @return uint256 token ID at the given index of the tokens list + */ + function tokenByIndex(uint256 _index) public view returns (uint256) { + require(_index < totalSupply()); + return allTokens[_index]; + } + + /** + * @dev Internal function to set the token URI for a given token + * Reverts if the token ID does not exist + * @param _tokenId uint256 ID of the token to set its URI + * @param _uri string URI to assign + */ + function _setTokenURI(uint256 _tokenId, string _uri) internal { + require(exists(_tokenId)); + tokenURIs[_tokenId] = _uri; + } + + /** + * @dev Internal function to add a token ID to the list of a given address + * @param _to address representing the new owner of the given token ID + * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address + */ + function addTokenTo(address _to, uint256 _tokenId) internal { + super.addTokenTo(_to, _tokenId); + uint256 length = ownedTokens[_to].length; + ownedTokens[_to].push(_tokenId); + ownedTokensIndex[_tokenId] = length; + } + + /** + * @dev Internal function to remove a token ID from the list of a given address + * @param _from address representing the previous owner of the given token ID + * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function removeTokenFrom(address _from, uint256 _tokenId) internal { + super.removeTokenFrom(_from, _tokenId); + + uint256 tokenIndex = ownedTokensIndex[_tokenId]; + uint256 lastTokenIndex = ownedTokens[_from].length.sub(1); + uint256 lastToken = ownedTokens[_from][lastTokenIndex]; + + ownedTokens[_from][tokenIndex] = lastToken; + ownedTokens[_from][lastTokenIndex] = 0; + // Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to + // be zero. Then we can make sure that we will remove _tokenId from the ownedTokens list since we are first swapping + // the lastToken to the first position, and then dropping the element placed in the last position of the list + + ownedTokens[_from].length--; + ownedTokensIndex[_tokenId] = 0; + ownedTokensIndex[lastToken] = tokenIndex; + } + + /** + * @dev Internal function to mint a new token + * Reverts if the given token ID already exists + * @param _to address the beneficiary that will own the minted token + * @param _tokenId uint256 ID of the token to be minted by the msg.sender + */ + function _mint(address _to, uint256 _tokenId) internal { + super._mint(_to, _tokenId); + + allTokensIndex[_tokenId] = allTokens.length; + allTokens.push(_tokenId); + } + + /** + * @dev Internal function to burn a specific token + * Reverts if the token does not exist + * @param _owner owner of the token to burn + * @param _tokenId uint256 ID of the token being burned by the msg.sender + */ + function _burn(address _owner, uint256 _tokenId) internal { + super._burn(_owner, _tokenId); + + // Clear metadata (if any) + if (bytes(tokenURIs[_tokenId]).length != 0) { + delete tokenURIs[_tokenId]; + } + + // Reorg all tokens array + uint256 tokenIndex = allTokensIndex[_tokenId]; + uint256 lastTokenIndex = allTokens.length.sub(1); + uint256 lastToken = allTokens[lastTokenIndex]; + + allTokens[tokenIndex] = lastToken; + allTokens[lastTokenIndex] = 0; + + allTokens.length--; + allTokensIndex[_tokenId] = 0; + allTokensIndex[lastToken] = tokenIndex; + } + +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol index abd04219d..b810d9f31 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol @@ -1,119 +1,650 @@ pragma solidity ^0.4.24; -import "../ERC721Token/ERC721Token.sol"; +import "./IYesComplianceToken.sol"; /** - * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific - * compliance-related queries with YES. (attestations) + * draft implementation of YES compliance token * - * primarily ERC721 is useful for the self-management of claiming addresses. a single token is more useful - * than a non-ERC721 interface because of interop with other 721-supporting systems/ui; it allows users to - * manage their financial stamp with flexibility using a well-established simple concept of non-fungible tokens. - * this interface is for anyone needing to carry around and otherwise manage their proof of compliance. + * NOTE: i have done relatively few gas optimization tweaks (beyond using the sturctures necessary to avoid any + * linear time procedures). + * in some cases i am using a call structure which replicates some checks. this is for code clarity/security - + * i marked a few obvious ones which could be optimized for gas, but :meh: * - * the financial systems these users authenticate against have a different set of API requirements. they need - * more contextualization ability than a balance check to support distinctions of attestations, as well as geographic - * distinction. these integrations are made simpler as the language of the query more closely match the language of compliance. - * - * this interface describes, beyond 721, these simple compliance-specific interfaces (and their management tools) - * - * notes: - * - no address can be associated with more than one identity (though addresses may have more than token). issuance - * in this circumstance will fail - * - one person or business = one entity - * - one entity may have many tokens across many addresses; they can mint and burn tokens tied to their identity at will - * - two token types: control & non-control. both carry compliance proof - * - control tokens let their holders mint and burn (within the same entity) - * - non-control tokens are solely for compliance queries - * - a lock on the entity is used instead of token revocation to remove the cash burden assumed by a customer to - * redistribute a fleet of coins - * - all country codes should be via ISO-3166-1 - * - * any (non-view) methods not explicitly marked idempotent are not idempotent. + * todo static owner should follow owner token? remove static owner? :security: :should: + * @author Tyson Malchow */ -contract YesComplianceTokenV1 is ERC721Token /*, ERC165 :should: */ { +contract YesComplianceToken is YesComplianceTokenV1 { - uint256 public constant OWNER_ENTITY_ID = 1; + uint64 private constant MAX_TOKENS_PER_ENTITY = 10240; // completely arbitrary limit + uint64 private constant MAX_ENTITIES = 2**32-1; // bc using 32 bit index tracking + uint64 private constant MAX_VALIDATORS_PER_MARK = 2**32-1; // bc using 32 bit index tracking + uint64 private constant TOTAL_YES_MARKS = 255; // bc 'uint8 yes' - uint8 public constant YESMARK_OWNER = 128; - uint8 public constant YESMARK_VALIDATOR = 129; + // todo could shorten the entity IDs to anything 160+ to make this cheaper? - /* - todo events: entity updated, destroyed, ???? - Finalized - Attested + /** @notice a single YES attestation */ + struct YesMark { - */ + /** @notice ISO-3166-1 country codes */ + uint16 countryCode; - /** - * @notice query api: returns true if the specified address has the given country/yes attestation. this - * is the primary method partners will use to query the active qualifications of any particular - * address. - */ - function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) ; + /** @notice the possibly-country-speicifc YES being marked. */ + uint8 yes; + + // 8 bits more space in this slot.. could upgrade yes to uint16? + + /** @notice the index of this mark in EntityRecord.yesMarks */ + uint32 yesMarkIdx; + + /** a list of the validator entities which have attested to this mark */ + uint256[] validatorEntityIds; - /** @notice same as isYes except as an imperative */ - function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view ; + /** @notice index of each validator entity ID in validatorEntityIds */ + mapping(uint256 => uint32) validatorEntityIdIdx; + + // uint8 entityListIdx; + } /** - * @notice retrieve all YES marks for an address in a particular country - * @param _validatorEntityId the validator ID to consider. or, use 0 for any of them - * @param _address the validator ID to consider, or 0 for any of them - * @param _countryCode the ISO-3166-1 country code - * @return (non-duplicate) array of YES marks present + * tracks the state for a single recognized entity */ - function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] /* memory */); + struct EntityRecord { + + /** true marking this entity ID has been encountered */ + bool init; + + /** when true, this entity is effectively useless */ + bool locked; + + // 30 bits more space in this slot + + /** position of the entityId in allEntityIds */ + uint32 entityIdIdx; + + /** used for creating reliable token IDs, monotonically increasing */ + uint64 tokenIdCounter; + + /** indexed YES mark lookups */ + mapping(bytes4 => YesMark) yesMarkByKey; + + /** raw collection of all marks keys */ + bytes4[] yesMarkKeys; - // function getCountries(uint256 _validatorEntityId, address _address) external view returns(uint16[] /* memory */); + /** all tokens associated with this identity */ + uint256[] tokenIds; + + // trellis/tower connection ? + // civic connection ? + // erc725/735 connection ? + } /** - * @notice create new tokens. fail if _to already - * belongs to a different entity and caller is not validator - * @param _control true if the new token is a control token (can mint, burn). aka NOT limited. - * @param _entityId the entity to mint for, supply 0 to use the entity tied to the caller - * @return the newly created token ID + * @notice all fields we want to add per-token. + * + * there may never be more than just control flag, in which case it may make sense to collapse this + * to just a mapping(uint256 => bool) ? */ - function mint(address _to, uint256 _entityId, bool _control) external returns (uint256); + struct TokenRecord { + + /** position of the tokenId in EntityRecord.tokenIds */ + uint32 tokenIdIdx; + + /** true if this token has administrative superpowers (aka is _not_ limited) */ + bool control; + + /** true if this token cannot move */ + bool finalized; + + // 30 bits more in this slot - /** @notice shortcut to mint() + setYes() in one call, for a single country */ - function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256); + // limitations: in/out? + } - /** @notice destroys a specific token */ - function burn(uint256 _tokenId) external; + address public ownerAddress; - /** @notice destroys the entire entity and all tokens */ - function burnEntity(uint256 _entityId) external; + mapping(uint256 => TokenRecord) public tokenRecordById; + mapping(uint256 => EntityRecord) public entityRecordById; + mapping(uint256 => uint256) public entityIdByTokenId; + + /** for entity enumeration. maximum of 2^256-1 total entities (i think we'll be ok) */ + uint256[] entityIds; + + constructor() public { + /* this space intentionally left blank */ + } /** - * @notice adds a specific attestations (yes) to an entity. idempotent: will return normally even if the mark - * was already set by this validator + * constructor alternative: first-time initialization the contract/token (required because of upgradeability) */ - function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + function initialize(string _name, string _symbol) { + // require(super._symbol.length == 0 || _symbol == super._symbol); // cannot change symbol after first init bc that could fuck shit up + _upgradeable_initialize(); // basically for security + super.initialize(_name, _symbol); // init token info + + // grant the owner token + mint_I(ownerAddress, OWNER_ENTITY_ID, true); + + // ecosystem owner gets both owner and validator marks (self-attested) + setYes_I(OWNER_ENTITY_ID, OWNER_ENTITY_ID, 0, YESMARK_OWNER); + setYes_I(OWNER_ENTITY_ID, OWNER_ENTITY_ID, 0, YESMARK_VALIDATOR); + } /** - * @notice removes a attestation(s) from a specific validator for an entity. idempotent + * executed in lieu of a constructor in a delegated context */ - function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + function _upgradeable_initialize() public { + super._upgradeable_initialize(); // provides require(msg.sender == _upgradeable_delegate_owner); + + // some things are still tied to the owner (instead of the yesmark_owner :notsureif:) + ownerAddress = msg.sender; + } + + // YesComplianceTokenV1 Interface Methods -------------------------------------------------------------------------- + + function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) { + return isYes_I(_validatorEntityId, _address, _countryCode, _yes); + } + + function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view { + require(isYes_I(_validatorEntityId, _address, _countryCode, _yes)); + } + + function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] memory) { + if(balanceOf(_address) == 0) + return new uint8[](0); + + uint256 entityId = entityIdByTokenId[tokenOfOwnerByIndex(_address, 0)]; + EntityRecord storage e = entityRecordById[entityId]; + uint256 j = 0; + uint256 i; + + // locked always bails + if(e.locked) + return new uint8[](0); + + uint8[] memory r = new uint8[](e.yesMarkKeys.length); + + for(i = 0; i < e.yesMarkKeys.length; i++) { + YesMark storage m = e.yesMarkByKey[e.yesMarkKeys[i]]; + + // filter country code + if(m.countryCode != _countryCode) + continue; + + // filter explicit validator entity + if(_validatorEntityId > 0 + && m.validatorEntityIdIdx[_validatorEntityId] == 0 + && (m.validatorEntityIds.length == 0 || m.validatorEntityIds[0] == _validatorEntityId)) + continue; + + // matched, chyess + r[j++] = m.yes; + } + + // reduce array length + assembly { mstore(r, j) } + + return r; + } + + function mint(address _to, uint256 _entityId, bool _control) external returns (uint256) /* internally protected */{ + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + + // make sure caller has a control token, at the least + require(tokenRecordById[callerTokenId].control, 'control token required'); + + // determine/validate the entity being minted for + uint256 realEntityId; + if(_entityId == 0 || _entityId == callerEntityId) { + // unspecified entity, or caller entity, can do! + realEntityId = callerEntityId; + + } else { + // otherwise make sure caller is a VALIDATOR, else fail + require(senderIsControlValidator(), 'illegal entity id'); // some duplicate checks/lookups, gas leak + realEntityId = _entityId; + } + + return mint_I(_to, realEntityId, _control); + } + + function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256) /* internally protected */ { + // lazy warning: this is a 90% copy/paste job from the mint directly above this + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + + // make sure caller has a control token, at the least + require(tokenRecordById[callerTokenId].control, 'control token required'); + + // determine/validate the entity being minted for + uint256 realEntityId; + if(_entityId == 0 || _entityId == callerEntityId) { + // unspecified entity, or caller entity, can do! + realEntityId = callerEntityId; + + } else { + // otherwise make sure caller is a VALIDATOR, else fail + require(senderIsControlValidator()); // some duplicate checks/lookups, gas leak + realEntityId = _entityId; + } + + // mint the coin + uint256 tokenId = mint_I(_to, realEntityId, _control); + + // now set the attestations + require(_yes.length <= TOTAL_YES_MARKS); // safety + for(uint256 i = 0; i<_yes.length; i++) { + setYes_I(_entityId, _countryCode, _yes[i]); + } + + return tokenId; + } + + function getEntityId(address _address) external view returns (uint256) { + return entityIdByTokenId[tokenOfOwnerByIndex(_address, 0)]; + } + + function burn(uint256 _tokenId) external permission_control_tokenId(_tokenId) { + uint256 entityId = entityIdByTokenId[_tokenId]; + + EntityRecord storage e = entity(entityId); + TokenRecord storage t = tokenRecordById[_tokenId]; + + // remove token from entity + e.tokenIds[t.tokenIdIdx] = e.tokenIds[e.tokenIds.length - 1]; + e.tokenIds.length--; + + // update tracked index (of swapped, if present) + if(e.tokenIds.length > t.tokenIdIdx) + tokenRecordById[e.tokenIds[t.tokenIdIdx]].tokenIdIdx = t.tokenIdIdx; + + // remove token record + delete tokenRecordById[_tokenId]; + + // burn the actual token + super._burn(tokenOwner[_tokenId], _tokenId); + } + + function burnEntity(uint256 _entityId) external permission_control_entityId(_entityId) { // self-burn allowed + EntityRecord storage e = entity(_entityId); + + // burn all the tokens + for(uint256 i = 0; i < e.tokenIds.length; i++) { + uint256 tokenId = e.tokenIds[i]; + super._burn(tokenOwner[tokenId], tokenId); + } + + // clear all the marks + clearYes_I(_entityId); + + // clear out entity record + e.init = false; + e.locked = false; + e.entityIdIdx = 0; + e.tokenIdCounter = 0; + + assert(e.yesMarkKeys.length == 0); + assert(e.tokenIds.length == 0); + } + + function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external permission_validator { + setYes_I(_entityId, _countryCode, _yes); + } + + function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external permission_validator { + require(_yes > 0); + require(_yes != 128); + + // special check against reserved country code 0 + if(_countryCode == 0) + require(senderIsEcosystemControl(), 'not authorized as ecosystem control'); // this is duplicating some things, gas leak + + EntityRecord storage e = entity(_entityId); + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + bytes4 key = yesKey(_countryCode, _yes); + + YesMark storage mark = e.yesMarkByKey[key]; + if(mark.yes == 0) + return; // not set by anyone, bail happily + + if(mark.validatorEntityIdIdx[callerEntityId] == 0 && + (mark.validatorEntityIds.length == 0 || mark.validatorEntityIds[0] != callerEntityId)) { + // set, but not by this validator, bail happily + return; + } + + clearYes_I(mark, e, callerEntityId); + } + + function clearYes(uint256 _entityId, uint16 _countryCode) external permission_validator { + // special check against 129 validator mark + if(_countryCode == 0) + require(senderIsEcosystemControl(), 'not authorized as ecosystem control (129)'); // this is duplicating some things, gas leak + + EntityRecord storage e = entity(_entityId); + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + uint256 i; + + for(i =0; i idx) + mark.validatorEntityIdIdx[mark.validatorEntityIds[idx]] = idx; + + // check if the entire mark needs deleting + if(mark.validatorEntityIds.length == 0) { + // yes, it does. swap/delete + idx = mark.yesMarkIdx; + e.yesMarkKeys[idx] = e.yesMarkKeys[e.yesMarkKeys.length - 1]; + e.yesMarkKeys.length--; + + // remap + if(e.yesMarkKeys.length > idx) + e.yesMarkByKey[e.yesMarkKeys[idx]].yesMarkIdx = idx; + + // delete mark + mark.countryCode = 0; + mark.yes = 0; + mark.yesMarkIdx = 0; + // assert(mark.validatorEntityIds.length == 0); + + return true; + } + + return false; + } + + function clearYes_I(uint256 _entityId) internal { + require(_entityId != OWNER_ENTITY_ID); + + EntityRecord storage e = entity(_entityId); + + // only ecosystem control can touch validators + if(!senderIsEcosystemControl()) + require(e.yesMarkByKey[yesKey(0, YESMARK_VALIDATOR)].yes == 0); + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + uint256 i; + + for(i =0; i 0 + || m.validatorEntityIds.length > 0 && m.validatorEntityIds[0] == _validatorEntityId; + } + + function setYes_I(uint256 _entityId, uint16 _countryCode, uint8 _yes) internal { + require(_yes > 0); + require(_yes != 128); + + // special check against 129 validator mark + if(_yes == 129) + require(senderIsEcosystemControl()); // this is duplicating some checks, gas leak + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + + setYes_I(callerEntityId, _entityId, _countryCode, _yes); + } + + function setYes_I(uint256 _validatorEntityId, uint256 _entityId, uint16 _countryCode, uint8 _yes) internal { + // assert(_yes > 0); + EntityRecord storage targetEntity = entity(_entityId); + + // locate existing mark + bytes4 key = yesKey(_countryCode, _yes); + YesMark storage mark = targetEntity.yesMarkByKey[key]; + + if(mark.yes == 0) { + require(targetEntity.yesMarkKeys.length < TOTAL_YES_MARKS); + + // new mark on the entity + mark.countryCode = _countryCode; + mark.yes = _yes; + mark.yesMarkIdx = uint32(targetEntity.yesMarkKeys.length); + targetEntity.yesMarkKeys.push(key); + + } else if(mark.validatorEntityIdIdx[_validatorEntityId] > 0 || + (mark.validatorEntityIds.length > 0 && mark.validatorEntityIds[0] == _validatorEntityId)) { + + // existing mark and the caller is already on it + /* + i'm inclined to make it do nothing in this case (instead of failing) since i'm not at this point positive how best + to distinguish error types to a caller, which would be required for a caller to know wtf to do in this case + (otherwise they need to query blockchain again) + (but that costs gas... :notsureif:) + */ + return; + } + + require(mark.validatorEntityIds.length < MAX_VALIDATORS_PER_MARK); + + // add this validator to the mark + mark.validatorEntityIdIdx[_validatorEntityId] = uint32(mark.validatorEntityIds.length); + mark.validatorEntityIds.push(_validatorEntityId); + } + + /** non-permissed internal minting impl */ + function mint_I(address _to, uint256 _entityId, bool _control) internal returns (uint256) { + EntityRecord storage e = entity(_entityId); + require(e.tokenIds.length < MAX_TOKENS_PER_ENTITY, 'token limit reached'); + require(e.tokenIdCounter < 2**64-1); // kind of ridiculous but whatever, safety first! + uint256 tokenId = uint256(keccak256(abi.encodePacked(_entityId, e.tokenIdCounter++))); + super._mint(_to, tokenId); + tokenRecordById[tokenId].tokenIdIdx = uint32(e.tokenIds.length); + tokenRecordById[tokenId].control = _control; + e.tokenIds.push(tokenId); + entityIdByTokenId[tokenId] = _entityId; + return tokenId; + } + + /** entity resolution (creation when needed) */ + function entity(uint256 _entityId) internal returns (EntityRecord storage) { + require(_entityId > 0); + EntityRecord storage e = entityRecordById[_entityId]; + if(e.init) return e; + require(entityIds.length < MAX_ENTITIES); + e.init = true; + e.entityIdIdx = uint32(entityIds.length); + entityIds.push(_entityId); + return e; + } + + /** override default addTokenTo for additional transaction limitations */ + function addTokenTo(address _to, uint256 _tokenId) internal { + uint256 entityId = entityIdByTokenId[_tokenId]; + + // ensure one owner cannot be associated with multiple entities + // NOTE: this breaks hotwallet integrations, at this point necessarily so + if(balanceOf(_to) > 0) { + uint256 prevEntityId = entityIdByTokenId[tokenOfOwnerByIndex(_to, 0)]; + require(prevEntityId == entityId, 'conflicting entities'); + } + + require(!tokenRecordById[_tokenId].finalized, 'token is finalized'); + + super.addTokenTo(_to, _tokenId); + } + + /** the sender is the same entity as the one specified */ + function senderIsEntity_ByEntityId(uint256 _entityId) internal view returns (bool) { + return _entityId == entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; + } + + /** the sender is the same entity as the one specified, and the sender is a control for that entity */ + function senderIsControl_ByEntityId(uint256 _entityId) internal view returns (bool) { + if(balanceOf(msg.sender) == 0) + return false; + uint256 tokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 senderEntityId = entityIdByTokenId[tokenId]; + return _entityId == senderEntityId && tokenRecordById[tokenId].control; + } + + /** the sender is a non-locked validator via control token */ + function senderIsControlValidator() internal view returns (bool) { + if(balanceOf(msg.sender) == 0) + return false; + uint256 tokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 senderEntityId = entityIdByTokenId[tokenId]; + EntityRecord storage e = entityRecordById[senderEntityId]; + return tokenRecordById[tokenId].control + && !e.locked + && entityRecordById[senderEntityId].yesMarkByKey[yesKey(0, YESMARK_VALIDATOR)].yes > 0; + } + + /** the sender is the same entity as the one tied to the token specified */ + function senderIsEntity_ByTokenId(uint256 _tokenId) internal view returns (bool) { + if(balanceOf(msg.sender) == 0) + return false; + return entityIdByTokenId[_tokenId] == entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; + } + + /** the sender is the same entity as the one tied to the token specified, and the sender is a control for that entity */ + function senderIsControl_ByTokenId(uint256 _tokenId) internal view returns (bool) { + if(balanceOf(msg.sender) == 0) + return false; + uint256 senderEntityId = entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; + return entityIdByTokenId[_tokenId] == senderEntityId && tokenRecordById[_tokenId].control; + } + + /** checks if sender is the singular ecosystem owner */ + function senderIsEcosystemControl() internal view returns (bool) { + // todo deprecate ownerAddress ?! + return msg.sender == ownerAddress || senderIsControl_ByEntityId(OWNER_ENTITY_ID); + } + + /** a key for a YES attestation mark */ + function yesKey(uint16 _countryCode, uint8 _yes) internal pure returns(bytes4) { + return bytes4(keccak256(abi.encodePacked(_countryCode, _yes))); + } - /** @notice removes all attestations in a given country for a particular entity. idempotent */ - function clearYes(uint256 _entityId, uint16 _countryCode) external; + // PERMISSIONS MODIFIERS ---------------------------------------------------------------- - /** @notice removes all attestations for a particular entity. idempotent */ - function clearYes(uint256 _entityId) external; + modifier permission_validator { + require(senderIsControlValidator(), 'not authorized as validator'); + _; + } - /** @notice assigns a lock to an entity, rendering all isYes queries false. idempotent */ - function setLocked(uint256 _entityId, bool _lock) external; + modifier permission_super { + require(senderIsEcosystemControl(), 'not authorized as ecosystem control'); + _; + } - /** @notice checks whether or not a particular entity is locked */ - function isLocked(uint256 _entityId) external view returns(bool); +// modifier permission_access_entityId(uint256 _entityId) { +// require(senderIsEcosystemControl() || senderIsEntity_ByEntityId(_entityId)); +// _; +// } - /** @notice returns true if the specified token has been finalized (cannot be moved) */ - function isFinalized(uint256 _tokenId) external view returns(bool); + modifier permission_control_entityId(uint256 _entityId) { + require(senderIsEcosystemControl() || senderIsControl_ByEntityId(_entityId), 'not authorized entity controller'); + _; + } - /** @notice finalizes a token by ID preventing it from getting moved. idempotent */ - function finalize(uint256 _tokenId) external; + modifier permission_access_tokenId(uint256 _tokenId) { + require(senderIsEcosystemControl() || senderIsEntity_ByTokenId(_tokenId)); + _; + } - /** @return the entity ID associated with an address (or fail if there is not one) */ - function getEntityId(address _address) external view returns(uint256); + modifier permission_control_tokenId(uint256 _tokenId) { + require(senderIsEcosystemControl() || senderIsControl_ByTokenId(_tokenId), 'not authorized token controller'); + _; + } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts new file mode 100644 index 000000000..41603e3c2 --- /dev/null +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -0,0 +1,191 @@ +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils } from '@0x/order-utils'; +import { RevertReason, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; + +import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; +import { ExchangeContract } from '../../generated-wrappers/exchange'; +import { DummyYesComplianceContract } from '../../generated-wrappers/forwarder'; +import { WETH9Contract } from '../../generated-wrappers/weth9'; +import { artifacts } from '../../src/artifacts'; +import { + expectContractCreationFailedAsync, + expectTransactionFailedAsync, + sendTransactionResult, +} from '../utils/assertions'; +import { chaiSetup } from '../utils/chai_setup'; +import { constants } from '../utils/constants'; +import { ERC20Wrapper } from '../utils/erc20_wrapper'; +import { ERC721Wrapper } from '../utils/erc721_wrapper'; +import { ExchangeWrapper } from '../utils/exchange_wrapper'; +import { ForwarderWrapper } from '../utils/forwarder_wrapper'; +import { OrderFactory } from '../utils/order_factory'; +import { ContractName, ERC20BalancesByOwner } from '../utils/types'; +import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +const DECIMALS_DEFAULT = 18; +const MAX_WETH_FILL_PERCENTAGE = 95; + +describe(ContractName.Forwarder, () => { + let makerAddress: string; + let owner: string; + let takerAddress: string; + let feeRecipientAddress: string; + let otherAddress: string; + let defaultMakerAssetAddress: string; + let zrxAssetData: string; + let wethAssetData: string; + + let weth: DummyERC20TokenContract; + let zrxToken: DummyERC20TokenContract; + let erc20TokenA: DummyERC20TokenContract; + let erc721Token: DummyERC721TokenContract; + let forwarderContract: ForwarderContract; + let wethContract: WETH9Contract; + let forwarderWrapper: ForwarderWrapper; + let exchangeWrapper: ExchangeWrapper; + + let orderWithoutFee: SignedOrder; + let orderWithFee: SignedOrder; + let feeOrder: SignedOrder; + let orderFactory: OrderFactory; + let erc20Wrapper: ERC20Wrapper; + let erc20Balances: ERC20BalancesByOwner; + let tx: TransactionReceiptWithDecodedLogs; + + let erc721MakerAssetIds: BigNumber[]; + let takerEthBalanceBefore: BigNumber; + let feePercentage: BigNumber; + let gasPrice: BigNumber; + + before(async () => { + await blockchainLifecycle.startAsync(); + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress, otherAddress] = accounts); + + const txHash = await web3Wrapper.sendTransactionAsync({ from: accounts[0], to: accounts[0], value: 0 }); + const transaction = await web3Wrapper.getTransactionByHashAsync(txHash); + gasPrice = new BigNumber(transaction.gasPrice); + + const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); + erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + + const numDummyErc20ToDeploy = 3; + [erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + numDummyErc20ToDeploy, + constants.DUMMY_TOKEN_DECIMALS, + ); + const erc20Proxy = await erc20Wrapper.deployProxyAsync(); + await erc20Wrapper.setBalancesAndAllowancesAsync(); + + [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); + const erc721Proxy = await erc721Wrapper.deployProxyAsync(); + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; + + wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, txDefaults); + weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider); + erc20Wrapper.addDummyTokenContract(weth); + + wethAssetData = assetDataUtils.encodeERC20AssetData(wethContract.address); + zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + zrxAssetData, + ); + exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); + await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); + + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { + from: owner, + }); + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { + from: owner, + }); + + defaultMakerAssetAddress = erc20TokenA.address; + const defaultTakerAssetAddress = wethContract.address; + const defaultOrderParams = { + exchangeAddress: exchangeInstance.address, + makerAddress, + feeRecipientAddress, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS_DEFAULT), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT), + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT), + }; + const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; + orderFactory = new OrderFactory(privateKey, defaultOrderParams); + + const forwarderInstance = await ForwarderContract.deployFrom0xArtifactAsync( + artifacts.Forwarder, + provider, + txDefaults, + exchangeInstance.address, + zrxAssetData, + wethAssetData, + ); + forwarderContract = new ForwarderContract(forwarderInstance.abi, forwarderInstance.address, provider); + forwarderWrapper = new ForwarderWrapper(forwarderContract, provider); + const zrxDepositAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18); + await web3Wrapper.awaitTransactionSuccessAsync( + await zrxToken.transfer.sendTransactionAsync(forwarderContract.address, zrxDepositAmount), + ); + erc20Wrapper.addTokenOwnerAddress(forwarderInstance.address); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + erc20Balances = await erc20Wrapper.getBalancesAsync(); + takerEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(takerAddress); + orderWithoutFee = await orderFactory.newSignedOrderAsync(); + feeOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), + }); + orderWithFee = await orderFactory.newSignedOrderAsync({ + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), + }); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + + describe('constructor', () => { + it('should revert if assetProxy is unregistered', async () => { + const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + zrxAssetData, + ); + return expectContractCreationFailedAsync( + (ForwarderContract.deployFrom0xArtifactAsync( + artifacts.Forwarder, + provider, + txDefaults, + exchangeInstance.address, + zrxAssetData, + wethAssetData, + ) as any) as sendTransactionResult, + RevertReason.UnregisteredAssetProxy, + ); + }); + }); +}); +// tslint:disable:max-file-line-count +// tslint:enable:no-unnecessary-type-assertion -- cgit v1.2.3 From 88595718c3f78d3facbd4ac67ba8328ce9b2bc8a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 15:37:11 -0800 Subject: Yes Compliance Token --- packages/contracts/contracts/tokens/README.md | 2 ++ .../YesComplianceToken/IYesComplianceToken.sol | 3 +- .../WyreERC721Token/ERC721BasicToken.sol | 32 ++++++++-------------- .../WyreERC721Token/ERC721Token.sol | 15 ++-------- .../YesComplianceToken/YesComplianceToken.sol | 2 -- .../test/extensions/compliant_forwarder.ts | 4 ++- 6 files changed, 21 insertions(+), 37 deletions(-) create mode 100644 packages/contracts/contracts/tokens/README.md (limited to 'packages') diff --git a/packages/contracts/contracts/tokens/README.md b/packages/contracts/contracts/tokens/README.md new file mode 100644 index 000000000..b54f0e046 --- /dev/null +++ b/packages/contracts/contracts/tokens/README.md @@ -0,0 +1,2 @@ +Contracts from https://github.com/sendwyre/yes-compliance-token +Modified to compile in our codebase. diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol index 1573c6bac..a1c9b9671 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol @@ -1,5 +1,6 @@ pragma solidity ^0.4.24; +import "./WyreERC721Token/ERC721Token.sol"; /** * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific @@ -30,7 +31,7 @@ pragma solidity ^0.4.24; * * any (non-view) methods not explicitly marked idempotent are not idempotent. */ -contract YesComplianceTokenV1 /*is ERC721Token*/ /*, ERC165 :should: */ { +contract YesComplianceTokenV1 is ERC721Token /*, ERC165 :should: */ { uint256 public constant OWNER_ENTITY_ID = 1; diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol index 1d3fa37b8..788e31580 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol @@ -1,17 +1,15 @@ pragma solidity ^0.4.21; import "./ERC721Basic.sol"; -import "./ERC721Receiver.sol"; -import "../../math/SafeMath.sol"; -import "../../AddressUtils.sol"; -import "../../introspection/ERC165Support.sol"; +import "../../ERC721Token/IERC721Receiver.sol"; +import "../../../utils/SafeMath/SafeMath.sol"; /** * @title ERC721 Non-Fungible Token Standard basic implementation * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ -contract ERC721BasicToken is ERC165Support, ERC721Basic { +contract ERC721BasicToken is ERC721Basic, SafeMath { bytes4 private constant InterfaceId_ERC721 = 0x80ac58cd; /* @@ -33,9 +31,6 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { * bytes4(keccak256('exists(uint256)')) */ - using SafeMath for uint256; - using AddressUtils for address; - // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` bytes4 private constant ERC721_RECEIVED = 0x150b7a02; @@ -70,15 +65,6 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { _; } - function _supportsInterface(bytes4 _interfaceId) - internal - view - returns (bool) - { - return super._supportsInterface(_interfaceId) || - _interfaceId == InterfaceId_ERC721 || _interfaceId == InterfaceId_ERC721Exists; - } - /** * @dev Gets the balance of the specified address * @param _owner address to query the balance of @@ -311,7 +297,7 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { function addTokenTo(address _to, uint256 _tokenId) internal { require(tokenOwner[_tokenId] == address(0)); tokenOwner[_tokenId] = _to; - ownedTokensCount[_to] = ownedTokensCount[_to].add(1); + ownedTokensCount[_to] = safeAdd(ownedTokensCount[_to], 1); } /** @@ -321,7 +307,7 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { */ function removeTokenFrom(address _from, uint256 _tokenId) internal { require(ownerOf(_tokenId) == _from); - ownedTokensCount[_from] = ownedTokensCount[_from].sub(1); + ownedTokensCount[_from] = safeSub(ownedTokensCount[_from], 1); tokenOwner[_tokenId] = address(0); } @@ -343,10 +329,14 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { internal returns (bool) { - if (!_to.isContract()) { + uint256 receiverCodeSize; + assembly { + receiverCodeSize := extcodesize(_to) + } + if (receiverCodeSize == 0) { return true; } - bytes4 retval = ERC721Receiver(_to).onERC721Received( + bytes4 retval = IERC721Receiver(_to).onERC721Received( msg.sender, _from, _tokenId, _data); return (retval == ERC721_RECEIVED); } diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol index c7acee6df..832ff3784 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol @@ -52,20 +52,11 @@ contract ERC721Token is ERC721BasicToken, ERC721 { /** * @dev Constructor function */ - function initialize(string _name, string _symbol) public isInitializer("ERC721Token", "1.9.0") { + function initialize(string _name, string _symbol) public { name_ = _name; symbol_ = _symbol; } - function _supportsInterface(bytes4 _interfaceId) - internal - view - returns (bool) - { - return super._supportsInterface(_interfaceId) || - _interfaceId == InterfaceId_ERC721Enumerable || _interfaceId == InterfaceId_ERC721Metadata; - } - /** * @dev Gets the token name * @return string representing the token name @@ -161,7 +152,7 @@ contract ERC721Token is ERC721BasicToken, ERC721 { super.removeTokenFrom(_from, _tokenId); uint256 tokenIndex = ownedTokensIndex[_tokenId]; - uint256 lastTokenIndex = ownedTokens[_from].length.sub(1); + uint256 lastTokenIndex = safeSub(ownedTokens[_from].length, 1); uint256 lastToken = ownedTokens[_from][lastTokenIndex]; ownedTokens[_from][tokenIndex] = lastToken; @@ -204,7 +195,7 @@ contract ERC721Token is ERC721BasicToken, ERC721 { // Reorg all tokens array uint256 tokenIndex = allTokensIndex[_tokenId]; - uint256 lastTokenIndex = allTokens.length.sub(1); + uint256 lastTokenIndex = safeSub(allTokens.length, 1); uint256 lastToken = allTokens[lastTokenIndex]; allTokens[tokenIndex] = lastToken; diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol index b810d9f31..65ea99d0c 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol @@ -118,7 +118,6 @@ contract YesComplianceToken is YesComplianceTokenV1 { */ function initialize(string _name, string _symbol) { // require(super._symbol.length == 0 || _symbol == super._symbol); // cannot change symbol after first init bc that could fuck shit up - _upgradeable_initialize(); // basically for security super.initialize(_name, _symbol); // init token info // grant the owner token @@ -133,7 +132,6 @@ contract YesComplianceToken is YesComplianceTokenV1 { * executed in lieu of a constructor in a delegated context */ function _upgradeable_initialize() public { - super._upgradeable_initialize(); // provides require(msg.sender == _upgradeable_delegate_owner); // some things are still tied to the owner (instead of the yesmark_owner :notsureif:) ownerAddress = msg.sender; diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 41603e3c2..d26bbd8ec 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -1,3 +1,4 @@ +/* import { BlockchainLifecycle } from '@0x/dev-utils'; import { assetDataUtils } from '@0x/order-utils'; import { RevertReason, SignedOrder } from '@0x/types'; @@ -8,7 +9,7 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { DummyYesComplianceContract } from '../../generated-wrappers/forwarder'; + import { WETH9Contract } from '../../generated-wrappers/weth9'; import { artifacts } from '../../src/artifacts'; import { @@ -189,3 +190,4 @@ describe(ContractName.Forwarder, () => { }); // tslint:disable:max-file-line-count // tslint:enable:no-unnecessary-type-assertion +*/ \ No newline at end of file -- cgit v1.2.3 From 0e0e05e0e07aca3cfbfd13b3fdc00183b7ac5a87 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 16:05:18 -0800 Subject: Compile Compliant Forwarder contract --- .../contracts/extensions/CompliantForwarder/CompliantForwarder.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 646a9f3b8..2febc5cce 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -76,7 +76,7 @@ contract CompliantForwarder { * takerAssetData * signature ------------------------------ - * Context-dependent offsets, unknown at compile time. + * Context-dependent offsets; unknown at compile time. */ // Add 0x4 to a given offset to account for the fillOrder selector prepended to `signedFillOrderTransaction`. // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. -- cgit v1.2.3 From c854c99f20e399bd36f86f69738913c8e5819a9f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 16:16:55 -0800 Subject: template for Compliant Forwarder tests --- .../test/extensions/compliant_forwarder.ts | 59 +++++++--------------- .../test/utils/compliant_forwarder_wrapper.ts | 0 2 files changed, 18 insertions(+), 41 deletions(-) create mode 100644 packages/contracts/test/utils/compliant_forwarder_wrapper.ts (limited to 'packages') diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index d26bbd8ec..bb9493aaa 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -1,4 +1,3 @@ -/* import { BlockchainLifecycle } from '@0x/dev-utils'; import { assetDataUtils } from '@0x/order-utils'; import { RevertReason, SignedOrder } from '@0x/types'; @@ -9,6 +8,7 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; +import { CompliantForwarderContract } from '../../generated-wrappers/compliant_forwarder'; import { WETH9Contract } from '../../generated-wrappers/weth9'; import { artifacts } from '../../src/artifacts'; @@ -46,8 +46,7 @@ describe(ContractName.Forwarder, () => { let weth: DummyERC20TokenContract; let zrxToken: DummyERC20TokenContract; let erc20TokenA: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; - let forwarderContract: ForwarderContract; + let compliantForwarderContract: CompliantForwarderContract; let wethContract: WETH9Contract; let forwarderWrapper: ForwarderWrapper; let exchangeWrapper: ExchangeWrapper; @@ -85,12 +84,6 @@ describe(ContractName.Forwarder, () => { const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - const erc721Proxy = await erc721Wrapper.deployProxyAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; - wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, txDefaults); weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider); erc20Wrapper.addDummyTokenContract(weth); @@ -105,14 +98,10 @@ describe(ContractName.Forwarder, () => { ); exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { from: owner, }); - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { - from: owner, - }); defaultMakerAssetAddress = erc20TokenA.address; const defaultTakerAssetAddress = wethContract.address; @@ -130,21 +119,28 @@ describe(ContractName.Forwarder, () => { const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); - const forwarderInstance = await ForwarderContract.deployFrom0xArtifactAsync( - artifacts.Forwarder, + const compliantForwarderInstance = await CompliantForwarderContract.deployFrom0xArtifactAsync( + artifacts.CompliantForwarder, provider, txDefaults, exchangeInstance.address, - zrxAssetData, - wethAssetData, + exchangeInstance.address, // @TODO CHANGE to Yes Token ); - forwarderContract = new ForwarderContract(forwarderInstance.abi, forwarderInstance.address, provider); - forwarderWrapper = new ForwarderWrapper(forwarderContract, provider); + + compliantForwarderContract = new CompliantForwarderContract( + compliantForwarderInstance.abi, + compliantForwarderInstance.address, + provider, + ); + /* + forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); + */ + const zrxDepositAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18); await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.transfer.sendTransactionAsync(forwarderContract.address, zrxDepositAmount), + await zrxToken.transfer.sendTransactionAsync(compliantForwarderContract.address, zrxDepositAmount), ); - erc20Wrapper.addTokenOwnerAddress(forwarderInstance.address); + erc20Wrapper.addTokenOwnerAddress(compliantForwarderInstance.address); }); after(async () => { await blockchainLifecycle.revertAsync(); @@ -167,27 +163,8 @@ describe(ContractName.Forwarder, () => { }); describe('constructor', () => { - it('should revert if assetProxy is unregistered', async () => { - const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - return expectContractCreationFailedAsync( - (ForwarderContract.deployFrom0xArtifactAsync( - artifacts.Forwarder, - provider, - txDefaults, - exchangeInstance.address, - zrxAssetData, - wethAssetData, - ) as any) as sendTransactionResult, - RevertReason.UnregisteredAssetProxy, - ); - }); + it('should revert if assetProxy is unregistered', async () => {}); }); }); // tslint:disable:max-file-line-count // tslint:enable:no-unnecessary-type-assertion -*/ \ No newline at end of file diff --git a/packages/contracts/test/utils/compliant_forwarder_wrapper.ts b/packages/contracts/test/utils/compliant_forwarder_wrapper.ts new file mode 100644 index 000000000..e69de29bb -- cgit v1.2.3 From b4aca370defb9dfe6c01b60d1b522d4a7b731f43 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 18:09:50 -0800 Subject: Writing tests for Compliant Forwarder --- .../test/extensions/compliant_forwarder.ts | 172 +++++++++++++++------ 1 file changed, 125 insertions(+), 47 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index bb9493aaa..9100c32f8 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -9,6 +9,7 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; import { CompliantForwarderContract } from '../../generated-wrappers/compliant_forwarder'; +import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; import { WETH9Contract } from '../../generated-wrappers/weth9'; import { artifacts } from '../../src/artifacts'; @@ -24,7 +25,9 @@ import { ERC721Wrapper } from '../utils/erc721_wrapper'; import { ExchangeWrapper } from '../utils/exchange_wrapper'; import { ForwarderWrapper } from '../utils/forwarder_wrapper'; import { OrderFactory } from '../utils/order_factory'; -import { ContractName, ERC20BalancesByOwner } from '../utils/types'; +import { orderUtils } from '../utils/order_utils'; +import { TransactionFactory } from '../utils/transaction_factory'; +import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; chaiSetup.configure(); @@ -33,12 +36,12 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const DECIMALS_DEFAULT = 18; const MAX_WETH_FILL_PERCENTAGE = 95; -describe(ContractName.Forwarder, () => { - let makerAddress: string; +describe.only(ContractName.Forwarder, () => { + let compliantMakerAddress: string; let owner: string; - let takerAddress: string; + let compliantTakerAddress: string; let feeRecipientAddress: string; - let otherAddress: string; + let noncompliantAddress: string; let defaultMakerAssetAddress: string; let zrxAssetData: string; let wethAssetData: string; @@ -46,6 +49,7 @@ describe(ContractName.Forwarder, () => { let weth: DummyERC20TokenContract; let zrxToken: DummyERC20TokenContract; let erc20TokenA: DummyERC20TokenContract; + let yesComplianceToken: YesComplianceTokenContract; let compliantForwarderContract: CompliantForwarderContract; let wethContract: WETH9Contract; let forwarderWrapper: ForwarderWrapper; @@ -59,37 +63,73 @@ describe(ContractName.Forwarder, () => { let erc20Balances: ERC20BalancesByOwner; let tx: TransactionReceiptWithDecodedLogs; + let makerAssetAddress: string; + let takerAssetAddress: string; + let erc721MakerAssetIds: BigNumber[]; let takerEthBalanceBefore: BigNumber; let feePercentage: BigNumber; - let gasPrice: BigNumber; + + let compliantSignedOrder: SignedOrder; + let compliantSignedFillOrderTx: SignedTransaction; + let noncompliantSignedFillOrderTx: SignedTransaction; + + const compliantMakerCountryCode = new BigNumber(519); + const compliantMakerYesMark = new BigNumber(1); + const compliantMakerEntityId = new BigNumber(2); + let compliantMakerYesTokenId; + + const compliantTakerCountryCode = new BigNumber(519); + const compliantTakerYesMark = new BigNumber(1); + const compliantTakerEntityId = new BigNumber(2); + let compliantTakerYesTokenId; + + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); + const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); + const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); + const salt = new BigNumber(0); + + let compliantForwarderInstance: CompliantForwarderContract; before(async () => { + // Create accounts await blockchainLifecycle.startAsync(); const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress, otherAddress] = accounts); - - const txHash = await web3Wrapper.sendTransactionAsync({ from: accounts[0], to: accounts[0], value: 0 }); - const transaction = await web3Wrapper.getTransactionByHashAsync(txHash); - gasPrice = new BigNumber(transaction.gasPrice); - + const usedAddresses = ([owner, compliantMakerAddress, compliantTakerAddress, feeRecipientAddress, noncompliantAddress] = accounts); + // Create wrappers const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - + // Deploy ERC20 tokens const numDummyErc20ToDeploy = 3; - [erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + let erc20TokenA: DummyERC20TokenContract; + let erc20TokenB: DummyERC20TokenContract; + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS, ); + makerAssetAddress = erc20TokenA.address; + takerAssetAddress = erc20TokenB.address; + // Deploy Yes Token + const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( + artifacts.YesComplianceToken, + provider, + txDefaults, + ); + compliantForwarderContract = new CompliantForwarderContract( + yesTokenInstance.abi, + yesTokenInstance.address, + provider, + ); + // Create proxies const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); - + // Deploy tokens & set asset data wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, txDefaults); weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider); erc20Wrapper.addDummyTokenContract(weth); - wethAssetData = assetDataUtils.encodeERC20AssetData(wethContract.address); zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + // Deploy Exchange congtract const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, provider, @@ -97,36 +137,35 @@ describe(ContractName.Forwarder, () => { zrxAssetData, ); exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); + // Register proxies await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { from: owner, }); - + // Default order parameters defaultMakerAssetAddress = erc20TokenA.address; const defaultTakerAssetAddress = wethContract.address; const defaultOrderParams = { exchangeAddress: exchangeInstance.address, - makerAddress, + compliantMakerAddress, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS_DEFAULT), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT), + makerAssetAmount: makerAssetAmount, + takerAssetAmount: takerAssetAmount, makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; + const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); - - const compliantForwarderInstance = await CompliantForwarderContract.deployFrom0xArtifactAsync( + // Deploy Compliant Forwarder + compliantForwarderInstance = await CompliantForwarderContract.deployFrom0xArtifactAsync( artifacts.CompliantForwarder, provider, txDefaults, exchangeInstance.address, - exchangeInstance.address, // @TODO CHANGE to Yes Token + yesTokenInstance.address, ); - compliantForwarderContract = new CompliantForwarderContract( compliantForwarderInstance.abi, compliantForwarderInstance.address, @@ -135,35 +174,74 @@ describe(ContractName.Forwarder, () => { /* forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ - - const zrxDepositAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.transfer.sendTransactionAsync(compliantForwarderContract.address, zrxDepositAmount), + // Verify Maker / Taker + const addressesCanControlTheirToken = true; + compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, compliantMakerCountryCode, [compliantMakerYesMark]); + compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, compliantTakerCountryCode, [compliantTakerYesMark]); + // Create Valid/Invalid orders + const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; + const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); + compliantSignedOrder = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderContract.address, + }); + const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(compliantSignedOrder); + const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + compliantSignedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, ); - erc20Wrapper.addTokenOwnerAddress(compliantForwarderInstance.address); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); + compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction(compliantSignedOrderWithoutExchangeAddressData); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); - erc20Balances = await erc20Wrapper.getBalancesAsync(); - takerEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - orderWithoutFee = await orderFactory.newSignedOrderAsync(); - feeOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - }); - orderWithFee = await orderFactory.newSignedOrderAsync({ - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - }); }); afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe('constructor', () => { - it('should revert if assetProxy is unregistered', async () => {}); + describe.only('fillOrder', () => { + let takerAssetFillAmount: BigNumber; + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + + // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract + // @TODO: Should fail if the + + it.only('should transfer the correct amounts when maker and taker are verified', async () => { + await compliantForwarderInstance.fillOrder.sendTransactionAsync(compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][makerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][makerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][takerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][takerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][takerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][takerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][makerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][makerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); }); }); // tslint:disable:max-file-line-count -- cgit v1.2.3 From 003075a8a594060817f2ddcb2124580e27b6b2a8 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 11:11:06 -0800 Subject: WIP - Tests for compliant forwarder --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 1 + .../contracts/test/extensions/compliant_forwarder.ts | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 2febc5cce..0ecf44006 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -51,6 +51,7 @@ contract CompliantForwarder { if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); } + // Extract maker address from fill order transaction // Below is the table of calldata offsets into a fillOrder transaction. diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 9100c32f8..da023a192 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -147,7 +147,7 @@ describe.only(ContractName.Forwarder, () => { const defaultTakerAssetAddress = wethContract.address; const defaultOrderParams = { exchangeAddress: exchangeInstance.address, - compliantMakerAddress, + makerAddress: compliantMakerAddress, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), @@ -174,10 +174,15 @@ describe.only(ContractName.Forwarder, () => { /* forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ + // Initialize Yes Token + await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({from: owner}); + const yesTokenName = "YesToken"; + const yesTokenTicker = "YEET"; + await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, {from: owner}); // Verify Maker / Taker const addressesCanControlTheirToken = true; - compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, compliantMakerCountryCode, [compliantMakerYesMark]); - compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, compliantTakerCountryCode, [compliantTakerYesMark]); + compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, compliantMakerCountryCode, [compliantMakerYesMark], {from: owner}); + compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, compliantTakerCountryCode, [compliantTakerYesMark], {from: owner}); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); @@ -200,13 +205,14 @@ describe.only(ContractName.Forwarder, () => { }); describe.only('fillOrder', () => { - let takerAssetFillAmount: BigNumber; beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract - // @TODO: Should fail if the + // @TODO: Should fail if the signed transaction is not intended for fillOrder + // @TODO: Should fail if maker is not verified + // @TODO: Should fail it taker is not verified it.only('should transfer the correct amounts when maker and taker are verified', async () => { await compliantForwarderInstance.fillOrder.sendTransactionAsync(compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature); -- cgit v1.2.3 From e81f92bffaf7748e710b342224004007261d2991 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 12:58:52 -0800 Subject: End-to-end test for compliant forwarder - works --- .../test/extensions/compliant_forwarder.ts | 56 +++++++++++----------- 1 file changed, 28 insertions(+), 28 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index da023a192..3e8b6a776 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -30,19 +30,24 @@ import { TransactionFactory } from '../utils/transaction_factory'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; +import { AbiEncoder } from '@0x/utils'; +import { MethodAbi } from 'ethereum-types'; + + chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const DECIMALS_DEFAULT = 18; const MAX_WETH_FILL_PERCENTAGE = 95; -describe.only(ContractName.Forwarder, () => { +describe.only(ContractName.CompliantForwarder, () => { let compliantMakerAddress: string; let owner: string; let compliantTakerAddress: string; let feeRecipientAddress: string; let noncompliantAddress: string; let defaultMakerAssetAddress: string; + let defaultTakerAssetAddress: string; let zrxAssetData: string; let wethAssetData: string; @@ -52,19 +57,11 @@ describe.only(ContractName.Forwarder, () => { let yesComplianceToken: YesComplianceTokenContract; let compliantForwarderContract: CompliantForwarderContract; let wethContract: WETH9Contract; - let forwarderWrapper: ForwarderWrapper; let exchangeWrapper: ExchangeWrapper; - let orderWithoutFee: SignedOrder; - let orderWithFee: SignedOrder; - let feeOrder: SignedOrder; let orderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; - let tx: TransactionReceiptWithDecodedLogs; - - let makerAssetAddress: string; - let takerAssetAddress: string; let erc721MakerAssetIds: BigNumber[]; let takerEthBalanceBefore: BigNumber; @@ -107,8 +104,8 @@ describe.only(ContractName.Forwarder, () => { numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS, ); - makerAssetAddress = erc20TokenA.address; - takerAssetAddress = erc20TokenB.address; + defaultMakerAssetAddress = erc20TokenA.address; + defaultTakerAssetAddress = erc20TokenB.address; // Deploy Yes Token const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( artifacts.YesComplianceToken, @@ -143,18 +140,16 @@ describe.only(ContractName.Forwarder, () => { from: owner, }); // Default order parameters - defaultMakerAssetAddress = erc20TokenA.address; - const defaultTakerAssetAddress = wethContract.address; const defaultOrderParams = { exchangeAddress: exchangeInstance.address, makerAddress: compliantMakerAddress, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - makerAssetAmount: makerAssetAmount, - takerAssetAmount: takerAssetAmount, - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), + makerAssetAmount, + takerAssetAmount, + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), }; const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); @@ -187,7 +182,7 @@ describe.only(ContractName.Forwarder, () => { const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); compliantSignedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderContract.address, + senderAddress: compliantForwarderInstance.address, }); const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(compliantSignedOrder); const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( @@ -215,8 +210,13 @@ describe.only(ContractName.Forwarder, () => { // @TODO: Should fail it taker is not verified it.only('should transfer the correct amounts when maker and taker are verified', async () => { - await compliantForwarderInstance.fillOrder.sendTransactionAsync(compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature); - const newBalances = await erc20Wrapper.getBalancesAsync(); + await compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature + ); + const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); @@ -226,20 +226,20 @@ describe.only(ContractName.Forwarder, () => { const takerFeePaid = compliantSignedOrder.takerFee .times(makerAssetFillAmount) .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][makerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][makerAssetAddress].minus(makerAssetFillAmount), + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][takerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][takerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), ); expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), ); - expect(newBalances[compliantTakerAddress][takerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][takerAssetAddress].minus(takerAssetFillAmount), + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][makerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][makerAssetAddress].add(makerAssetFillAmount), + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), ); expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), -- cgit v1.2.3 From a4ab038aa850f53539d0e4652297e52ecd45a8d3 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 13:07:59 -0800 Subject: Ran prettier --- .../test/extensions/compliant_forwarder.ts | 83 +++++++++++++--------- 1 file changed, 50 insertions(+), 33 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 3e8b6a776..91729d158 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -30,15 +30,10 @@ import { TransactionFactory } from '../utils/transaction_factory'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; -import { AbiEncoder } from '@0x/utils'; -import { MethodAbi } from 'ethereum-types'; - - chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const DECIMALS_DEFAULT = 18; -const MAX_WETH_FILL_PERCENTAGE = 95; describe.only(ContractName.CompliantForwarder, () => { let compliantMakerAddress: string; @@ -81,9 +76,9 @@ describe.only(ContractName.CompliantForwarder, () => { const compliantTakerEntityId = new BigNumber(2); let compliantTakerYesTokenId; - const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); - const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); + const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); + const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); const salt = new BigNumber(0); let compliantForwarderInstance: CompliantForwarderContract; @@ -92,7 +87,13 @@ describe.only(ContractName.CompliantForwarder, () => { // Create accounts await blockchainLifecycle.startAsync(); const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, compliantMakerAddress, compliantTakerAddress, feeRecipientAddress, noncompliantAddress] = accounts); + const usedAddresses = ([ + owner, + compliantMakerAddress, + compliantTakerAddress, + feeRecipientAddress, + noncompliantAddress, + ] = accounts); // Create wrappers const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); @@ -107,7 +108,7 @@ describe.only(ContractName.CompliantForwarder, () => { defaultMakerAssetAddress = erc20TokenA.address; defaultTakerAssetAddress = erc20TokenB.address; // Deploy Yes Token - const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( + const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( artifacts.YesComplianceToken, provider, txDefaults, @@ -169,28 +170,46 @@ describe.only(ContractName.CompliantForwarder, () => { /* forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ - // Initialize Yes Token - await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({from: owner}); - const yesTokenName = "YesToken"; - const yesTokenTicker = "YEET"; - await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, {from: owner}); - // Verify Maker / Taker - const addressesCanControlTheirToken = true; - compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, compliantMakerCountryCode, [compliantMakerYesMark], {from: owner}); - compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, compliantTakerCountryCode, [compliantTakerYesMark], {from: owner}); + // Initialize Yes Token + await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({ from: owner }); + const yesTokenName = 'YesToken'; + const yesTokenTicker = 'YEET'; + await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); + // Verify Maker / Taker + const addressesCanControlTheirToken = true; + compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync( + compliantMakerAddress, + compliantMakerEntityId, + addressesCanControlTheirToken, + compliantMakerCountryCode, + [compliantMakerYesMark], + { from: owner }, + ); + compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync( + compliantTakerAddress, + compliantTakerEntityId, + addressesCanControlTheirToken, + compliantTakerCountryCode, + [compliantTakerYesMark], + { from: owner }, + ); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); compliantSignedOrder = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, }); - const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(compliantSignedOrder); + const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + compliantSignedOrder, + ); const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( compliantSignedOrderWithoutExchangeAddress, takerAssetFillAmount, compliantSignedOrder.signature, ); - compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction(compliantSignedOrderWithoutExchangeAddressData); + compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( + compliantSignedOrderWithoutExchangeAddressData, + ); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -198,25 +217,19 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract - // @TODO: Should fail if the signed transaction is not intended for fillOrder - // @TODO: Should fail if maker is not verified - // @TODO: Should fail it taker is not verified - it.only('should transfer the correct amounts when maker and taker are verified', async () => { - await compliantForwarderInstance.fillOrder.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature + await compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, ); - const newBalances = await erc20Wrapper.getBalancesAsync(); + const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); @@ -248,6 +261,10 @@ describe.only(ContractName.CompliantForwarder, () => { erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), ); }); + // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract + // @TODO: Should fail if the signed transaction is not intended for fillOrder + // @TODO: Should fail if maker is not verified + // @TODO: Should fail it taker is not verified }); }); // tslint:disable:max-file-line-count -- cgit v1.2.3 From 989b5b0a98c578321e37439066f9a45287824a6d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 13:13:52 -0800 Subject: Ran linter --- .../CompliantForwarder/CompliantForwarder.sol | 1 - .../test/extensions/compliant_forwarder.ts | 53 +++++----------------- 2 files changed, 11 insertions(+), 43 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 0ecf44006..2febc5cce 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -51,7 +51,6 @@ contract CompliantForwarder { if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); } - // Extract maker address from fill order transaction // Below is the table of calldata offsets into a fillOrder transaction. diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 91729d158..932012c0d 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -4,14 +4,12 @@ import { RevertReason, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; -import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; import { CompliantForwarderContract } from '../../generated-wrappers/compliant_forwarder'; import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; -import { WETH9Contract } from '../../generated-wrappers/weth9'; import { artifacts } from '../../src/artifacts'; import { expectContractCreationFailedAsync, @@ -21,9 +19,7 @@ import { import { chaiSetup } from '../utils/chai_setup'; import { constants } from '../utils/constants'; import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; import { ExchangeWrapper } from '../utils/exchange_wrapper'; -import { ForwarderWrapper } from '../utils/forwarder_wrapper'; import { OrderFactory } from '../utils/order_factory'; import { orderUtils } from '../utils/order_utils'; import { TransactionFactory } from '../utils/transaction_factory'; @@ -44,42 +40,20 @@ describe.only(ContractName.CompliantForwarder, () => { let defaultMakerAssetAddress: string; let defaultTakerAssetAddress: string; let zrxAssetData: string; - let wethAssetData: string; - - let weth: DummyERC20TokenContract; let zrxToken: DummyERC20TokenContract; - let erc20TokenA: DummyERC20TokenContract; - let yesComplianceToken: YesComplianceTokenContract; - let compliantForwarderContract: CompliantForwarderContract; - let wethContract: WETH9Contract; let exchangeWrapper: ExchangeWrapper; let orderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; - let erc721MakerAssetIds: BigNumber[]; - let takerEthBalanceBefore: BigNumber; - let feePercentage: BigNumber; - let compliantSignedOrder: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; let noncompliantSignedFillOrderTx: SignedTransaction; - const compliantMakerCountryCode = new BigNumber(519); - const compliantMakerYesMark = new BigNumber(1); - const compliantMakerEntityId = new BigNumber(2); - let compliantMakerYesTokenId; - - const compliantTakerCountryCode = new BigNumber(519); - const compliantTakerYesMark = new BigNumber(1); - const compliantTakerEntityId = new BigNumber(2); - let compliantTakerYesTokenId; - const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); - const salt = new BigNumber(0); let compliantForwarderInstance: CompliantForwarderContract; @@ -95,7 +69,6 @@ describe.only(ContractName.CompliantForwarder, () => { noncompliantAddress, ] = accounts); // Create wrappers - const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); // Deploy ERC20 tokens const numDummyErc20ToDeploy = 3; @@ -107,26 +80,16 @@ describe.only(ContractName.CompliantForwarder, () => { ); defaultMakerAssetAddress = erc20TokenA.address; defaultTakerAssetAddress = erc20TokenB.address; + zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); // Deploy Yes Token const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( artifacts.YesComplianceToken, provider, txDefaults, ); - compliantForwarderContract = new CompliantForwarderContract( - yesTokenInstance.abi, - yesTokenInstance.address, - provider, - ); // Create proxies const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); - // Deploy tokens & set asset data - wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, txDefaults); - weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider); - erc20Wrapper.addDummyTokenContract(weth); - wethAssetData = assetDataUtils.encodeERC20AssetData(wethContract.address); - zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); // Deploy Exchange congtract const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, @@ -162,12 +125,12 @@ describe.only(ContractName.CompliantForwarder, () => { exchangeInstance.address, yesTokenInstance.address, ); - compliantForwarderContract = new CompliantForwarderContract( + /* + const compliantForwarderContract = new CompliantForwarderContract( compliantForwarderInstance.abi, compliantForwarderInstance.address, provider, ); - /* forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ // Initialize Yes Token @@ -177,7 +140,10 @@ describe.only(ContractName.CompliantForwarder, () => { await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); // Verify Maker / Taker const addressesCanControlTheirToken = true; - compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync( + const compliantMakerCountryCode = new BigNumber(519); + const compliantMakerYesMark = new BigNumber(1); + const compliantMakerEntityId = new BigNumber(2); + await yesTokenInstance.mint2.sendTransactionAsync( compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, @@ -185,7 +151,10 @@ describe.only(ContractName.CompliantForwarder, () => { [compliantMakerYesMark], { from: owner }, ); - compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync( + const compliantTakerCountryCode = new BigNumber(519); + const compliantTakerYesMark = new BigNumber(1); + const compliantTakerEntityId = new BigNumber(2); + await yesTokenInstance.mint2.sendTransactionAsync( compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, -- cgit v1.2.3 From 33e41dd500960fde6bf1f5b1f4cf650731086963 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 13:56:39 -0800 Subject: More tests + require instead of revert in compliance contract --- .../CompliantForwarder/CompliantForwarder.sol | 19 +++-- .../test/extensions/compliant_forwarder.ts | 89 +++++++++++++++++++--- packages/types/src/index.ts | 2 + 3 files changed, 92 insertions(+), 18 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 2febc5cce..f34ee699d 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -51,8 +51,14 @@ contract CompliantForwarder { if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); } + + // Taker must be compliant + require( + COMPLIANCE_TOKEN.balanceOf(signerAddress) > 0, + "TAKER_UNVERIFIED" + ); - // Extract maker address from fill order transaction + // Extract maker address from fill order transaction and ensure maker is compliant // Below is the table of calldata offsets into a fillOrder transaction. /** ### parameters @@ -82,13 +88,10 @@ contract CompliantForwarder { // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. // Putting this together: makerAddress = 0x60 + 0x4 + 0xc = 0x70 address makerAddress = signedFillOrderTransaction.readAddress(0x70); - - // Verify maker/taker have been verified by the compliance token. - if (COMPLIANCE_TOKEN.balanceOf(makerAddress) == 0) { - revert("MAKER_UNVERIFIED"); - } else if (COMPLIANCE_TOKEN.balanceOf(signerAddress) == 0) { - revert("TAKER_UNVERIFIED"); - } + require( + COMPLIANCE_TOKEN.balanceOf(makerAddress) > 0, + "MAKER_UNVERIFIED" + ); // All entities are verified. Execute fillOrder. EXCHANGE.executeTransaction( diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 932012c0d..311ad78e9 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -4,6 +4,7 @@ import { RevertReason, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; +import * as ethUtil from 'ethereumjs-util'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; @@ -12,9 +13,8 @@ import { YesComplianceTokenContract } from '../../generated-wrappers/yes_complia import { artifacts } from '../../src/artifacts'; import { - expectContractCreationFailedAsync, expectTransactionFailedAsync, - sendTransactionResult, + expectTransactionFailedWithoutReasonAsync, } from '../utils/assertions'; import { chaiSetup } from '../utils/chai_setup'; import { constants } from '../utils/constants'; @@ -36,17 +36,19 @@ describe.only(ContractName.CompliantForwarder, () => { let owner: string; let compliantTakerAddress: string; let feeRecipientAddress: string; - let noncompliantAddress: string; + let nonCompliantAddress: string; let defaultMakerAssetAddress: string; let defaultTakerAssetAddress: string; let zrxAssetData: string; let zrxToken: DummyERC20TokenContract; + let exchangeInstance: ExchangeContract; let exchangeWrapper: ExchangeWrapper; let orderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; + let takerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; let noncompliantSignedFillOrderTx: SignedTransaction; @@ -66,7 +68,7 @@ describe.only(ContractName.CompliantForwarder, () => { compliantMakerAddress, compliantTakerAddress, feeRecipientAddress, - noncompliantAddress, + nonCompliantAddress, ] = accounts); // Create wrappers erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); @@ -91,7 +93,7 @@ describe.only(ContractName.CompliantForwarder, () => { const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); // Deploy Exchange congtract - const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( + exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, provider, txDefaults, @@ -164,7 +166,7 @@ describe.only(ContractName.CompliantForwarder, () => { ); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; - const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); + takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); compliantSignedOrder = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, }); @@ -190,8 +192,7 @@ describe.only(ContractName.CompliantForwarder, () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - - it.only('should transfer the correct amounts when maker and taker are verified', async () => { + it('should transfer the correct amounts when maker and taker are compliant', async () => { await compliantForwarderInstance.fillOrder.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, @@ -230,8 +231,76 @@ describe.only(ContractName.CompliantForwarder, () => { erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), ); }); - // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract - // @TODO: Should fail if the signed transaction is not intended for fillOrder + it('should revert if the signed transaction is not intended for fillOrder', async () => { + // Create signed order without the fillOrder function selector + const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); + const selectorLengthInBytes = 4; + const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); + const badSelector = '0x00000000'; + const badSelectorBuf = ethUtil.toBuffer(badSelector); + const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); + const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + txDataBufWithBadSelectorHex, + compliantSignedFillOrderTx.signature, + )); + }); + it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { + // Create signed order with incorrect senderAddress + const notCompliantForwarderAddress = zrxToken.address; + const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: notCompliantForwarderAddress, + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadSenderAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.fillOrder.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + )); + }); + it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadSenderAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedAsync( + compliantForwarderInstance.fillOrder.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + ), + RevertReason.MakerUnverified + ); + }); // @TODO: Should fail if maker is not verified // @TODO: Should fail it taker is not verified }); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 6b728af71..0c6fd7fd7 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -243,6 +243,8 @@ export enum RevertReason { AuctionNotStarted = 'AUCTION_NOT_STARTED', AuctionInvalidBeginTime = 'INVALID_BEGIN_TIME', InvalidAssetData = 'INVALID_ASSET_DATA', + MakerUnverified = 'MAKER_UNVERIFED', + TakerUnverified = 'TAKER_UNVERIFIED', } export enum StatusCodes { -- cgit v1.2.3 From 3f7bd24250d0a965e4f4c95b731b2c6e5a481fcb Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 14:05:04 -0800 Subject: Wrapped up tests for compliant forwarder --- .../CompliantForwarder/CompliantForwarder.sol | 3 ++- .../contracts/test/extensions/compliant_forwarder.ts | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index f34ee699d..aaa2d93d6 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -93,12 +93,13 @@ contract CompliantForwarder { "MAKER_UNVERIFIED" ); + /* // All entities are verified. Execute fillOrder. EXCHANGE.executeTransaction( salt, signerAddress, signedFillOrderTransaction, signature - ); + );*/ } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 311ad78e9..61bbe020c 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -273,14 +273,25 @@ describe.only(ContractName.CompliantForwarder, () => { signedFillOrderTx.signature, )); }); + it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { + return expectTransactionFailedAsync( + compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + nonCompliantAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, + ), + RevertReason.TakerUnverified + ); + }); it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { // Create signed order with non-compliant maker address - const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, makerAddress: nonCompliantAddress }); const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadSenderAddress, + signedOrderWithBadMakerAddress, ); const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( signedOrderWithoutExchangeAddress, @@ -301,8 +312,6 @@ describe.only(ContractName.CompliantForwarder, () => { RevertReason.MakerUnverified ); }); - // @TODO: Should fail if maker is not verified - // @TODO: Should fail it taker is not verified }); }); // tslint:disable:max-file-line-count -- cgit v1.2.3 From 743c4c36eb6300866a0bd0c8a4bf39a3ce7b31c2 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 14:06:09 -0800 Subject: Removed unnecessary comments in compliant forwarder --- .../contracts/extensions/CompliantForwarder/CompliantForwarder.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index aaa2d93d6..f34ee699d 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -93,13 +93,12 @@ contract CompliantForwarder { "MAKER_UNVERIFIED" ); - /* // All entities are verified. Execute fillOrder. EXCHANGE.executeTransaction( salt, signerAddress, signedFillOrderTransaction, signature - );*/ + ); } } \ No newline at end of file -- cgit v1.2.3 From 37a1271af256af2c0a64b947b3c212a749b540b0 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 16:30:51 -0800 Subject: fillOrder -> executeTransaction rename --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 2 +- packages/contracts/test/extensions/compliant_forwarder.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index f34ee699d..7a4c8d2f6 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -38,7 +38,7 @@ contract CompliantForwarder { COMPLIANCE_TOKEN = IERC721Token(complianceToken); } - function fillOrder( + function executeTransaction( uint256 salt, address signerAddress, bytes signedFillOrderTransaction, diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 61bbe020c..c962d82ac 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -193,7 +193,7 @@ describe.only(ContractName.CompliantForwarder, () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); it('should transfer the correct amounts when maker and taker are compliant', async () => { - await compliantForwarderInstance.fillOrder.sendTransactionAsync( + await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, @@ -241,7 +241,7 @@ describe.only(ContractName.CompliantForwarder, () => { const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.fillOrder.sendTransactionAsync( + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, txDataBufWithBadSelectorHex, @@ -266,7 +266,7 @@ describe.only(ContractName.CompliantForwarder, () => { signedOrderWithoutExchangeAddressData, ); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.fillOrder.sendTransactionAsync( + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( signedFillOrderTx.salt, signedFillOrderTx.signerAddress, signedFillOrderTx.data, @@ -275,7 +275,7 @@ describe.only(ContractName.CompliantForwarder, () => { }); it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { return expectTransactionFailedAsync( - compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, nonCompliantAddress, compliantSignedFillOrderTx.data, @@ -303,7 +303,7 @@ describe.only(ContractName.CompliantForwarder, () => { ); // Call compliant forwarder return expectTransactionFailedAsync( - compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( signedFillOrderTx.salt, signedFillOrderTx.signerAddress, signedFillOrderTx.data, -- cgit v1.2.3 From 4e341582ae74d7ab1c52c4e888d72c0c1c78e890 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 18:24:19 -0800 Subject: Extract makerAddress in assembly --- .../CompliantForwarder/CompliantForwarder.sol | 42 ++++++++++++++++++---- .../test/extensions/compliant_forwarder.ts | 16 ++++++++- 2 files changed, 50 insertions(+), 8 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 7a4c8d2f6..b8ba43b15 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -31,6 +31,8 @@ contract CompliantForwarder { IExchange internal EXCHANGE; IERC721Token internal COMPLIANCE_TOKEN; + bytes4 constant internal EXCHANGE_FILL_ORDER_SELECTOR_2 = 0xb4be83d5; + constructor(address exchange, address complianceToken) public { @@ -41,17 +43,43 @@ contract CompliantForwarder { function executeTransaction( uint256 salt, address signerAddress, - bytes signedFillOrderTransaction, + bytes signedExchangeTransaction, bytes signature ) - public + external { // Validate `signedFillOrderTransaction` - bytes4 selector = signedFillOrderTransaction.readBytes4(0); - if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { - revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); + bytes4 selector = signedExchangeTransaction.readBytes4(0); + address makerAddress = 0x00; + assembly { + function getMakerAddress(orderPtr) -> makerAddress { + let orderOffset := calldataload(orderPtr) + makerAddress := calldataload(orderOffset) + } + + switch selector + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { + let exchangeTxPtr := calldataload(0x44) + + // Add 0x20 for length offset and 0x04 for selector offset + let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 + let orderPtr := add(0x4,add(exchangeTxPtr, add(0x24, orderPtrRelativeToExchangeTx))) + + makerAddress := calldataload(orderPtr) + + + //makerAddress := getMakerAddress(orderPtr) + } + default { + // revert(0, 100) + } } + /* + if (selector != 0xb4be83d5) { + revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); + }*/ + // Taker must be compliant require( COMPLIANCE_TOKEN.balanceOf(signerAddress) > 0, @@ -87,7 +115,7 @@ contract CompliantForwarder { // Add 0x4 to a given offset to account for the fillOrder selector prepended to `signedFillOrderTransaction`. // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. // Putting this together: makerAddress = 0x60 + 0x4 + 0xc = 0x70 - address makerAddress = signedFillOrderTransaction.readAddress(0x70); + //address makerAddress = signedExchangeTransaction.readAddress(0x70); require( COMPLIANCE_TOKEN.balanceOf(makerAddress) > 0, "MAKER_UNVERIFIED" @@ -97,7 +125,7 @@ contract CompliantForwarder { EXCHANGE.executeTransaction( salt, signerAddress, - signedFillOrderTransaction, + signedExchangeTransaction, signature ); } diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index c962d82ac..b916f54c9 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -26,6 +26,9 @@ import { TransactionFactory } from '../utils/transaction_factory'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; +import { MethodAbi } from 'ethereum-types'; +import { AbiEncoder } from '@0x/utils'; + chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); @@ -192,7 +195,18 @@ describe.only(ContractName.CompliantForwarder, () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { + it.only('should transfer the correct amounts when maker and taker are compliant', async () => { + + + const method = new AbiEncoder.Method(compliantForwarderInstance.abi[0] as MethodAbi); + const args = [ + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature + ]; + console.log(method.encode(args, {annotate: true})); + await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, -- cgit v1.2.3 From 9f68ac7bbecea109692d62d5555bac67e86c123a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 30 Nov 2018 16:43:04 -0800 Subject: Making progress on generalized forwarder --- .../CompliantForwarder/CompliantForwarder.sol | 140 +++++++++++---------- .../test/extensions/compliant_forwarder.ts | 35 +++--- 2 files changed, 98 insertions(+), 77 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index b8ba43b15..739f55024 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -22,16 +22,20 @@ pragma experimental ABIEncoderV2; import "../../protocol/Exchange/interfaces/IExchange.sol"; import "../../tokens/ERC721Token/IERC721Token.sol"; import "../../utils/LibBytes/LibBytes.sol"; +import "../../utils/ExchangeSelectors/ExchangeSelectors.sol"; -contract CompliantForwarder { +contract CompliantForwarder is ExchangeSelectors{ using LibBytes for bytes; - bytes4 constant internal EXCHANGE_FILL_ORDER_SELECTOR = bytes4(keccak256("fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)")); IExchange internal EXCHANGE; IERC721Token internal COMPLIANCE_TOKEN; - bytes4 constant internal EXCHANGE_FILL_ORDER_SELECTOR_2 = 0xb4be83d5; + event ValidatedAddresses ( + bytes32 selector, + address one, + address[] addresses + ); constructor(address exchange, address complianceToken) public @@ -49,84 +53,94 @@ contract CompliantForwarder { external { // Validate `signedFillOrderTransaction` - bytes4 selector = signedExchangeTransaction.readBytes4(0); - address makerAddress = 0x00; + address[] memory validatedAddresses; + bytes32 selectorS; + address one; assembly { - function getMakerAddress(orderPtr) -> makerAddress { - let orderOffset := calldataload(orderPtr) - makerAddress := calldataload(orderOffset) + // Adds address to validate + function addAddressToValidate(addressToValidate) { + // Compute `addressesToValidate` memory location + let addressesToValidate_ := mload(0x40) + let nAddressesToValidate_ := mload(addressesToValidate_) + + // Increment length + nAddressesToValidate_ := add(mload(addressesToValidate_), 1) + mstore(addressesToValidate_, nAddressesToValidate_) + + // Append address to validate + let offset := mul(32, nAddressesToValidate_) + mstore(add(addressesToValidate_, offset), addressToValidate) } - switch selector - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { + function appendMakerAddressFromOrder(paramIndex) -> makerAddress { let exchangeTxPtr := calldataload(0x44) - // Add 0x20 for length offset and 0x04 for selector offset let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 let orderPtr := add(0x4,add(exchangeTxPtr, add(0x24, orderPtrRelativeToExchangeTx))) - makerAddress := calldataload(orderPtr) - - - //makerAddress := getMakerAddress(orderPtr) + addAddressToValidate(makerAddress) + } + + + // Extract addresses to validate + let exchangeTxPtr1 := calldataload(0x44) + let selector := and(calldataload(add(0x4, add(0x20, exchangeTxPtr1))), 0xffffffff00000000000000000000000000000000000000000000000000000000) + switch selector + case 0x097bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ + { + + } + case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ + { + + } + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 /* fillOrder */ + { + one := appendMakerAddressFromOrder(0) + //appendSignerAddress() } + case 0xd46b02c300000000000000000000000000000000000000000000000000000000 /* cancelOrder */ {} default { - // revert(0, 100) + revert(0, 100) } + + let addressesToValidate := mload(0x40) + let nAddressesToValidate := mload(addressesToValidate) + let newMemFreePtr := add(addressesToValidate, add(0x20, mul(mload(addressesToValidate), 0x20))) + mstore(0x40, newMemFreePtr) + + // Validate addresses + /* + let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) + for {let i := add(32, mload(addressesToValidate))} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { + // call `COMPLIANCE_TOKEN.balanceOf` + let success := call( + gas, // forward all gas + complianceTokenAddress, // call address of asset proxy + 0, // don't send any ETH + i, // pointer to start of input + 32, // length of input (one padded address) + 0, // write output over memory that won't be reused + 0 // don't copy output to memory + ) + if eq(success, 0) { + revert(0, 100) + } + }*/ + + validatedAddresses := addressesToValidate + selectorS := selector } - - /* - if (selector != 0xb4be83d5) { - revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); - }*/ - - // Taker must be compliant - require( - COMPLIANCE_TOKEN.balanceOf(signerAddress) > 0, - "TAKER_UNVERIFIED" - ); - - // Extract maker address from fill order transaction and ensure maker is compliant - // Below is the table of calldata offsets into a fillOrder transaction. - /** - ### parameters - 0x00 ptr - 0x20 takerAssetFillAmount - 0x40 ptr - ### order - 0x60 makerAddress - 0x80 takerAddress - 0xa0 feeRecipientAddress - 0xc0 senderAddress - 0xe0 makerAssetAmount - 0x100 takerAssetAmount - 0x120 makerFee - 0x140 takerFee - 0x160 expirationTimeSeconds - 0x180 salt - 0x1a0 ptr - 0x1c0 ptr - 0x1e0 makerAssetData - * takerAssetData - * signature - ------------------------------ - * Context-dependent offsets; unknown at compile time. - */ - // Add 0x4 to a given offset to account for the fillOrder selector prepended to `signedFillOrderTransaction`. - // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. - // Putting this together: makerAddress = 0x60 + 0x4 + 0xc = 0x70 - //address makerAddress = signedExchangeTransaction.readAddress(0x70); - require( - COMPLIANCE_TOKEN.balanceOf(makerAddress) > 0, - "MAKER_UNVERIFIED" - ); + + emit ValidatedAddresses(selectorS, one, validatedAddresses); // All entities are verified. Execute fillOrder. + /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - ); + );*/ } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index b916f54c9..4eedffe05 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -5,6 +5,7 @@ import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; @@ -26,8 +27,10 @@ import { TransactionFactory } from '../utils/transaction_factory'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; -import { MethodAbi } from 'ethereum-types'; +import { MethodAbi, AbiDefinition } from 'ethereum-types'; import { AbiEncoder } from '@0x/utils'; +import { Method } from '@0x/utils/lib/src/abi_encoder'; +import { LogDecoder } from '../utils/log_decoder'; chaiSetup.configure(); const expect = chai.expect; @@ -184,6 +187,18 @@ describe.only(ContractName.CompliantForwarder, () => { compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( compliantSignedOrderWithoutExchangeAddressData, ); + + /* generate selectors for every exchange method + _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { + try { + const method = new Method(abiDefinition as MethodAbi); + console.log('\n', `// ${method.getDataItem().name}`); + console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); + console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); + } catch(e) { + _.noop(); + } + });*/ }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -196,23 +211,15 @@ describe.only(ContractName.CompliantForwarder, () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); it.only('should transfer the correct amounts when maker and taker are compliant', async () => { - - - const method = new AbiEncoder.Method(compliantForwarderInstance.abi[0] as MethodAbi); - const args = [ - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature - ]; - console.log(method.encode(args, {annotate: true})); - - await compliantForwarderInstance.executeTransaction.sendTransactionAsync( + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature, ); + const decoder = new LogDecoder(web3Wrapper); + const tx = await decoder.getTxWithDecodedLogsAsync(txHash); + console.log(JSON.stringify(tx, null, 4)); const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) @@ -298,7 +305,7 @@ describe.only(ContractName.CompliantForwarder, () => { RevertReason.TakerUnverified ); }); - it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + it.only('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, -- cgit v1.2.3 From 8c9d48477d47fdb974e03e1beb9d6752d1149ea1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 30 Nov 2018 16:43:22 -0800 Subject: Exchange selector contract - autogenerated --- .../utils/ExchangeSelectors/ExchangeSelectors.sol | 162 +++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol (limited to 'packages') diff --git a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol new file mode 100644 index 000000000..d4c9bef28 --- /dev/null +++ b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol @@ -0,0 +1,162 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + + +contract ExchangeSelectors { + // filled + bytes4 constant filledSelector = 0x288cdc91; + bytes4 constant filledSelectorGenerator = bytes4(keccak256('filled(bytes32)')); + + // batchFillOrders + bytes4 constant batchFillOrdersSelector = 0x297bb70b; + bytes4 constant batchFillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + + // cancelled + bytes4 constant cancelledSelector = 0x2ac12622; + bytes4 constant cancelledSelectorGenerator = bytes4(keccak256('cancelled(bytes32)')); + + // preSign + bytes4 constant preSignSelector = 0x3683ef8e; + bytes4 constant preSignSelectorGenerator = bytes4(keccak256('preSign(bytes32,address,bytes)')); + + // matchOrders + bytes4 constant matchOrdersSelector = 0x3c28d861; + bytes4 constant matchOrdersSelectorGenerator = bytes4(keccak256('matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)')); + + // fillOrderNoThrow + bytes4 constant fillOrderNoThrowSelector = 0x3e228bae; + bytes4 constant fillOrderNoThrowSelectorGenerator = bytes4(keccak256('fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + + // assetProxies + bytes4 constant assetProxiesSelector = 0x3fd3c997; + bytes4 constant assetProxiesSelectorGenerator = bytes4(keccak256('assetProxies(bytes4)')); + + // batchCancelOrders + bytes4 constant batchCancelOrdersSelector = 0x4ac14782; + bytes4 constant batchCancelOrdersSelectorGenerator = bytes4(keccak256('batchCancelOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); + + // batchFillOrKillOrders + bytes4 constant batchFillOrKillOrdersSelector = 0x4d0ae546; + bytes4 constant batchFillOrKillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrKillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + + // cancelOrdersUpTo + bytes4 constant cancelOrdersUpToSelector = 0x4f9559b1; + bytes4 constant cancelOrdersUpToSelectorGenerator = bytes4(keccak256('cancelOrdersUpTo(uint256)')); + + // batchFillOrdersNoThrow + bytes4 constant batchFillOrdersNoThrowSelector = 0x50dde190; + bytes4 constant batchFillOrdersNoThrowSelectorGenerator = bytes4(keccak256('batchFillOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + + // getAssetProxy + bytes4 constant getAssetProxySelector = 0x60704108; + bytes4 constant getAssetProxySelectorGenerator = bytes4(keccak256('getAssetProxy(bytes4)')); + + // transactions + bytes4 constant transactionsSelector = 0x642f2eaf; + bytes4 constant transactionsSelectorGenerator = bytes4(keccak256('transactions(bytes32)')); + + // fillOrKillOrder + bytes4 constant fillOrKillOrderSelector = 0x64a3bc15; + bytes4 constant fillOrKillOrderSelectorGenerator = bytes4(keccak256('fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + + // setSignatureValidatorApproval + bytes4 constant setSignatureValidatorApprovalSelector = 0x77fcce68; + bytes4 constant setSignatureValidatorApprovalSelectorGenerator = bytes4(keccak256('setSignatureValidatorApproval(address,bool)')); + + // allowedValidators + bytes4 constant allowedValidatorsSelector = 0x7b8e3514; + bytes4 constant allowedValidatorsSelectorGenerator = bytes4(keccak256('allowedValidators(address,address)')); + + // marketSellOrders + bytes4 constant marketSellOrdersSelector = 0x7e1d9808; + bytes4 constant marketSellOrdersSelectorGenerator = bytes4(keccak256('marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + + // getOrdersInfo + bytes4 constant getOrdersInfoSelector = 0x7e9d74dc; + bytes4 constant getOrdersInfoSelectorGenerator = bytes4(keccak256('getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); + + // preSigned + bytes4 constant preSignedSelector = 0x82c174d0; + bytes4 constant preSignedSelectorGenerator = bytes4(keccak256('preSigned(bytes32,address)')); + + // owner + bytes4 constant ownerSelector = 0x8da5cb5b; + bytes4 constant ownerSelectorGenerator = bytes4(keccak256('owner()')); + + // isValidSignature + bytes4 constant isValidSignatureSelector = 0x93634702; + bytes4 constant isValidSignatureSelectorGenerator = bytes4(keccak256('isValidSignature(bytes32,address,bytes)')); + + // marketBuyOrdersNoThrow + bytes4 constant marketBuyOrdersNoThrowSelector = 0xa3e20380; + bytes4 constant marketBuyOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketBuyOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + + // fillOrder + bytes4 constant fillOrderSelector = 0xb4be83d5; + bytes4 constant fillOrderSelectorGenerator = bytes4(keccak256('fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + + // executeTransaction + bytes4 constant executeTransactionSelector = 0xbfc8bfce; + bytes4 constant executeTransactionSelectorGenerator = bytes4(keccak256('executeTransaction(uint256,address,bytes,bytes)')); + + // registerAssetProxy + bytes4 constant registerAssetProxySelector = 0xc585bb93; + bytes4 constant registerAssetProxySelectorGenerator = bytes4(keccak256('registerAssetProxy(address)')); + + // getOrderInfo + bytes4 constant getOrderInfoSelector = 0xc75e0a81; + bytes4 constant getOrderInfoSelectorGenerator = bytes4(keccak256('getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); + + // cancelOrder + bytes4 constant cancelOrderSelector = 0xd46b02c3; + bytes4 constant cancelOrderSelectorGenerator = bytes4(keccak256('cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); + + // orderEpoch + bytes4 constant orderEpochSelector = 0xd9bfa73e; + bytes4 constant orderEpochSelectorGenerator = bytes4(keccak256('orderEpoch(address,address)')); + + // ZRX_ASSET_DATA + bytes4 constant ZRX_ASSET_DATASelector = 0xdb123b1a; + bytes4 constant ZRX_ASSET_DATASelectorGenerator = bytes4(keccak256('ZRX_ASSET_DATA()')); + + // marketSellOrdersNoThrow + bytes4 constant marketSellOrdersNoThrowSelector = 0xdd1c7d18; + bytes4 constant marketSellOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + + // EIP712_DOMAIN_HASH + bytes4 constant EIP712_DOMAIN_HASHSelector = 0xe306f779; + bytes4 constant EIP712_DOMAIN_HASHSelectorGenerator = bytes4(keccak256('EIP712_DOMAIN_HASH()')); + + // marketBuyOrders + bytes4 constant marketBuyOrdersSelector = 0xe5fa431b; + bytes4 constant marketBuyOrdersSelectorGenerator = bytes4(keccak256('marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + + // currentContextAddress + bytes4 constant currentContextAddressSelector = 0xeea086ba; + bytes4 constant currentContextAddressSelectorGenerator = bytes4(keccak256('currentContextAddress()')); + + // transferOwnership + bytes4 constant transferOwnershipSelector = 0xf2fde38b; + bytes4 constant transferOwnershipSelectorGenerator = bytes4(keccak256('transferOwnership(address)')); + + // VERSION + bytes4 constant VERSIONSelector = 0xffa1ad74; + bytes4 constant VERSIONSelectorGenerator = bytes4(keccak256('VERSION()')); +} \ No newline at end of file -- cgit v1.2.3 From 61a906e9e774b40cb1c1053005fef98aa1cc7c85 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 12:23:30 -0800 Subject: Abstract address validation in asm --- .../CompliantForwarder/CompliantForwarder.sol | 34 +++++++++++++--------- 1 file changed, 21 insertions(+), 13 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 739f55024..27c578eae 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -72,12 +72,12 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } - function appendMakerAddressFromOrder(paramIndex) -> makerAddress { + function appendMakerAddressFromOrder(paramIndex) { let exchangeTxPtr := calldataload(0x44) // Add 0x20 for length offset and 0x04 for selector offset let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 let orderPtr := add(0x4,add(exchangeTxPtr, add(0x24, orderPtrRelativeToExchangeTx))) - makerAddress := calldataload(orderPtr) + let makerAddress := calldataload(orderPtr) addAddressToValidate(makerAddress) } @@ -96,8 +96,8 @@ contract CompliantForwarder is ExchangeSelectors{ } case 0xb4be83d500000000000000000000000000000000000000000000000000000000 /* fillOrder */ { - one := appendMakerAddressFromOrder(0) - //appendSignerAddress() + appendMakerAddressFromOrder(0) + addAddressToValidate(signerAddress) } case 0xd46b02c300000000000000000000000000000000000000000000000000000000 /* cancelOrder */ {} default { @@ -110,23 +110,32 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(0x40, newMemFreePtr) // Validate addresses - /* let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) - for {let i := add(32, mload(addressesToValidate))} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { + for {let i := add(0x20, addressesToValidate)} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { + // Construct calldata for `COMPLIANCE_TOKEN.balanceOf` + mstore(newMemFreePtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) + mstore(add(4, newMemFreePtr), mload(i)) + // call `COMPLIANCE_TOKEN.balanceOf` let success := call( gas, // forward all gas complianceTokenAddress, // call address of asset proxy 0, // don't send any ETH - i, // pointer to start of input - 32, // length of input (one padded address) - 0, // write output over memory that won't be reused - 0 // don't copy output to memory + newMemFreePtr, // pointer to start of input + 0x24, // length of input (one padded address) + newMemFreePtr, // write output to next free memory offset + 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { revert(0, 100) } - }*/ + + // Revert if balance not held + let addressBalance := mload(newMemFreePtr) + if eq(addressBalance, 0) { + revert(0, 100) + } + } validatedAddresses := addressesToValidate selectorS := selector @@ -135,12 +144,11 @@ contract CompliantForwarder is ExchangeSelectors{ emit ValidatedAddresses(selectorS, one, validatedAddresses); // All entities are verified. Execute fillOrder. - /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - );*/ + ); } } \ No newline at end of file -- cgit v1.2.3 From 16bd0ce7ec834029b5a6e8aa308c1d52ce6130ea Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 14:48:10 -0800 Subject: generalized KYC extension passing all tests --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 12 +++++++++++- packages/contracts/test/extensions/compliant_forwarder.ts | 8 ++++---- packages/types/src/index.ts | 1 + 3 files changed, 16 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 27c578eae..b79d8db54 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -127,13 +127,23 @@ contract CompliantForwarder is ExchangeSelectors{ 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { + // Revert with `Error("BALANCE_CHECK_FAILED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001453454e4445525f4e4f545f415554484f52495a454400000000000000) + mstore(96, 0) revert(0, 100) } // Revert if balance not held let addressBalance := mload(newMemFreePtr) if eq(addressBalance, 0) { - revert(0, 100) + // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) + mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) + revert(0, 109) } } diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 4eedffe05..639893798 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -210,7 +210,7 @@ describe.only(ContractName.CompliantForwarder, () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it.only('should transfer the correct amounts when maker and taker are compliant', async () => { + it('should transfer the correct amounts when maker and taker are compliant', async () => { const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, @@ -302,10 +302,10 @@ describe.only(ContractName.CompliantForwarder, () => { compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature, ), - RevertReason.TakerUnverified + RevertReason.AtLeastOneAddressHasZeroBalance ); }); - it.only('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, @@ -330,7 +330,7 @@ describe.only(ContractName.CompliantForwarder, () => { signedFillOrderTx.data, signedFillOrderTx.signature, ), - RevertReason.MakerUnverified + RevertReason.AtLeastOneAddressHasZeroBalance ); }); }); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 0c6fd7fd7..022b24e70 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -245,6 +245,7 @@ export enum RevertReason { InvalidAssetData = 'INVALID_ASSET_DATA', MakerUnverified = 'MAKER_UNVERIFED', TakerUnverified = 'TAKER_UNVERIFIED', + AtLeastOneAddressHasZeroBalance = 'AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE', } export enum StatusCodes { -- cgit v1.2.3 From bab7569ed9b0739a3d01c64dea17adb1bf5b82e1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 15:38:06 -0800 Subject: Refactoring asm --- .../CompliantForwarder/CompliantForwarder.sol | 44 ++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index b79d8db54..4ad9092bd 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -72,7 +72,42 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } - function appendMakerAddressFromOrder(paramIndex) { + function toGlobalCalldataOffset(offset) -> globalOffset { + globalOffset := add(0x4, offset) + } + + function toExchangeCalldataOffset(offset, orderParamIndex) -> exchangeOffset { + // exchangeTxPtr at global level + // 0x20 for length offset into exchange TX + // 0x4 for function selector in exhcange TX + let exchangeTxPtr := calldataload(0x44) + exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) + } + + function toOrderOffset(offset, orderParamIndex) -> orderOffset { + let exchangeOffset := calldataload( + toExchangeCalldataOffset( + offset, + orderParamIndex + ) + ) + orderOffset := toExchangeCalldataOffset(exchangeOffset, orderParamIndex) + } + + // function readMakerFieldFromOrder() + + /* + function readFieldFromOrder() + + function readMakerFieldFromOrder()*/ + + function appendMakerAddressFromOrder(orderParamIndex) { + let makerAddress := calldataload(toOrderOffset(0 /* makerAddress is at 0'th field */, 0 /*order is 1st param*/)) + addAddressToValidate(makerAddress) + } + +/* + function appendMakerAddressFromOrderSet(paramIndex) { let exchangeTxPtr := calldataload(0x44) // Add 0x20 for length offset and 0x04 for selector offset let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 @@ -80,6 +115,7 @@ contract CompliantForwarder is ExchangeSelectors{ let makerAddress := calldataload(orderPtr) addAddressToValidate(makerAddress) } +*/ // Extract addresses to validate @@ -88,11 +124,13 @@ contract CompliantForwarder is ExchangeSelectors{ switch selector case 0x097bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - + //appendMakerAddressFromOrderSet() } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { - + // appendMakerAddressFromOrder(0) + //// appendMakerAddressFromOrder(1) + // addAddressToValidate(signerAddress) } case 0xb4be83d500000000000000000000000000000000000000000000000000000000 /* fillOrder */ { -- cgit v1.2.3 From a332c5e5c2ff11a7d4bd534f694d7859483a33f0 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 16:36:19 -0800 Subject: Code to validate addresses from order arrays --- .../CompliantForwarder/CompliantForwarder.sol | 29 ++++++++++++++++------ .../test/extensions/compliant_forwarder.ts | 28 ++++++++++++++++++++- 2 files changed, 49 insertions(+), 8 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 4ad9092bd..ee32b135e 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -33,7 +33,7 @@ contract CompliantForwarder is ExchangeSelectors{ event ValidatedAddresses ( bytes32 selector, - address one, + bytes32 one, address[] addresses ); @@ -55,7 +55,7 @@ contract CompliantForwarder is ExchangeSelectors{ // Validate `signedFillOrderTransaction` address[] memory validatedAddresses; bytes32 selectorS; - address one; + bytes32 one; assembly { // Adds address to validate function addAddressToValidate(addressToValidate) { @@ -106,6 +106,17 @@ contract CompliantForwarder is ExchangeSelectors{ addAddressToValidate(makerAddress) } + function appendMakerAddressesFromOrderSet(orderSetParamIndex) -> one { + let orderSetPtr := calldataload(toExchangeCalldataOffset(0, 0)) + let orderSetPtrCalldata := toExchangeCalldataOffset(add(orderSetPtr, 0x20), 0) + let orderSetLength := calldataload(toExchangeCalldataOffset(orderSetPtr, 0)) + for {let orderPtrOffset := add(0x20, orderSetPtr)} lt(orderPtrOffset, add(0x20, add(orderSetPtr, mul(0x20, orderSetLength)))) {orderPtrOffset := add(0x20, orderPtrOffset)} { + let orderPtr := calldataload(toExchangeCalldataOffset(orderPtrOffset, 0)) + let makerAddress := calldataload(add(orderSetPtrCalldata, orderPtr)) + addAddressToValidate(makerAddress) + } + } + /* function appendMakerAddressFromOrderSet(paramIndex) { let exchangeTxPtr := calldataload(0x44) @@ -118,13 +129,15 @@ contract CompliantForwarder is ExchangeSelectors{ */ + + // Extract addresses to validate let exchangeTxPtr1 := calldataload(0x44) let selector := and(calldataload(add(0x4, add(0x20, exchangeTxPtr1))), 0xffffffff00000000000000000000000000000000000000000000000000000000) switch selector - case 0x097bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ + case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - //appendMakerAddressFromOrderSet() + one := appendMakerAddressesFromOrderSet(0) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { @@ -147,6 +160,7 @@ contract CompliantForwarder is ExchangeSelectors{ let newMemFreePtr := add(addressesToValidate, add(0x20, mul(mload(addressesToValidate), 0x20))) mstore(0x40, newMemFreePtr) + /* // Validate addresses let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) for {let i := add(0x20, addressesToValidate)} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { @@ -165,7 +179,7 @@ contract CompliantForwarder is ExchangeSelectors{ 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { - // Revert with `Error("BALANCE_CHECK_FAILED")` + // Revert with `Error("BALANCE_CHECK_FAILED")` @TODO mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) mstore(64, 0x0000001453454e4445525f4e4f545f415554484f52495a454400000000000000) @@ -183,7 +197,7 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) revert(0, 109) } - } + }*/ validatedAddresses := addressesToValidate selectorS := selector @@ -192,11 +206,12 @@ contract CompliantForwarder is ExchangeSelectors{ emit ValidatedAddresses(selectorS, one, validatedAddresses); // All entities are verified. Execute fillOrder. + /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - ); + );*/ } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 639893798..846414685 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -206,7 +206,7 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe.only('fillOrder', () => { + describe('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); @@ -334,6 +334,32 @@ describe.only(ContractName.CompliantForwarder, () => { ); }); }); + + describe.only('batchFillOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it.only ('should transfer the correct amounts when maker and taker are compliant', async () => { + let order2 = _.cloneDeep(compliantSignedOrder); + order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; + const orders = [compliantSignedOrder, order2]; + const fillAmounts = [new BigNumber(4), new BigNumber(4)]; + const signatures = ["0xabcd", "0xabcd"]; + const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); + console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + exchangeCalldata, + compliantSignedFillOrderTx.signature, + ); + const decoder = new LogDecoder(web3Wrapper); + const tx = await decoder.getTxWithDecodedLogsAsync(txHash); + console.log(JSON.stringify(tx, null, 4)); + }); + }); }); // tslint:disable:max-file-line-count // tslint:enable:no-unnecessary-type-assertion -- cgit v1.2.3 From 8007ef6c0b08ce7d2394323f000df7baeb254ebf Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:02:58 -0800 Subject: cleaning I --- .../CompliantForwarder/CompliantForwarder.sol | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index ee32b135e..0c596dfc2 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -117,6 +117,22 @@ contract CompliantForwarder is ExchangeSelectors{ } } + function exchangeCalldataload(offset) -> value { + value := calldataload(toExchangeCalldataOffset(offset, 0)) + } + + + function appendMakerAddressesFromOrderSet2(orderSetParamIndex) -> one { + let orderSetPtr := exchangeCalldataload(0) + let orderSetLength := exchangeCalldataload(orderSetPtr) + + for {let orderPtrOffset := add(0x20, orderSetPtr)} lt(orderPtrOffset, add(0x20, add(orderSetPtr, mul(0x20, orderSetLength)))) {orderPtrOffset := add(0x20, orderPtrOffset)} { + let orderPtr := exchangeCalldataload(orderPtrOffset) + let makerAddress := exchangeCalldataload(add(orderSetPtr, add(0x20, orderPtr))) + addAddressToValidate(makerAddress) + } + } + /* function appendMakerAddressFromOrderSet(paramIndex) { let exchangeTxPtr := calldataload(0x44) @@ -137,7 +153,7 @@ contract CompliantForwarder is ExchangeSelectors{ switch selector case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - one := appendMakerAddressesFromOrderSet(0) + one := appendMakerAddressesFromOrderSet2(0) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { -- cgit v1.2.3 From c040ad085063ec67abfd73c5cf61739f54d2a7a6 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:18:34 -0800 Subject: cleaning --- .../CompliantForwarder/CompliantForwarder.sol | 60 ++++------------------ .../test/extensions/compliant_forwarder.ts | 15 +++--- 2 files changed, 19 insertions(+), 56 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 0c596dfc2..2d06d69e6 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -84,76 +84,36 @@ contract CompliantForwarder is ExchangeSelectors{ exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) } - function toOrderOffset(offset, orderParamIndex) -> orderOffset { - let exchangeOffset := calldataload( - toExchangeCalldataOffset( - offset, - orderParamIndex - ) - ) - orderOffset := toExchangeCalldataOffset(exchangeOffset, orderParamIndex) + function exchangeCalldataload(offset) -> value { + + value := calldataload(toExchangeCalldataOffset(offset, 0)) } - // function readMakerFieldFromOrder() - - /* - function readFieldFromOrder() - - function readMakerFieldFromOrder()*/ - function appendMakerAddressFromOrder(orderParamIndex) { - let makerAddress := calldataload(toOrderOffset(0 /* makerAddress is at 0'th field */, 0 /*order is 1st param*/)) + let orderPtr := exchangeCalldataload(0) + let makerAddress := exchangeCalldataload(orderPtr) addAddressToValidate(makerAddress) } function appendMakerAddressesFromOrderSet(orderSetParamIndex) -> one { - let orderSetPtr := calldataload(toExchangeCalldataOffset(0, 0)) - let orderSetPtrCalldata := toExchangeCalldataOffset(add(orderSetPtr, 0x20), 0) - let orderSetLength := calldataload(toExchangeCalldataOffset(orderSetPtr, 0)) - for {let orderPtrOffset := add(0x20, orderSetPtr)} lt(orderPtrOffset, add(0x20, add(orderSetPtr, mul(0x20, orderSetLength)))) {orderPtrOffset := add(0x20, orderPtrOffset)} { - let orderPtr := calldataload(toExchangeCalldataOffset(orderPtrOffset, 0)) - let makerAddress := calldataload(add(orderSetPtrCalldata, orderPtr)) - addAddressToValidate(makerAddress) - } - } - - function exchangeCalldataload(offset) -> value { - value := calldataload(toExchangeCalldataOffset(offset, 0)) - } - - - function appendMakerAddressesFromOrderSet2(orderSetParamIndex) -> one { let orderSetPtr := exchangeCalldataload(0) let orderSetLength := exchangeCalldataload(orderSetPtr) - - for {let orderPtrOffset := add(0x20, orderSetPtr)} lt(orderPtrOffset, add(0x20, add(orderSetPtr, mul(0x20, orderSetLength)))) {orderPtrOffset := add(0x20, orderPtrOffset)} { + let orderSetElementPtr := add(orderSetPtr, 0x20) + let orderSetElementEndPtr := add(orderSetElementPtr, mul(orderSetLength, 0x20)) + for {let orderPtrOffset := orderSetElementPtr} lt(orderPtrOffset, orderSetElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { let orderPtr := exchangeCalldataload(orderPtrOffset) - let makerAddress := exchangeCalldataload(add(orderSetPtr, add(0x20, orderPtr))) + let makerAddress := exchangeCalldataload(add(orderPtr, orderSetElementPtr)) addAddressToValidate(makerAddress) } } -/* - function appendMakerAddressFromOrderSet(paramIndex) { - let exchangeTxPtr := calldataload(0x44) - // Add 0x20 for length offset and 0x04 for selector offset - let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 - let orderPtr := add(0x4,add(exchangeTxPtr, add(0x24, orderPtrRelativeToExchangeTx))) - let makerAddress := calldataload(orderPtr) - addAddressToValidate(makerAddress) - } -*/ - - - - // Extract addresses to validate let exchangeTxPtr1 := calldataload(0x44) let selector := and(calldataload(add(0x4, add(0x20, exchangeTxPtr1))), 0xffffffff00000000000000000000000000000000000000000000000000000000) switch selector case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - one := appendMakerAddressesFromOrderSet2(0) + one := appendMakerAddressesFromOrderSet(0) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 846414685..e995e5435 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -206,11 +206,11 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe('fillOrder', () => { + describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { + it.only('should transfer the correct amounts when maker and taker are compliant', async () => { const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, @@ -220,7 +220,10 @@ describe.only(ContractName.CompliantForwarder, () => { const decoder = new LogDecoder(web3Wrapper); const tx = await decoder.getTxWithDecodedLogsAsync(txHash); console.log(JSON.stringify(tx, null, 4)); - const newBalances = await erc20Wrapper.getBalancesAsync(); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + + + /*const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); @@ -250,7 +253,7 @@ describe.only(ContractName.CompliantForwarder, () => { ); expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); + );*/ }); it('should revert if the signed transaction is not intended for fillOrder', async () => { // Create signed order without the fillOrder function selector @@ -335,11 +338,11 @@ describe.only(ContractName.CompliantForwarder, () => { }); }); - describe.only('batchFillOrders', () => { + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it.only ('should transfer the correct amounts when maker and taker are compliant', async () => { + it('should transfer the correct amounts when maker and taker are compliant', async () => { let order2 = _.cloneDeep(compliantSignedOrder); order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; const orders = [compliantSignedOrder, order2]; -- cgit v1.2.3 From 28a5ed6a9a1e44dd298d98f9f4bcb65b1d021e26 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:19:58 -0800 Subject: cleaning --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 2d06d69e6..687e931da 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -72,21 +72,13 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } - function toGlobalCalldataOffset(offset) -> globalOffset { - globalOffset := add(0x4, offset) - } - - function toExchangeCalldataOffset(offset, orderParamIndex) -> exchangeOffset { + function exchangeCalldataload(offset) -> value { // exchangeTxPtr at global level // 0x20 for length offset into exchange TX // 0x4 for function selector in exhcange TX let exchangeTxPtr := calldataload(0x44) - exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) - } - - function exchangeCalldataload(offset) -> value { - - value := calldataload(toExchangeCalldataOffset(offset, 0)) + let exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) + value := calldataload(exchangeOffset) } function appendMakerAddressFromOrder(orderParamIndex) { -- cgit v1.2.3 From df0de071841e2953f6843a86bbce4ecb3ad7b04f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:26:24 -0800 Subject: cleaning --- .../CompliantForwarder/CompliantForwarder.sol | 28 +++++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 687e931da..b5290dc10 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -72,36 +72,46 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } + function validateAddress(addressToValidate) { + + } + function exchangeCalldataload(offset) -> value { // exchangeTxPtr at global level // 0x20 for length offset into exchange TX // 0x4 for function selector in exhcange TX let exchangeTxPtr := calldataload(0x44) - let exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) + let exchangeOffset := add(exchangeTxPtr, add(0x24, offset)) value := calldataload(exchangeOffset) } + function loadExchangeData(offset) -> value { + value := exchangeCalldataload(add(offset, 0x4)) + } + function appendMakerAddressFromOrder(orderParamIndex) { - let orderPtr := exchangeCalldataload(0) - let makerAddress := exchangeCalldataload(orderPtr) + let orderPtr := loadExchangeData(0) + let makerAddress := loadExchangeData(orderPtr) addAddressToValidate(makerAddress) } function appendMakerAddressesFromOrderSet(orderSetParamIndex) -> one { - let orderSetPtr := exchangeCalldataload(0) - let orderSetLength := exchangeCalldataload(orderSetPtr) + let orderSetPtr := loadExchangeData(0) + let orderSetLength := loadExchangeData(orderSetPtr) let orderSetElementPtr := add(orderSetPtr, 0x20) let orderSetElementEndPtr := add(orderSetElementPtr, mul(orderSetLength, 0x20)) for {let orderPtrOffset := orderSetElementPtr} lt(orderPtrOffset, orderSetElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { - let orderPtr := exchangeCalldataload(orderPtrOffset) - let makerAddress := exchangeCalldataload(add(orderPtr, orderSetElementPtr)) + let orderPtr := loadExchangeData(orderPtrOffset) + let makerAddress := loadExchangeData(add(orderPtr, orderSetElementPtr)) addAddressToValidate(makerAddress) } } // Extract addresses to validate - let exchangeTxPtr1 := calldataload(0x44) - let selector := and(calldataload(add(0x4, add(0x20, exchangeTxPtr1))), 0xffffffff00000000000000000000000000000000000000000000000000000000) + let selector := and( + exchangeCalldataload(0), + 0xffffffff00000000000000000000000000000000000000000000000000000000 + ) switch selector case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { -- cgit v1.2.3 From 5863a29a913e6418c5ae9cbec146ad30c3eee6cc Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:35:45 -0800 Subject: cleaning --- .../CompliantForwarder/CompliantForwarder.sol | 58 +++++++++++----------- .../test/extensions/compliant_forwarder.ts | 6 +-- 2 files changed, 30 insertions(+), 34 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index b5290dc10..55100cae1 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -32,8 +32,6 @@ contract CompliantForwarder is ExchangeSelectors{ IERC721Token internal COMPLIANCE_TOKEN; event ValidatedAddresses ( - bytes32 selector, - bytes32 one, address[] addresses ); @@ -52,11 +50,29 @@ contract CompliantForwarder is ExchangeSelectors{ ) external { - // Validate `signedFillOrderTransaction` + // Addresses that are validated below. address[] memory validatedAddresses; - bytes32 selectorS; - bytes32 one; + + /** + * Do not add variables after this point. + * The assembly block may overwrite their values. + */ + + // Validate addresses assembly { + function exchangeCalldataload(offset) -> value { + // exchangeTxPtr at global level + // 0x20 for length offset into exchange TX + // 0x4 for function selector in exhcange TX + let exchangeTxPtr := calldataload(0x44) + let exchangeOffset := add(exchangeTxPtr, add(0x24, offset)) + value := calldataload(exchangeOffset) + } + + function loadExchangeData(offset) -> value { + value := exchangeCalldataload(add(offset, 0x4)) + } + // Adds address to validate function addAddressToValidate(addressToValidate) { // Compute `addressesToValidate` memory location @@ -72,30 +88,13 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } - function validateAddress(addressToValidate) { - - } - - function exchangeCalldataload(offset) -> value { - // exchangeTxPtr at global level - // 0x20 for length offset into exchange TX - // 0x4 for function selector in exhcange TX - let exchangeTxPtr := calldataload(0x44) - let exchangeOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeOffset) - } - - function loadExchangeData(offset) -> value { - value := exchangeCalldataload(add(offset, 0x4)) - } - function appendMakerAddressFromOrder(orderParamIndex) { let orderPtr := loadExchangeData(0) let makerAddress := loadExchangeData(orderPtr) addAddressToValidate(makerAddress) } - function appendMakerAddressesFromOrderSet(orderSetParamIndex) -> one { + function appendMakerAddressesFromOrderSet(orderSetParamIndex) { let orderSetPtr := loadExchangeData(0) let orderSetLength := loadExchangeData(orderSetPtr) let orderSetElementPtr := add(orderSetPtr, 0x20) @@ -115,7 +114,7 @@ contract CompliantForwarder is ExchangeSelectors{ switch selector case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - one := appendMakerAddressesFromOrderSet(0) + appendMakerAddressesFromOrderSet(0) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { @@ -133,12 +132,12 @@ contract CompliantForwarder is ExchangeSelectors{ revert(0, 100) } + // let addressesToValidate := mload(0x40) let nAddressesToValidate := mload(addressesToValidate) let newMemFreePtr := add(addressesToValidate, add(0x20, mul(mload(addressesToValidate), 0x20))) mstore(0x40, newMemFreePtr) - /* // Validate addresses let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) for {let i := add(0x20, addressesToValidate)} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { @@ -175,21 +174,20 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) revert(0, 109) } - }*/ + } + // Record validated addresses validatedAddresses := addressesToValidate - selectorS := selector } - emit ValidatedAddresses(selectorS, one, validatedAddresses); + emit ValidatedAddresses(validatedAddresses); // All entities are verified. Execute fillOrder. - /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - );*/ + ); } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index e995e5435..cc05f9981 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -221,9 +221,7 @@ describe.only(ContractName.CompliantForwarder, () => { const tx = await decoder.getTxWithDecodedLogsAsync(txHash); console.log(JSON.stringify(tx, null, 4)); console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); - - - /*const newBalances = await erc20Wrapper.getBalancesAsync(); + const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); @@ -253,7 +251,7 @@ describe.only(ContractName.CompliantForwarder, () => { ); expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - );*/ + ); }); it('should revert if the signed transaction is not intended for fillOrder', async () => { // Create signed order without the fillOrder function selector -- cgit v1.2.3 From 4217d0cd7d7993b72415cd34a8fc3fab6c17976e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:43:00 -0800 Subject: cleanup --- .../CompliantForwarder/CompliantForwarder.sol | 33 ++++++++++++---------- 1 file changed, 18 insertions(+), 15 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 55100cae1..f81b975be 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -115,6 +115,7 @@ contract CompliantForwarder is ExchangeSelectors{ case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { appendMakerAddressesFromOrderSet(0) + addAddressToValidate(signerAddress) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { @@ -132,40 +133,42 @@ contract CompliantForwarder is ExchangeSelectors{ revert(0, 100) } - // + // Load addresses to validate from memory let addressesToValidate := mload(0x40) - let nAddressesToValidate := mload(addressesToValidate) - let newMemFreePtr := add(addressesToValidate, add(0x20, mul(mload(addressesToValidate), 0x20))) - mstore(0x40, newMemFreePtr) + let addressesToValidateLength := mload(addressesToValidate) + let addressesToValidateElementPtr := add(addressesToValidate, 0x20) + let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) + + // Record new free memory pointer to after `addressesToValidate` array + // This is to avoid corruption when making calls in the loop below. + let freeMemPtr := addressesToValidateElementEndPtr + mstore(0x40, freeMemPtr) // Validate addresses let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) - for {let i := add(0x20, addressesToValidate)} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { + + for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { // Construct calldata for `COMPLIANCE_TOKEN.balanceOf` - mstore(newMemFreePtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(4, newMemFreePtr), mload(i)) + mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) + mstore(add(4, freeMemPtr), mload(addressToValidate)) // call `COMPLIANCE_TOKEN.balanceOf` let success := call( gas, // forward all gas complianceTokenAddress, // call address of asset proxy 0, // don't send any ETH - newMemFreePtr, // pointer to start of input + freeMemPtr, // pointer to start of input 0x24, // length of input (one padded address) - newMemFreePtr, // write output to next free memory offset + freeMemPtr, // write output to next free memory offset 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { - // Revert with `Error("BALANCE_CHECK_FAILED")` @TODO - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000001453454e4445525f4e4f545f415554484f52495a454400000000000000) - mstore(96, 0) + // @TODO Revert with `Error("BALANCE_CHECK_FAILED")` revert(0, 100) } // Revert if balance not held - let addressBalance := mload(newMemFreePtr) + let addressBalance := mload(freeMemPtr) if eq(addressBalance, 0) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) -- cgit v1.2.3 From 0556defa58ba0a74779a0dc828c112631fb19eb6 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:46:25 -0800 Subject: working on batch fills. Compliance part is finished. --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 8 ++++---- packages/contracts/test/extensions/compliant_forwarder.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index f81b975be..706c2091d 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -171,11 +171,11 @@ contract CompliantForwarder is ExchangeSelectors{ let addressBalance := mload(freeMemPtr) if eq(addressBalance, 0) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + /*mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) mstore(64, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) - revert(0, 109) + revert(0, 109)*/ } } @@ -186,11 +186,11 @@ contract CompliantForwarder is ExchangeSelectors{ emit ValidatedAddresses(validatedAddresses); // All entities are verified. Execute fillOrder. - EXCHANGE.executeTransaction( + /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - ); + );*/ } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index cc05f9981..b066f5d08 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -206,11 +206,11 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe.only('fillOrder', () => { + describe('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it.only('should transfer the correct amounts when maker and taker are compliant', async () => { + it('should transfer the correct amounts when maker and taker are compliant', async () => { const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, @@ -336,7 +336,7 @@ describe.only(ContractName.CompliantForwarder, () => { }); }); - describe('batchFillOrders', () => { + describe.only('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); -- cgit v1.2.3 From 3bb147b0f1554366733098dac1af924225effc26 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 13:37:48 -0800 Subject: Documented inline assembly functions --- .../CompliantForwarder/CompliantForwarder.sol | 200 ++++++++++++++++----- .../test/extensions/compliant_forwarder.ts | 4 +- 2 files changed, 153 insertions(+), 51 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 706c2091d..2dce95716 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -60,93 +60,193 @@ contract CompliantForwarder is ExchangeSelectors{ // Validate addresses assembly { + /** + * Emulates the `calldataload` opcode on the embedded Exchange calldata, + * which is accessed through `signedExchangeTransaction`. + * @param offset - Offset into the Exchange calldata. + * @return value - Corresponding 32 byte value stored at `offset`. + */ function exchangeCalldataload(offset) -> value { - // exchangeTxPtr at global level - // 0x20 for length offset into exchange TX - // 0x4 for function selector in exhcange TX + // Pointer to exchange transaction + // 0x04 for calldata selector + // 0x40 to access `signedExchangeTransaction`, which is the third parameter let exchangeTxPtr := calldataload(0x44) - let exchangeOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeOffset) + + // Offset into Exchange calldata + // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. + // 0x04 for calldata selector + // 0x20 for length field of `signedExchangeTransaction` + let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) + value := calldataload(exchangeCalldataOffset) } + /** + * Convenience function that skips the 4 byte selector when loading + * from the embedded Exchange calldata. + * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) + * @return value - Corresponding 32 byte value stored at `offset` + 4. + */ function loadExchangeData(offset) -> value { value := exchangeCalldataload(add(offset, 0x4)) } - // Adds address to validate - function addAddressToValidate(addressToValidate) { - // Compute `addressesToValidate` memory location + /** + * A running list is maintained of addresses to validate. + * This function records an address in this array. + * @param addressToValidate - Address to record for validation. + * @note - Variables are scoped but names are not, so we append + * underscores to names that share the global namespace. + */ + function recordAddressToValidate(addressToValidate) { + // Compute `addressesToValidate` memory offset let addressesToValidate_ := mload(0x40) let nAddressesToValidate_ := mload(addressesToValidate_) // Increment length - nAddressesToValidate_ := add(mload(addressesToValidate_), 1) + nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) mstore(addressesToValidate_, nAddressesToValidate_) // Append address to validate - let offset := mul(32, nAddressesToValidate_) + let offset := mul(nAddressesToValidate_, 0x20) mstore(add(addressesToValidate_, offset), addressToValidate) } - function appendMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(0) + /** + * Extracts the maker address from an order stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records it in + * the running list of addresses to validate. + * @param orderParamIndex - Index of the order in the Exchange function's signature + */ + function recordMakerAddressFromOrder(orderParamIndex) { + let orderPtr := loadExchangeData(orderParamIndex) let makerAddress := loadExchangeData(orderPtr) - addAddressToValidate(makerAddress) + recordAddressToValidate(makerAddress) } - function appendMakerAddressesFromOrderSet(orderSetParamIndex) { - let orderSetPtr := loadExchangeData(0) - let orderSetLength := loadExchangeData(orderSetPtr) - let orderSetElementPtr := add(orderSetPtr, 0x20) - let orderSetElementEndPtr := add(orderSetElementPtr, mul(orderSetLength, 0x20)) - for {let orderPtrOffset := orderSetElementPtr} lt(orderPtrOffset, orderSetElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { + /** + * Extracts the maker addresses from an array of orders stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records them in + * the running list of addresses to validate. + * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature + */ + function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { + let orderArrayPtr := loadExchangeData(0x0) + let orderArrayLength := loadExchangeData(orderArrayPtr) + let orderArrayElementPtr := add(orderArrayPtr, 0x20) + let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) + for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { let orderPtr := loadExchangeData(orderPtrOffset) - let makerAddress := loadExchangeData(add(orderPtr, orderSetElementPtr)) - addAddressToValidate(makerAddress) + let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) + recordAddressToValidate(makerAddress) } } - // Extract addresses to validate - let selector := and( - exchangeCalldataload(0), - 0xffffffff00000000000000000000000000000000000000000000000000000000 - ) - switch selector - case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ - { - appendMakerAddressesFromOrderSet(0) - addAddressToValidate(signerAddress) + /** + * Records address of signer in the running list of addresses to validate. + * @note: We cannot access `signerAddress` directly from within the asm function, + * so it is loaded from the calldata. + */ + function recordSignerAddress() { + // Load the signer address from calldata + // 0x04 for selector + // 0x20 to access `signerAddress`, which is the second parameter. + let signerAddress_ := calldataload(0x24) + recordAddressToValidate(signerAddress_) + } + + /** + * Records addresses to be validated when Exchange transaction is a batch fill variant. + * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow + * Reference signature: (Order[],uint256[],bytes[]) + */ + function recordAddressesForBatchFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a fill order variant. + * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow + * Reference signature: (Order,uint256,bytes) + */ + function recordAddressesForFillOrderVariant() { + // Record maker address from the order (param index 0) + // The signer is the taker for this order and must also be validated. + recordMakerAddressFromOrder(0) + recordSignerAddress() } - case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ - { - // appendMakerAddressFromOrder(0) - //// appendMakerAddressFromOrder(1) - // addAddressToValidate(signerAddress) + + /** + * Records addresses to be validated when Exchange transaction is a market fill variant. + * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow + * Reference signature: (Order[],uint256,bytes[]) + */ + function recordAddressesForMarketFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() } - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 /* fillOrder */ - { - appendMakerAddressFromOrder(0) - addAddressToValidate(signerAddress) + + /** + * Records addresses to be validated when Exchange transaction is matchOrders. + * Reference signature: matchOrders(Order,Order) + */ + function recordAddressesForMatchOrders() { + // Record maker address from both orders (param indices 0 & 1). + // The signer is the taker and must also be validated. + recordMakerAddressFromOrder(0) + recordMakerAddressFromOrder(1) + recordSignerAddress() } - case 0xd46b02c300000000000000000000000000000000000000000000000000000000 /* cancelOrder */ {} + + ///// Record Addresses to Validate ///// + + // Addresses needing validation depends on which Exchange function is being called. + // Step 1/2 Read the exchange function selector. + let exchangeFunctionSelector := and( + exchangeCalldataload(0x0), + 0xffffffff00000000000000000000000000000000000000000000000000000000 + ) + + // Step 2/2 Extract addresses to validate based on this selector. + // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors + switch exchangeFunctionSelector + case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders + case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow + case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder + case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow + case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder + case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders + case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow + case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders + case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow + case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders + case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder + case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders + case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo default { revert(0, 100) } - // Load addresses to validate from memory + ///// Validate Recorded Addresses ///// + + // Load from memory the addresses to validate let addressesToValidate := mload(0x40) let addressesToValidateLength := mload(addressesToValidate) let addressesToValidateElementPtr := add(addressesToValidate, 0x20) let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) - // Record new free memory pointer to after `addressesToValidate` array + // Set free memory pointer to after `addressesToValidate` array. // This is to avoid corruption when making calls in the loop below. let freeMemPtr := addressesToValidateElementEndPtr mstore(0x40, freeMemPtr) // Validate addresses let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) - for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { // Construct calldata for `COMPLIANCE_TOKEN.balanceOf` mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) @@ -171,11 +271,11 @@ contract CompliantForwarder is ExchangeSelectors{ let addressBalance := mload(freeMemPtr) if eq(addressBalance, 0) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` - /*mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) mstore(64, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) - revert(0, 109)*/ + revert(0, 109) } } @@ -183,14 +283,16 @@ contract CompliantForwarder is ExchangeSelectors{ validatedAddresses := addressesToValidate } + + ///// If we hit this point then all addresses are valid ///// emit ValidatedAddresses(validatedAddresses); - // All entities are verified. Execute fillOrder. - /* EXCHANGE.executeTransaction( + // All addresses are valid. Execute fillOrder. + EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - );*/ + ); } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index b066f5d08..8fa811936 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -206,7 +206,7 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe('fillOrder', () => { + describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); @@ -336,7 +336,7 @@ describe.only(ContractName.CompliantForwarder, () => { }); }); - describe.only('batchFillOrders', () => { + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); -- cgit v1.2.3 From ba986432eca33ffb4d3c916a78049c6633147b82 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 14:03:48 -0800 Subject: Some comments --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 2dce95716..71c124292 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -229,6 +229,7 @@ contract CompliantForwarder is ExchangeSelectors{ case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo default { + // @TODO Revert with `Error("INVALID_OR_UNHANDLED_EXCHANGE_SELECTOR")` revert(0, 100) } @@ -272,9 +273,9 @@ contract CompliantForwarder is ExchangeSelectors{ if eq(addressBalance, 0) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) - mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) + mstore(0x60, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) revert(0, 109) } } @@ -283,7 +284,6 @@ contract CompliantForwarder is ExchangeSelectors{ validatedAddresses := addressesToValidate } - ///// If we hit this point then all addresses are valid ///// emit ValidatedAddresses(validatedAddresses); -- cgit v1.2.3 From dbf1de2e691743672fa4918e7ab0f1e3948401f1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 14:21:07 -0800 Subject: Revert reasons for balance threshold filter --- .../CompliantForwarder/CompliantForwarder.sol | 41 +++++++++++++++++----- .../test/extensions/compliant_forwarder.ts | 4 +-- packages/types/src/index.ts | 7 ++-- 3 files changed, 39 insertions(+), 13 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 71c124292..d33f4f398 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -229,8 +229,18 @@ contract CompliantForwarder is ExchangeSelectors{ case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo default { - // @TODO Revert with `Error("INVALID_OR_UNHANDLED_EXCHANGE_SELECTOR")` - revert(0, 100) + // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) + mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. + revert(0, 132) } ///// Validate Recorded Addresses ///// @@ -264,19 +274,34 @@ contract CompliantForwarder is ExchangeSelectors{ 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { - // @TODO Revert with `Error("BALANCE_CHECK_FAILED")` + // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) + mstore(0x60, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. revert(0, 100) } // Revert if balance not held let addressBalance := mload(freeMemPtr) if eq(addressBalance, 0) { - // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) - mstore(0x60, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) - revert(0, 109) + mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) + mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. + revert(0, 132) } } diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 8fa811936..196167264 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -303,7 +303,7 @@ describe.only(ContractName.CompliantForwarder, () => { compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature, ), - RevertReason.AtLeastOneAddressHasZeroBalance + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { @@ -331,7 +331,7 @@ describe.only(ContractName.CompliantForwarder, () => { signedFillOrderTx.data, signedFillOrderTx.signature, ), - RevertReason.AtLeastOneAddressHasZeroBalance + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); }); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 022b24e70..4d5b6e1f2 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -243,9 +243,10 @@ export enum RevertReason { AuctionNotStarted = 'AUCTION_NOT_STARTED', AuctionInvalidBeginTime = 'INVALID_BEGIN_TIME', InvalidAssetData = 'INVALID_ASSET_DATA', - MakerUnverified = 'MAKER_UNVERIFED', - TakerUnverified = 'TAKER_UNVERIFIED', - AtLeastOneAddressHasZeroBalance = 'AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE', + // Balance Threshold Filter + InvalidOrBlockedExchangeSelector = 'INVALID_OR_BLOCKED_EXCHANGE_SELECTOR', + BalanceQueryFailed = 'BALANCE_QUERY_FAILED', + AtLeastOneAddressDoesNotMeetBalanceThreshold= 'AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD', } export enum StatusCodes { -- cgit v1.2.3 From 1cdd82178ff630827095e778a222fafa4161969e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 15:23:25 -0800 Subject: ComplianceForwarder renamed to BalanceThresholdFilter --- .../BalanceThresholdFilter.sol | 323 ++++++++++++++++++ .../interfaces/IThresholdAsset.sol | 30 ++ .../CompliantForwarder/CompliantForwarder.sol | 323 ------------------ .../test/extensions/balance_threshold_filter.ts | 366 +++++++++++++++++++++ .../test/extensions/compliant_forwarder.ts | 366 --------------------- 5 files changed, 719 insertions(+), 689 deletions(-) create mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol create mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol delete mode 100644 packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol create mode 100644 packages/contracts/test/extensions/balance_threshold_filter.ts delete mode 100644 packages/contracts/test/extensions/compliant_forwarder.ts (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol new file mode 100644 index 000000000..93b63eb52 --- /dev/null +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol @@ -0,0 +1,323 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/Exchange/interfaces/IExchange.sol"; +import "../../utils/LibBytes/LibBytes.sol"; +import "../../utils/ExchangeSelectors/ExchangeSelectors.sol"; +import "./interfaces/IThresholdAsset.sol"; + +contract BalanceThresholdFilter is ExchangeSelectors { + + using LibBytes for bytes; + + IExchange internal EXCHANGE; + IThresholdAsset internal THRESHOLD_ASSET; + + event ValidatedAddresses ( + address[] addresses + ); + + constructor(address exchange, address thresholdAsset) + public + { + EXCHANGE = IExchange(exchange); + THRESHOLD_ASSET = IThresholdAsset(thresholdAsset); + } + + function executeTransaction( + uint256 salt, + address signerAddress, + bytes signedExchangeTransaction, + bytes signature + ) + external + { + // Addresses that are validated below. + address[] memory validatedAddresses; + + /** + * Do not add variables after this point. + * The assembly block may overwrite their values. + */ + + // Validate addresses + assembly { + /** + * Emulates the `calldataload` opcode on the embedded Exchange calldata, + * which is accessed through `signedExchangeTransaction`. + * @param offset - Offset into the Exchange calldata. + * @return value - Corresponding 32 byte value stored at `offset`. + */ + function exchangeCalldataload(offset) -> value { + // Pointer to exchange transaction + // 0x04 for calldata selector + // 0x40 to access `signedExchangeTransaction`, which is the third parameter + let exchangeTxPtr := calldataload(0x44) + + // Offset into Exchange calldata + // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. + // 0x04 for calldata selector + // 0x20 for length field of `signedExchangeTransaction` + let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) + value := calldataload(exchangeCalldataOffset) + } + + /** + * Convenience function that skips the 4 byte selector when loading + * from the embedded Exchange calldata. + * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) + * @return value - Corresponding 32 byte value stored at `offset` + 4. + */ + function loadExchangeData(offset) -> value { + value := exchangeCalldataload(add(offset, 0x4)) + } + + /** + * A running list is maintained of addresses to validate. + * This function records an address in this array. + * @param addressToValidate - Address to record for validation. + * @note - Variables are scoped but names are not, so we append + * underscores to names that share the global namespace. + */ + function recordAddressToValidate(addressToValidate) { + // Compute `addressesToValidate` memory offset + let addressesToValidate_ := mload(0x40) + let nAddressesToValidate_ := mload(addressesToValidate_) + + // Increment length + nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) + mstore(addressesToValidate_, nAddressesToValidate_) + + // Append address to validate + let offset := mul(nAddressesToValidate_, 0x20) + mstore(add(addressesToValidate_, offset), addressToValidate) + } + + /** + * Extracts the maker address from an order stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records it in + * the running list of addresses to validate. + * @param orderParamIndex - Index of the order in the Exchange function's signature + */ + function recordMakerAddressFromOrder(orderParamIndex) { + let orderPtr := loadExchangeData(orderParamIndex) + let makerAddress := loadExchangeData(orderPtr) + recordAddressToValidate(makerAddress) + } + + /** + * Extracts the maker addresses from an array of orders stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records them in + * the running list of addresses to validate. + * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature + */ + function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { + let orderArrayPtr := loadExchangeData(0x0) + let orderArrayLength := loadExchangeData(orderArrayPtr) + let orderArrayElementPtr := add(orderArrayPtr, 0x20) + let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) + for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { + let orderPtr := loadExchangeData(orderPtrOffset) + let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) + recordAddressToValidate(makerAddress) + } + } + + /** + * Records address of signer in the running list of addresses to validate. + * @note: We cannot access `signerAddress` directly from within the asm function, + * so it is loaded from the calldata. + */ + function recordSignerAddress() { + // Load the signer address from calldata + // 0x04 for selector + // 0x20 to access `signerAddress`, which is the second parameter. + let signerAddress_ := calldataload(0x24) + recordAddressToValidate(signerAddress_) + } + + /** + * Records addresses to be validated when Exchange transaction is a batch fill variant. + * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow + * Reference signature: (Order[],uint256[],bytes[]) + */ + function recordAddressesForBatchFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a fill order variant. + * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow + * Reference signature: (Order,uint256,bytes) + */ + function recordAddressesForFillOrderVariant() { + // Record maker address from the order (param index 0) + // The signer is the taker for this order and must also be validated. + recordMakerAddressFromOrder(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a market fill variant. + * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow + * Reference signature: (Order[],uint256,bytes[]) + */ + function recordAddressesForMarketFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is matchOrders. + * Reference signature: matchOrders(Order,Order) + */ + function recordAddressesForMatchOrders() { + // Record maker address from both orders (param indices 0 & 1). + // The signer is the taker and must also be validated. + recordMakerAddressFromOrder(0) + recordMakerAddressFromOrder(1) + recordSignerAddress() + } + + ///// Record Addresses to Validate ///// + + // Addresses needing validation depends on which Exchange function is being called. + // Step 1/2 Read the exchange function selector. + let exchangeFunctionSelector := and( + exchangeCalldataload(0x0), + 0xffffffff00000000000000000000000000000000000000000000000000000000 + ) + + // Step 2/2 Extract addresses to validate based on this selector. + // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors + switch exchangeFunctionSelector + case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders + case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow + case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder + case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow + case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder + case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders + case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow + case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders + case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow + case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders + case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder + case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders + case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo + default { + // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) + mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. + revert(0, 132) + } + + ///// Validate Recorded Addresses ///// + + // Load from memory the addresses to validate + let addressesToValidate := mload(0x40) + let addressesToValidateLength := mload(addressesToValidate) + let addressesToValidateElementPtr := add(addressesToValidate, 0x20) + let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) + + // Set free memory pointer to after `addressesToValidate` array. + // This is to avoid corruption when making calls in the loop below. + let freeMemPtr := addressesToValidateElementEndPtr + mstore(0x40, freeMemPtr) + + // Validate addresses + let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) + for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { + // Construct calldata for `THRESHOLD_ASSET.balanceOf` + mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) + mstore(add(4, freeMemPtr), mload(addressToValidate)) + + // call `THRESHOLD_ASSET.balanceOf` + let success := call( + gas, // forward all gas + thresholdAssetAddress, // call address of asset proxy + 0, // don't send any ETH + freeMemPtr, // pointer to start of input + 0x24, // length of input (one padded address) + freeMemPtr, // write output to next free memory offset + 0x20 // reserve space for return balance (0x20 bytes) + ) + if eq(success, 0) { + // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) + mstore(0x60, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. + revert(0, 100) + } + + // Revert if balance not held + let addressBalance := mload(freeMemPtr) + if eq(addressBalance, 0) { + // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) + mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. + revert(0, 132) + } + } + + // Record validated addresses + validatedAddresses := addressesToValidate + } + + ///// If we hit this point then all addresses are valid ///// + emit ValidatedAddresses(validatedAddresses); + + // All addresses are valid. Execute fillOrder. + EXCHANGE.executeTransaction( + salt, + signerAddress, + signedExchangeTransaction, + signature + ); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol new file mode 100644 index 000000000..61acaba0a --- /dev/null +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol @@ -0,0 +1,30 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +contract IThresholdAsset { + + /// @param _owner The address from which the balance will be retrieved + /// @return Balance of owner + function balanceOf(address _owner) + external + view + returns (uint256); + +} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol deleted file mode 100644 index d33f4f398..000000000 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ /dev/null @@ -1,323 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; - -import "../../protocol/Exchange/interfaces/IExchange.sol"; -import "../../tokens/ERC721Token/IERC721Token.sol"; -import "../../utils/LibBytes/LibBytes.sol"; -import "../../utils/ExchangeSelectors/ExchangeSelectors.sol"; - -contract CompliantForwarder is ExchangeSelectors{ - - using LibBytes for bytes; - - IExchange internal EXCHANGE; - IERC721Token internal COMPLIANCE_TOKEN; - - event ValidatedAddresses ( - address[] addresses - ); - - constructor(address exchange, address complianceToken) - public - { - EXCHANGE = IExchange(exchange); - COMPLIANCE_TOKEN = IERC721Token(complianceToken); - } - - function executeTransaction( - uint256 salt, - address signerAddress, - bytes signedExchangeTransaction, - bytes signature - ) - external - { - // Addresses that are validated below. - address[] memory validatedAddresses; - - /** - * Do not add variables after this point. - * The assembly block may overwrite their values. - */ - - // Validate addresses - assembly { - /** - * Emulates the `calldataload` opcode on the embedded Exchange calldata, - * which is accessed through `signedExchangeTransaction`. - * @param offset - Offset into the Exchange calldata. - * @return value - Corresponding 32 byte value stored at `offset`. - */ - function exchangeCalldataload(offset) -> value { - // Pointer to exchange transaction - // 0x04 for calldata selector - // 0x40 to access `signedExchangeTransaction`, which is the third parameter - let exchangeTxPtr := calldataload(0x44) - - // Offset into Exchange calldata - // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. - // 0x04 for calldata selector - // 0x20 for length field of `signedExchangeTransaction` - let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeCalldataOffset) - } - - /** - * Convenience function that skips the 4 byte selector when loading - * from the embedded Exchange calldata. - * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) - * @return value - Corresponding 32 byte value stored at `offset` + 4. - */ - function loadExchangeData(offset) -> value { - value := exchangeCalldataload(add(offset, 0x4)) - } - - /** - * A running list is maintained of addresses to validate. - * This function records an address in this array. - * @param addressToValidate - Address to record for validation. - * @note - Variables are scoped but names are not, so we append - * underscores to names that share the global namespace. - */ - function recordAddressToValidate(addressToValidate) { - // Compute `addressesToValidate` memory offset - let addressesToValidate_ := mload(0x40) - let nAddressesToValidate_ := mload(addressesToValidate_) - - // Increment length - nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) - mstore(addressesToValidate_, nAddressesToValidate_) - - // Append address to validate - let offset := mul(nAddressesToValidate_, 0x20) - mstore(add(addressesToValidate_, offset), addressToValidate) - } - - /** - * Extracts the maker address from an order stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records it in - * the running list of addresses to validate. - * @param orderParamIndex - Index of the order in the Exchange function's signature - */ - function recordMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(orderParamIndex) - let makerAddress := loadExchangeData(orderPtr) - recordAddressToValidate(makerAddress) - } - - /** - * Extracts the maker addresses from an array of orders stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records them in - * the running list of addresses to validate. - * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature - */ - function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { - let orderArrayPtr := loadExchangeData(0x0) - let orderArrayLength := loadExchangeData(orderArrayPtr) - let orderArrayElementPtr := add(orderArrayPtr, 0x20) - let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) - for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { - let orderPtr := loadExchangeData(orderPtrOffset) - let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) - recordAddressToValidate(makerAddress) - } - } - - /** - * Records address of signer in the running list of addresses to validate. - * @note: We cannot access `signerAddress` directly from within the asm function, - * so it is loaded from the calldata. - */ - function recordSignerAddress() { - // Load the signer address from calldata - // 0x04 for selector - // 0x20 to access `signerAddress`, which is the second parameter. - let signerAddress_ := calldataload(0x24) - recordAddressToValidate(signerAddress_) - } - - /** - * Records addresses to be validated when Exchange transaction is a batch fill variant. - * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow - * Reference signature: (Order[],uint256[],bytes[]) - */ - function recordAddressesForBatchFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is a fill order variant. - * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow - * Reference signature: (Order,uint256,bytes) - */ - function recordAddressesForFillOrderVariant() { - // Record maker address from the order (param index 0) - // The signer is the taker for this order and must also be validated. - recordMakerAddressFromOrder(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is a market fill variant. - * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow - * Reference signature: (Order[],uint256,bytes[]) - */ - function recordAddressesForMarketFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is matchOrders. - * Reference signature: matchOrders(Order,Order) - */ - function recordAddressesForMatchOrders() { - // Record maker address from both orders (param indices 0 & 1). - // The signer is the taker and must also be validated. - recordMakerAddressFromOrder(0) - recordMakerAddressFromOrder(1) - recordSignerAddress() - } - - ///// Record Addresses to Validate ///// - - // Addresses needing validation depends on which Exchange function is being called. - // Step 1/2 Read the exchange function selector. - let exchangeFunctionSelector := and( - exchangeCalldataload(0x0), - 0xffffffff00000000000000000000000000000000000000000000000000000000 - ) - - // Step 2/2 Extract addresses to validate based on this selector. - // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors - switch exchangeFunctionSelector - case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders - case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow - case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder - case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow - case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder - case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders - case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow - case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders - case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow - case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders - case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder - case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders - case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo - default { - // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) - mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. - revert(0, 132) - } - - ///// Validate Recorded Addresses ///// - - // Load from memory the addresses to validate - let addressesToValidate := mload(0x40) - let addressesToValidateLength := mload(addressesToValidate) - let addressesToValidateElementPtr := add(addressesToValidate, 0x20) - let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) - - // Set free memory pointer to after `addressesToValidate` array. - // This is to avoid corruption when making calls in the loop below. - let freeMemPtr := addressesToValidateElementEndPtr - mstore(0x40, freeMemPtr) - - // Validate addresses - let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) - for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { - // Construct calldata for `COMPLIANCE_TOKEN.balanceOf` - mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(4, freeMemPtr), mload(addressToValidate)) - - // call `COMPLIANCE_TOKEN.balanceOf` - let success := call( - gas, // forward all gas - complianceTokenAddress, // call address of asset proxy - 0, // don't send any ETH - freeMemPtr, // pointer to start of input - 0x24, // length of input (one padded address) - freeMemPtr, // write output to next free memory offset - 0x20 // reserve space for return balance (0x20 bytes) - ) - if eq(success, 0) { - // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) - mstore(0x60, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. - revert(0, 100) - } - - // Revert if balance not held - let addressBalance := mload(freeMemPtr) - if eq(addressBalance, 0) { - // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) - mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. - revert(0, 132) - } - } - - // Record validated addresses - validatedAddresses := addressesToValidate - } - - ///// If we hit this point then all addresses are valid ///// - emit ValidatedAddresses(validatedAddresses); - - // All addresses are valid. Execute fillOrder. - EXCHANGE.executeTransaction( - salt, - signerAddress, - signedExchangeTransaction, - signature - ); - } -} \ No newline at end of file diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts new file mode 100644 index 000000000..50fd79439 --- /dev/null +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -0,0 +1,366 @@ +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils } from '@0x/order-utils'; +import { RevertReason, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; +import { ExchangeContract } from '../../generated-wrappers/exchange'; +import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; +import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; + +import { artifacts } from '../../src/artifacts'; +import { + expectTransactionFailedAsync, + expectTransactionFailedWithoutReasonAsync, +} from '../utils/assertions'; +import { chaiSetup } from '../utils/chai_setup'; +import { constants } from '../utils/constants'; +import { ERC20Wrapper } from '../utils/erc20_wrapper'; +import { ExchangeWrapper } from '../utils/exchange_wrapper'; +import { OrderFactory } from '../utils/order_factory'; +import { orderUtils } from '../utils/order_utils'; +import { TransactionFactory } from '../utils/transaction_factory'; +import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; +import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; + +import { MethodAbi, AbiDefinition } from 'ethereum-types'; +import { AbiEncoder } from '@0x/utils'; +import { Method } from '@0x/utils/lib/src/abi_encoder'; +import { LogDecoder } from '../utils/log_decoder'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +const DECIMALS_DEFAULT = 18; + +describe.only(ContractName.BalanceThresholdFilter, () => { + let compliantMakerAddress: string; + let owner: string; + let compliantTakerAddress: string; + let feeRecipientAddress: string; + let nonCompliantAddress: string; + let defaultMakerAssetAddress: string; + let defaultTakerAssetAddress: string; + let zrxAssetData: string; + let zrxToken: DummyERC20TokenContract; + let exchangeInstance: ExchangeContract; + let exchangeWrapper: ExchangeWrapper; + + let orderFactory: OrderFactory; + let erc20Wrapper: ERC20Wrapper; + let erc20Balances: ERC20BalancesByOwner; + + let takerTransactionFactory: TransactionFactory; + let compliantSignedOrder: SignedOrder; + let compliantSignedFillOrderTx: SignedTransaction; + let noncompliantSignedFillOrderTx: SignedTransaction; + + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); + const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); + const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); + + let compliantForwarderInstance: BalanceThresholdFilterContract; + + before(async () => { + // Create accounts + await blockchainLifecycle.startAsync(); + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const usedAddresses = ([ + owner, + compliantMakerAddress, + compliantTakerAddress, + feeRecipientAddress, + nonCompliantAddress, + ] = accounts); + // Create wrappers + erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + // Deploy ERC20 tokens + const numDummyErc20ToDeploy = 3; + let erc20TokenA: DummyERC20TokenContract; + let erc20TokenB: DummyERC20TokenContract; + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + numDummyErc20ToDeploy, + constants.DUMMY_TOKEN_DECIMALS, + ); + defaultMakerAssetAddress = erc20TokenA.address; + defaultTakerAssetAddress = erc20TokenB.address; + zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + // Deploy Yes Token + const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( + artifacts.YesComplianceToken, + provider, + txDefaults, + ); + // Create proxies + const erc20Proxy = await erc20Wrapper.deployProxyAsync(); + await erc20Wrapper.setBalancesAndAllowancesAsync(); + // Deploy Exchange congtract + exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + zrxAssetData, + ); + exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); + // Register proxies + await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { + from: owner, + }); + // Default order parameters + const defaultOrderParams = { + exchangeAddress: exchangeInstance.address, + makerAddress: compliantMakerAddress, + feeRecipientAddress, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), + makerAssetAmount, + takerAssetAmount, + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), + }; + const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; + orderFactory = new OrderFactory(privateKey, defaultOrderParams); + // Deploy Compliant Forwarder + compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( + artifacts.BalanceThresholdFilter, + provider, + txDefaults, + exchangeInstance.address, + yesTokenInstance.address, + ); + /* + const compliantForwarderContract = new BalanceThresholdFilterContract( + compliantForwarderInstance.abi, + compliantForwarderInstance.address, + provider, + ); + forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); + */ + // Initialize Yes Token + await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({ from: owner }); + const yesTokenName = 'YesToken'; + const yesTokenTicker = 'YEET'; + await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); + // Verify Maker / Taker + const addressesCanControlTheirToken = true; + const compliantMakerCountryCode = new BigNumber(519); + const compliantMakerYesMark = new BigNumber(1); + const compliantMakerEntityId = new BigNumber(2); + await yesTokenInstance.mint2.sendTransactionAsync( + compliantMakerAddress, + compliantMakerEntityId, + addressesCanControlTheirToken, + compliantMakerCountryCode, + [compliantMakerYesMark], + { from: owner }, + ); + const compliantTakerCountryCode = new BigNumber(519); + const compliantTakerYesMark = new BigNumber(1); + const compliantTakerEntityId = new BigNumber(2); + await yesTokenInstance.mint2.sendTransactionAsync( + compliantTakerAddress, + compliantTakerEntityId, + addressesCanControlTheirToken, + compliantTakerCountryCode, + [compliantTakerYesMark], + { from: owner }, + ); + // Create Valid/Invalid orders + const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; + takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); + compliantSignedOrder = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + }); + const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + compliantSignedOrder, + ); + const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + compliantSignedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( + compliantSignedOrderWithoutExchangeAddressData, + ); + + /* generate selectors for every exchange method + _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { + try { + const method = new Method(abiDefinition as MethodAbi); + console.log('\n', `// ${method.getDataItem().name}`); + console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); + console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); + } catch(e) { + _.noop(); + } + });*/ + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe.only('fillOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('should transfer the correct amounts when maker and taker are compliant', async () => { + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, + ); + const decoder = new LogDecoder(web3Wrapper); + const tx = await decoder.getTxWithDecodedLogsAsync(txHash); + console.log(JSON.stringify(tx, null, 4)); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); + it('should revert if the signed transaction is not intended for fillOrder', async () => { + // Create signed order without the fillOrder function selector + const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); + const selectorLengthInBytes = 4; + const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); + const badSelector = '0x00000000'; + const badSelectorBuf = ethUtil.toBuffer(badSelector); + const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); + const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + txDataBufWithBadSelectorHex, + compliantSignedFillOrderTx.signature, + )); + }); + it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { + // Create signed order with incorrect senderAddress + const notBalanceThresholdFilterAddress = zrxToken.address; + const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: notBalanceThresholdFilterAddress, + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadSenderAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + )); + }); + it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { + return expectTransactionFailedAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + nonCompliantAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadMakerAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + }); + + describe('batchFillOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('should transfer the correct amounts when maker and taker are compliant', async () => { + let order2 = _.cloneDeep(compliantSignedOrder); + order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; + const orders = [compliantSignedOrder, order2]; + const fillAmounts = [new BigNumber(4), new BigNumber(4)]; + const signatures = ["0xabcd", "0xabcd"]; + const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); + console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + exchangeCalldata, + compliantSignedFillOrderTx.signature, + ); + const decoder = new LogDecoder(web3Wrapper); + const tx = await decoder.getTxWithDecodedLogsAsync(txHash); + console.log(JSON.stringify(tx, null, 4)); + }); + }); +}); +// tslint:disable:max-file-line-count +// tslint:enable:no-unnecessary-type-assertion diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts deleted file mode 100644 index 196167264..000000000 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ /dev/null @@ -1,366 +0,0 @@ -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { RevertReason, SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { CompliantForwarderContract } from '../../generated-wrappers/compliant_forwarder'; -import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; - -import { artifacts } from '../../src/artifacts'; -import { - expectTransactionFailedAsync, - expectTransactionFailedWithoutReasonAsync, -} from '../utils/assertions'; -import { chaiSetup } from '../utils/chai_setup'; -import { constants } from '../utils/constants'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; -import { OrderFactory } from '../utils/order_factory'; -import { orderUtils } from '../utils/order_utils'; -import { TransactionFactory } from '../utils/transaction_factory'; -import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; -import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; - -import { MethodAbi, AbiDefinition } from 'ethereum-types'; -import { AbiEncoder } from '@0x/utils'; -import { Method } from '@0x/utils/lib/src/abi_encoder'; -import { LogDecoder } from '../utils/log_decoder'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -const DECIMALS_DEFAULT = 18; - -describe.only(ContractName.CompliantForwarder, () => { - let compliantMakerAddress: string; - let owner: string; - let compliantTakerAddress: string; - let feeRecipientAddress: string; - let nonCompliantAddress: string; - let defaultMakerAssetAddress: string; - let defaultTakerAssetAddress: string; - let zrxAssetData: string; - let zrxToken: DummyERC20TokenContract; - let exchangeInstance: ExchangeContract; - let exchangeWrapper: ExchangeWrapper; - - let orderFactory: OrderFactory; - let erc20Wrapper: ERC20Wrapper; - let erc20Balances: ERC20BalancesByOwner; - - let takerTransactionFactory: TransactionFactory; - let compliantSignedOrder: SignedOrder; - let compliantSignedFillOrderTx: SignedTransaction; - let noncompliantSignedFillOrderTx: SignedTransaction; - - const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); - const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); - - let compliantForwarderInstance: CompliantForwarderContract; - - before(async () => { - // Create accounts - await blockchainLifecycle.startAsync(); - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([ - owner, - compliantMakerAddress, - compliantTakerAddress, - feeRecipientAddress, - nonCompliantAddress, - ] = accounts); - // Create wrappers - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - // Deploy ERC20 tokens - const numDummyErc20ToDeploy = 3; - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - defaultMakerAssetAddress = erc20TokenA.address; - defaultTakerAssetAddress = erc20TokenB.address; - zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Deploy Yes Token - const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( - artifacts.YesComplianceToken, - provider, - txDefaults, - ); - // Create proxies - const erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - // Deploy Exchange congtract - exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); - // Register proxies - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { - from: owner, - }); - // Default order parameters - const defaultOrderParams = { - exchangeAddress: exchangeInstance.address, - makerAddress: compliantMakerAddress, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - makerAssetAmount, - takerAssetAmount, - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), - }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - // Deploy Compliant Forwarder - compliantForwarderInstance = await CompliantForwarderContract.deployFrom0xArtifactAsync( - artifacts.CompliantForwarder, - provider, - txDefaults, - exchangeInstance.address, - yesTokenInstance.address, - ); - /* - const compliantForwarderContract = new CompliantForwarderContract( - compliantForwarderInstance.abi, - compliantForwarderInstance.address, - provider, - ); - forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); - */ - // Initialize Yes Token - await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({ from: owner }); - const yesTokenName = 'YesToken'; - const yesTokenTicker = 'YEET'; - await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); - // Verify Maker / Taker - const addressesCanControlTheirToken = true; - const compliantMakerCountryCode = new BigNumber(519); - const compliantMakerYesMark = new BigNumber(1); - const compliantMakerEntityId = new BigNumber(2); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantMakerAddress, - compliantMakerEntityId, - addressesCanControlTheirToken, - compliantMakerCountryCode, - [compliantMakerYesMark], - { from: owner }, - ); - const compliantTakerCountryCode = new BigNumber(519); - const compliantTakerYesMark = new BigNumber(1); - const compliantTakerEntityId = new BigNumber(2); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantTakerAddress, - compliantTakerEntityId, - addressesCanControlTheirToken, - compliantTakerCountryCode, - [compliantTakerYesMark], - { from: owner }, - ); - // Create Valid/Invalid orders - const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; - takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); - compliantSignedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, - }); - const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - compliantSignedOrder, - ); - const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - compliantSignedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( - compliantSignedOrderWithoutExchangeAddressData, - ); - - /* generate selectors for every exchange method - _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { - try { - const method = new Method(abiDefinition as MethodAbi); - console.log('\n', `// ${method.getDataItem().name}`); - console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); - console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); - } catch(e) { - _.noop(); - } - });*/ - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe.only('fillOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { - const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature, - ); - const decoder = new LogDecoder(web3Wrapper); - const tx = await decoder.getTxWithDecodedLogsAsync(txHash); - console.log(JSON.stringify(tx, null, 4)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if the signed transaction is not intended for fillOrder', async () => { - // Create signed order without the fillOrder function selector - const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); - const selectorLengthInBytes = 4; - const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); - const badSelector = '0x00000000'; - const badSelectorBuf = ethUtil.toBuffer(badSelector); - const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); - const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - txDataBufWithBadSelectorHex, - compliantSignedFillOrderTx.signature, - )); - }); - it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { - // Create signed order with incorrect senderAddress - const notCompliantForwarderAddress = zrxToken.address; - const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: notCompliantForwarderAddress, - }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadSenderAddress, - ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, - ); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, - )); - }); - it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { - return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - nonCompliantAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadMakerAddress, - ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, - ); - // Call compliant forwarder - return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('batchFillOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { - let order2 = _.cloneDeep(compliantSignedOrder); - order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; - const orders = [compliantSignedOrder, order2]; - const fillAmounts = [new BigNumber(4), new BigNumber(4)]; - const signatures = ["0xabcd", "0xabcd"]; - const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); - console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); - - const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - exchangeCalldata, - compliantSignedFillOrderTx.signature, - ); - const decoder = new LogDecoder(web3Wrapper); - const tx = await decoder.getTxWithDecodedLogsAsync(txHash); - console.log(JSON.stringify(tx, null, 4)); - }); - }); -}); -// tslint:disable:max-file-line-count -// tslint:enable:no-unnecessary-type-assertion -- cgit v1.2.3 From 2be9b1ff082ff2753798cc0c218ab1829949661f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 15:47:45 -0800 Subject: Updated Balance Threshold Filter to use mixin pattern --- .../BalanceThresholdFilter.sol | 292 +------------------ .../MixinBalanceThresholdFilterCore.sol | 312 +++++++++++++++++++++ .../mixins/MBalanceThresholdFilterCore.sol | 44 +++ 3 files changed, 358 insertions(+), 290 deletions(-) create mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol create mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol index 93b63eb52..ce3e925fe 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol @@ -20,20 +20,11 @@ pragma solidity 0.4.24; pragma experimental ABIEncoderV2; import "../../protocol/Exchange/interfaces/IExchange.sol"; -import "../../utils/LibBytes/LibBytes.sol"; -import "../../utils/ExchangeSelectors/ExchangeSelectors.sol"; import "./interfaces/IThresholdAsset.sol"; +import "./MixinBalanceThresholdFilterCore.sol"; -contract BalanceThresholdFilter is ExchangeSelectors { - using LibBytes for bytes; - - IExchange internal EXCHANGE; - IThresholdAsset internal THRESHOLD_ASSET; - - event ValidatedAddresses ( - address[] addresses - ); +contract BalanceThresholdFilter is MixinBalanceThresholdFilterCore { constructor(address exchange, address thresholdAsset) public @@ -41,283 +32,4 @@ contract BalanceThresholdFilter is ExchangeSelectors { EXCHANGE = IExchange(exchange); THRESHOLD_ASSET = IThresholdAsset(thresholdAsset); } - - function executeTransaction( - uint256 salt, - address signerAddress, - bytes signedExchangeTransaction, - bytes signature - ) - external - { - // Addresses that are validated below. - address[] memory validatedAddresses; - - /** - * Do not add variables after this point. - * The assembly block may overwrite their values. - */ - - // Validate addresses - assembly { - /** - * Emulates the `calldataload` opcode on the embedded Exchange calldata, - * which is accessed through `signedExchangeTransaction`. - * @param offset - Offset into the Exchange calldata. - * @return value - Corresponding 32 byte value stored at `offset`. - */ - function exchangeCalldataload(offset) -> value { - // Pointer to exchange transaction - // 0x04 for calldata selector - // 0x40 to access `signedExchangeTransaction`, which is the third parameter - let exchangeTxPtr := calldataload(0x44) - - // Offset into Exchange calldata - // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. - // 0x04 for calldata selector - // 0x20 for length field of `signedExchangeTransaction` - let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeCalldataOffset) - } - - /** - * Convenience function that skips the 4 byte selector when loading - * from the embedded Exchange calldata. - * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) - * @return value - Corresponding 32 byte value stored at `offset` + 4. - */ - function loadExchangeData(offset) -> value { - value := exchangeCalldataload(add(offset, 0x4)) - } - - /** - * A running list is maintained of addresses to validate. - * This function records an address in this array. - * @param addressToValidate - Address to record for validation. - * @note - Variables are scoped but names are not, so we append - * underscores to names that share the global namespace. - */ - function recordAddressToValidate(addressToValidate) { - // Compute `addressesToValidate` memory offset - let addressesToValidate_ := mload(0x40) - let nAddressesToValidate_ := mload(addressesToValidate_) - - // Increment length - nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) - mstore(addressesToValidate_, nAddressesToValidate_) - - // Append address to validate - let offset := mul(nAddressesToValidate_, 0x20) - mstore(add(addressesToValidate_, offset), addressToValidate) - } - - /** - * Extracts the maker address from an order stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records it in - * the running list of addresses to validate. - * @param orderParamIndex - Index of the order in the Exchange function's signature - */ - function recordMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(orderParamIndex) - let makerAddress := loadExchangeData(orderPtr) - recordAddressToValidate(makerAddress) - } - - /** - * Extracts the maker addresses from an array of orders stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records them in - * the running list of addresses to validate. - * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature - */ - function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { - let orderArrayPtr := loadExchangeData(0x0) - let orderArrayLength := loadExchangeData(orderArrayPtr) - let orderArrayElementPtr := add(orderArrayPtr, 0x20) - let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) - for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { - let orderPtr := loadExchangeData(orderPtrOffset) - let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) - recordAddressToValidate(makerAddress) - } - } - - /** - * Records address of signer in the running list of addresses to validate. - * @note: We cannot access `signerAddress` directly from within the asm function, - * so it is loaded from the calldata. - */ - function recordSignerAddress() { - // Load the signer address from calldata - // 0x04 for selector - // 0x20 to access `signerAddress`, which is the second parameter. - let signerAddress_ := calldataload(0x24) - recordAddressToValidate(signerAddress_) - } - - /** - * Records addresses to be validated when Exchange transaction is a batch fill variant. - * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow - * Reference signature: (Order[],uint256[],bytes[]) - */ - function recordAddressesForBatchFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is a fill order variant. - * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow - * Reference signature: (Order,uint256,bytes) - */ - function recordAddressesForFillOrderVariant() { - // Record maker address from the order (param index 0) - // The signer is the taker for this order and must also be validated. - recordMakerAddressFromOrder(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is a market fill variant. - * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow - * Reference signature: (Order[],uint256,bytes[]) - */ - function recordAddressesForMarketFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is matchOrders. - * Reference signature: matchOrders(Order,Order) - */ - function recordAddressesForMatchOrders() { - // Record maker address from both orders (param indices 0 & 1). - // The signer is the taker and must also be validated. - recordMakerAddressFromOrder(0) - recordMakerAddressFromOrder(1) - recordSignerAddress() - } - - ///// Record Addresses to Validate ///// - - // Addresses needing validation depends on which Exchange function is being called. - // Step 1/2 Read the exchange function selector. - let exchangeFunctionSelector := and( - exchangeCalldataload(0x0), - 0xffffffff00000000000000000000000000000000000000000000000000000000 - ) - - // Step 2/2 Extract addresses to validate based on this selector. - // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors - switch exchangeFunctionSelector - case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders - case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow - case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder - case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow - case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder - case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders - case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow - case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders - case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow - case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders - case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder - case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders - case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo - default { - // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) - mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. - revert(0, 132) - } - - ///// Validate Recorded Addresses ///// - - // Load from memory the addresses to validate - let addressesToValidate := mload(0x40) - let addressesToValidateLength := mload(addressesToValidate) - let addressesToValidateElementPtr := add(addressesToValidate, 0x20) - let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) - - // Set free memory pointer to after `addressesToValidate` array. - // This is to avoid corruption when making calls in the loop below. - let freeMemPtr := addressesToValidateElementEndPtr - mstore(0x40, freeMemPtr) - - // Validate addresses - let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) - for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { - // Construct calldata for `THRESHOLD_ASSET.balanceOf` - mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(4, freeMemPtr), mload(addressToValidate)) - - // call `THRESHOLD_ASSET.balanceOf` - let success := call( - gas, // forward all gas - thresholdAssetAddress, // call address of asset proxy - 0, // don't send any ETH - freeMemPtr, // pointer to start of input - 0x24, // length of input (one padded address) - freeMemPtr, // write output to next free memory offset - 0x20 // reserve space for return balance (0x20 bytes) - ) - if eq(success, 0) { - // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) - mstore(0x60, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. - revert(0, 100) - } - - // Revert if balance not held - let addressBalance := mload(freeMemPtr) - if eq(addressBalance, 0) { - // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) - mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. - revert(0, 132) - } - } - - // Record validated addresses - validatedAddresses := addressesToValidate - } - - ///// If we hit this point then all addresses are valid ///// - emit ValidatedAddresses(validatedAddresses); - - // All addresses are valid. Execute fillOrder. - EXCHANGE.executeTransaction( - salt, - signerAddress, - signedExchangeTransaction, - signature - ); - } } \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol new file mode 100644 index 000000000..dbb352f90 --- /dev/null +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -0,0 +1,312 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "./mixins/MBalanceThresholdFilterCore.sol"; + + +contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { + + function executeTransaction( + uint256 salt, + address signerAddress, + bytes signedExchangeTransaction, + bytes signature + ) + external + { + // Validate addresses. + validateBalanceThresholdsOrRevert(); + + // All addresses are valid. Execute fillOrder. + EXCHANGE.executeTransaction( + salt, + signerAddress, + signedExchangeTransaction, + signature + ); + } + + function validateBalanceThresholdsOrRevert() + internal + { + // Addresses that are validated below. + address[] memory validatedAddresses; + + /** + * Do not add variables after this point. + * The assembly block may overwrite their values. + */ + + // Validate addresses + assembly { + /** + * Emulates the `calldataload` opcode on the embedded Exchange calldata, + * which is accessed through `signedExchangeTransaction`. + * @param offset - Offset into the Exchange calldata. + * @return value - Corresponding 32 byte value stored at `offset`. + */ + function exchangeCalldataload(offset) -> value { + // Pointer to exchange transaction + // 0x04 for calldata selector + // 0x40 to access `signedExchangeTransaction`, which is the third parameter + let exchangeTxPtr := calldataload(0x44) + + // Offset into Exchange calldata + // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. + // 0x04 for calldata selector + // 0x20 for length field of `signedExchangeTransaction` + let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) + value := calldataload(exchangeCalldataOffset) + } + + /** + * Convenience function that skips the 4 byte selector when loading + * from the embedded Exchange calldata. + * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) + * @return value - Corresponding 32 byte value stored at `offset` + 4. + */ + function loadExchangeData(offset) -> value { + value := exchangeCalldataload(add(offset, 0x4)) + } + + /** + * A running list is maintained of addresses to validate. + * This function records an address in this array. + * @param addressToValidate - Address to record for validation. + * @note - Variables are scoped but names are not, so we append + * underscores to names that share the global namespace. + */ + function recordAddressToValidate(addressToValidate) { + // Compute `addressesToValidate` memory offset + let addressesToValidate_ := mload(0x40) + let nAddressesToValidate_ := mload(addressesToValidate_) + + // Increment length + nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) + mstore(addressesToValidate_, nAddressesToValidate_) + + // Append address to validate + let offset := mul(nAddressesToValidate_, 0x20) + mstore(add(addressesToValidate_, offset), addressToValidate) + } + + /** + * Extracts the maker address from an order stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records it in + * the running list of addresses to validate. + * @param orderParamIndex - Index of the order in the Exchange function's signature + */ + function recordMakerAddressFromOrder(orderParamIndex) { + let orderPtr := loadExchangeData(orderParamIndex) + let makerAddress := loadExchangeData(orderPtr) + recordAddressToValidate(makerAddress) + } + + /** + * Extracts the maker addresses from an array of orders stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records them in + * the running list of addresses to validate. + * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature + */ + function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { + let orderArrayPtr := loadExchangeData(orderArrayParamIndex) + let orderArrayLength := loadExchangeData(orderArrayPtr) + let orderArrayElementPtr := add(orderArrayPtr, 0x20) + let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) + for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { + let orderPtr := loadExchangeData(orderPtrOffset) + let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) + recordAddressToValidate(makerAddress) + } + } + + /** + * Records address of signer in the running list of addresses to validate. + * @note: We cannot access `signerAddress` directly from within the asm function, + * so it is loaded from the calldata. + */ + function recordSignerAddress() { + // Load the signer address from calldata + // 0x04 for selector + // 0x20 to access `signerAddress`, which is the second parameter. + let signerAddress_ := calldataload(0x24) + recordAddressToValidate(signerAddress_) + } + + /** + * Records addresses to be validated when Exchange transaction is a batch fill variant. + * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow + * Reference signature: (Order[],uint256[],bytes[]) + */ + function recordAddressesForBatchFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a fill order variant. + * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow + * Reference signature: (Order,uint256,bytes) + */ + function recordAddressesForFillOrderVariant() { + // Record maker address from the order (param index 0) + // The signer is the taker for this order and must also be validated. + recordMakerAddressFromOrder(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a market fill variant. + * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow + * Reference signature: (Order[],uint256,bytes[]) + */ + function recordAddressesForMarketFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is matchOrders. + * Reference signature: matchOrders(Order,Order) + */ + function recordAddressesForMatchOrders() { + // Record maker address from both orders (param indices 0 & 1). + // The signer is the taker and must also be validated. + recordMakerAddressFromOrder(0) + recordMakerAddressFromOrder(1) + recordSignerAddress() + } + + ///// Record Addresses to Validate ///// + + // Addresses needing validation depends on which Exchange function is being called. + // Step 1/2 Read the exchange function selector. + let exchangeFunctionSelector := and( + exchangeCalldataload(0x0), + 0xffffffff00000000000000000000000000000000000000000000000000000000 + ) + + // Step 2/2 Extract addresses to validate based on this selector. + // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors + switch exchangeFunctionSelector + case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders + case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow + case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder + case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow + case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder + case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders + case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow + case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders + case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow + case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders + case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder + case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders + case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo + default { + // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) + mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. + revert(0, 132) + } + + ///// Validate Recorded Addresses ///// + + // Load from memory the addresses to validate + let addressesToValidate := mload(0x40) + let addressesToValidateLength := mload(addressesToValidate) + let addressesToValidateElementPtr := add(addressesToValidate, 0x20) + let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) + + // Set free memory pointer to after `addressesToValidate` array. + // This is to avoid corruption when making calls in the loop below. + let freeMemPtr := addressesToValidateElementEndPtr + mstore(0x40, freeMemPtr) + + // Validate addresses + let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) + for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { + // Construct calldata for `THRESHOLD_ASSET.balanceOf` + mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) + mstore(add(4, freeMemPtr), mload(addressToValidate)) + + // call `THRESHOLD_ASSET.balanceOf` + let success := call( + gas, // forward all gas + thresholdAssetAddress, // call address of asset proxy + 0, // don't send any ETH + freeMemPtr, // pointer to start of input + 0x24, // length of input (one padded address) + freeMemPtr, // write output to next free memory offset + 0x20 // reserve space for return balance (0x20 bytes) + ) + if eq(success, 0) { + // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) + mstore(0x60, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. + revert(0, 100) + } + + // Revert if balance not held + let addressBalance := mload(freeMemPtr) + if eq(addressBalance, 0) { + // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) + mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. + revert(0, 132) + } + } + + // Record validated addresses + validatedAddresses := addressesToValidate + } + + ///// If we hit this point then all addresses are valid ///// + emit ValidatedAddresses(validatedAddresses); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol new file mode 100644 index 000000000..1de415f27 --- /dev/null +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -0,0 +1,44 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../../protocol/Exchange/interfaces/IExchange.sol"; +import "../interfaces/IThresholdAsset.sol"; + + +contract MBalanceThresholdFilterCore { + + IExchange internal EXCHANGE; + IThresholdAsset internal THRESHOLD_ASSET; + + event ValidatedAddresses ( + address[] addresses + ); + + function executeTransaction( + uint256 salt, + address signerAddress, + bytes signedExchangeTransaction, + bytes signature + ) + external; + + function validateBalanceThresholdsOrRevert() internal; +} \ No newline at end of file -- cgit v1.2.3 From 58a382d9b69eedc5f618c8d29d130151ba0ed740 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 16:07:45 -0800 Subject: Function Documentation --- .../MixinBalanceThresholdFilterCore.sol | 126 +++++++++++---------- .../mixins/MBalanceThresholdFilterCore.sol | 29 +++++ 2 files changed, 96 insertions(+), 59 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index dbb352f90..fe4a50a29 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -24,6 +24,28 @@ import "./mixins/MBalanceThresholdFilterCore.sol"; contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { + /// @dev Executes an Exchange transaction iff the maker and taker meet + /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR + /// the exchange function is a cancellation. + /// Supported Exchange functions: + /// - batchFillOrdersNoThrow + /// - batchFillOrKillOrders + /// - fillOrder + /// - fillOrderNoThrow + /// - fillOrKillOrder + /// - marketBuyOrders + /// - marketBuyOrdersNoThrow + /// - marketSellOrders + /// - marketSellOrdersNoThrow + /// - matchOrders + /// - cancelOrder + /// - batchCancelOrders + /// - cancelOrdersUpTo + /// Trying to call any other exchange function will throw. + /// @param salt Arbitrary number to ensure uniqueness of transaction hash. + /// @param signerAddress Address of transaction signer. + /// @param signedExchangeTransaction AbiV2 encoded calldata. + /// @param signature Proof of signer transaction by signer. function executeTransaction( uint256 salt, address signerAddress, @@ -44,25 +66,29 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { ); } + /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD` + /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold + /// then this function will revert. Which addresses are validated depends on + /// which Exchange function is to be called (defined by `signedExchangeTransaction` above). + /// No parameters are taken as this function reads arguments directly from calldata, to save gas. + /// If all addresses are valid then this function emits a ValidatedAddresses event, listing all + /// of the addresses whose balance thresholds it checked. function validateBalanceThresholdsOrRevert() internal { // Addresses that are validated below. address[] memory validatedAddresses; - /** - * Do not add variables after this point. - * The assembly block may overwrite their values. - */ + + ///// Do not add variables after this point. ///// + ///// The assembly block may overwrite their values. ///// // Validate addresses assembly { - /** - * Emulates the `calldataload` opcode on the embedded Exchange calldata, - * which is accessed through `signedExchangeTransaction`. - * @param offset - Offset into the Exchange calldata. - * @return value - Corresponding 32 byte value stored at `offset`. - */ + /// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata, + /// which is accessed through `signedExchangeTransaction`. + /// @param offset - Offset into the Exchange calldata. + /// @return value - Corresponding 32 byte value stored at `offset`. function exchangeCalldataload(offset) -> value { // Pointer to exchange transaction // 0x04 for calldata selector @@ -77,23 +103,19 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { value := calldataload(exchangeCalldataOffset) } - /** - * Convenience function that skips the 4 byte selector when loading - * from the embedded Exchange calldata. - * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) - * @return value - Corresponding 32 byte value stored at `offset` + 4. - */ + /// @dev Convenience function that skips the 4 byte selector when loading + /// from the embedded Exchange calldata. + /// @param offset - Offset into the Exchange calldata (minus the 4 byte selector) + /// @return value - Corresponding 32 byte value stored at `offset` + 4. function loadExchangeData(offset) -> value { value := exchangeCalldataload(add(offset, 0x4)) } - /** - * A running list is maintained of addresses to validate. - * This function records an address in this array. - * @param addressToValidate - Address to record for validation. - * @note - Variables are scoped but names are not, so we append - * underscores to names that share the global namespace. - */ + /// @dev A running list is maintained of addresses to validate. + /// This function records an address in this array. + /// @param addressToValidate - Address to record for validation. + /// @note - Variables are scoped but names are not, so we append + /// underscores to names that share the global namespace. function recordAddressToValidate(addressToValidate) { // Compute `addressesToValidate` memory offset let addressesToValidate_ := mload(0x40) @@ -108,24 +130,20 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { mstore(add(addressesToValidate_, offset), addressToValidate) } - /** - * Extracts the maker address from an order stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records it in - * the running list of addresses to validate. - * @param orderParamIndex - Index of the order in the Exchange function's signature - */ + /// @dev Extracts the maker address from an order stored in the Exchange calldata + /// (which is embedded in `signedExchangeTransaction`), and records it in + /// the running list of addresses to validate. + /// @param orderParamIndex - Index of the order in the Exchange function's signature function recordMakerAddressFromOrder(orderParamIndex) { let orderPtr := loadExchangeData(orderParamIndex) let makerAddress := loadExchangeData(orderPtr) recordAddressToValidate(makerAddress) } - /** - * Extracts the maker addresses from an array of orders stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records them in - * the running list of addresses to validate. - * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature - */ + /// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata + /// (which is embedded in `signedExchangeTransaction`), and records them in + /// the running list of addresses to validate. + /// @param orderArrayParamIndex - Index of the order array in the Exchange function's signature function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { let orderArrayPtr := loadExchangeData(orderArrayParamIndex) let orderArrayLength := loadExchangeData(orderArrayPtr) @@ -138,11 +156,9 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { } } - /** - * Records address of signer in the running list of addresses to validate. - * @note: We cannot access `signerAddress` directly from within the asm function, - * so it is loaded from the calldata. - */ + /// @dev Records address of signer in the running list of addresses to validate. + /// @note: We cannot access `signerAddress` directly from within the asm function, + /// so it is loaded from the calldata. function recordSignerAddress() { // Load the signer address from calldata // 0x04 for selector @@ -151,11 +167,9 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { recordAddressToValidate(signerAddress_) } - /** - * Records addresses to be validated when Exchange transaction is a batch fill variant. - * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow - * Reference signature: (Order[],uint256[],bytes[]) - */ + /// @dev Records addresses to be validated when Exchange transaction is a batch fill variant. + /// This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow + /// Reference signature: (Order[],uint256[],bytes[]) function recordAddressesForBatchFillVariant() { // Record maker addresses from order array (parameter index 0) // The signer is the taker for these orders and must also be validated. @@ -163,11 +177,9 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { recordSignerAddress() } - /** - * Records addresses to be validated when Exchange transaction is a fill order variant. - * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow - * Reference signature: (Order,uint256,bytes) - */ + /// @dev Records addresses to be validated when Exchange transaction is a fill order variant. + /// This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow + /// Reference signature: (Order,uint256,bytes) function recordAddressesForFillOrderVariant() { // Record maker address from the order (param index 0) // The signer is the taker for this order and must also be validated. @@ -175,11 +187,9 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { recordSignerAddress() } - /** - * Records addresses to be validated when Exchange transaction is a market fill variant. - * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow - * Reference signature: (Order[],uint256,bytes[]) - */ + /// @dev Records addresses to be validated when Exchange transaction is a market fill variant. + /// This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow + /// Reference signature: (Order[],uint256,bytes[]) function recordAddressesForMarketFillVariant() { // Record maker addresses from order array (parameter index 0) // The signer is the taker for these orders and must also be validated. @@ -187,10 +197,8 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { recordSignerAddress() } - /** - * Records addresses to be validated when Exchange transaction is matchOrders. - * Reference signature: matchOrders(Order,Order) - */ + /// @dev Records addresses to be validated when Exchange transaction is matchOrders. + /// Reference signature: matchOrders(Order,Order) function recordAddressesForMatchOrders() { // Record maker address from both orders (param indices 0 & 1). // The signer is the taker and must also be validated. diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol index 1de415f27..ecebaa31b 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -32,6 +32,28 @@ contract MBalanceThresholdFilterCore { address[] addresses ); + /// @dev Executes an Exchange transaction iff the maker and taker meet + /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR + /// the exchange function is a cancellation. + /// Supported Exchange functions: + /// - batchFillOrdersNoThrow + /// - batchFillOrKillOrders + /// - fillOrder + /// - fillOrderNoThrow + /// - fillOrKillOrder + /// - marketBuyOrders + /// - marketBuyOrdersNoThrow + /// - marketSellOrders + /// - marketSellOrdersNoThrow + /// - matchOrders + /// - cancelOrder + /// - batchCancelOrders + /// - cancelOrdersUpTo + /// Trying to call any other exchange function will throw. + /// @param salt Arbitrary number to ensure uniqueness of transaction hash. + /// @param signerAddress Address of transaction signer. + /// @param signedExchangeTransaction AbiV2 encoded calldata. + /// @param signature Proof of signer transaction by signer. function executeTransaction( uint256 salt, address signerAddress, @@ -40,5 +62,12 @@ contract MBalanceThresholdFilterCore { ) external; + /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD` + /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold + /// then this function will revert. Which addresses are validated depends on + /// which Exchange function is to be called (defined by `signedExchangeTransaction` above). + /// No parameters are taken as this function reads arguments directly from calldata, to save gas. + /// If all addresses are valid then this function emits a ValidatedAddresses event, listing all + /// of the addresses whose balance thresholds it checked. function validateBalanceThresholdsOrRevert() internal; } \ No newline at end of file -- cgit v1.2.3 From 14c97b3ec368904d2e072f4603ab41ea8458aebb Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 16:15:35 -0800 Subject: Include threshold balance in constructor of BalanceThresholdFilter contract --- .../BalanceThresholdFilter/BalanceThresholdFilter.sol | 11 ++++++++++- .../MixinBalanceThresholdFilterCore.sol | 3 ++- .../mixins/MBalanceThresholdFilterCore.sol | 7 +++++++ .../contracts/test/extensions/balance_threshold_filter.ts | 2 ++ 4 files changed, 21 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol index ce3e925fe..bf4a94509 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol @@ -26,10 +26,19 @@ import "./MixinBalanceThresholdFilterCore.sol"; contract BalanceThresholdFilter is MixinBalanceThresholdFilterCore { - constructor(address exchange, address thresholdAsset) + /// @dev Constructs BalanceThresholdFilter. + /// @param exchange Address of 0x exchange. + /// @param thresholdAsset The asset that must be held by makers/takers. + /// @param thresholdBalance The minimum balance of `thresholdAsset` that must be held by makers/takers. + constructor( + address exchange, + address thresholdAsset, + uint256 thresholdBalance + ) public { EXCHANGE = IExchange(exchange); THRESHOLD_ASSET = IThresholdAsset(thresholdAsset); + THRESHOLD_BALANCE = thresholdBalance; } } \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index fe4a50a29..51b3b9736 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -263,6 +263,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // Validate addresses let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) + let thresholdBalance := sload(THRESHOLD_BALANCE_slot) for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { // Construct calldata for `THRESHOLD_ASSET.balanceOf` mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) @@ -294,7 +295,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // Revert if balance not held let addressBalance := mload(freeMemPtr) - if eq(addressBalance, 0) { + if lt(addressBalance, thresholdBalance) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol index ecebaa31b..046caecdd 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -25,9 +25,16 @@ import "../interfaces/IThresholdAsset.sol"; contract MBalanceThresholdFilterCore { + // Points to 0x exchange contract IExchange internal EXCHANGE; + + // The asset that must be held by makers/takers IThresholdAsset internal THRESHOLD_ASSET; + // The minimum balance of `THRESHOLD_ASSET` that must be held by makers/takers + uint256 internal THRESHOLD_BALANCE; + + // Addresses that hold at least `THRESHOLD_BALANCE` of `THRESHOLD_ASSET` event ValidatedAddresses ( address[] addresses ); diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 50fd79439..db4fea77a 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -126,12 +126,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); // Deploy Compliant Forwarder + const erc721BalanceThreshold = new BigNumber(1); compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( artifacts.BalanceThresholdFilter, provider, txDefaults, exchangeInstance.address, yesTokenInstance.address, + erc721BalanceThreshold ); /* const compliantForwarderContract = new BalanceThresholdFilterContract( -- cgit v1.2.3 From 18f028fb0833b5a5b983a213343caa7460262002 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 16:47:45 -0800 Subject: Removed unnecessary note --- .../BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol | 2 -- 1 file changed, 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 51b3b9736..f730c5a11 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -114,8 +114,6 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { /// @dev A running list is maintained of addresses to validate. /// This function records an address in this array. /// @param addressToValidate - Address to record for validation. - /// @note - Variables are scoped but names are not, so we append - /// underscores to names that share the global namespace. function recordAddressToValidate(addressToValidate) { // Compute `addressesToValidate` memory offset let addressesToValidate_ := mload(0x40) -- cgit v1.2.3 From 7e7880aea05b70e6d0515586a005fafe34eba850 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 17:28:05 -0800 Subject: Conformed to hex format for constructing offsets --- .../BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index f730c5a11..9bed7ba47 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -79,7 +79,6 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // Addresses that are validated below. address[] memory validatedAddresses; - ///// Do not add variables after this point. ///// ///// The assembly block may overwrite their values. ///// @@ -265,7 +264,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { // Construct calldata for `THRESHOLD_ASSET.balanceOf` mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(4, freeMemPtr), mload(addressToValidate)) + mstore(add(freeMemPtr, 0x04), mload(addressToValidate)) // call `THRESHOLD_ASSET.balanceOf` let success := call( -- cgit v1.2.3 From d882133e444d034c62b5f88c988d4cdd3fb2dbf4 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 6 Dec 2018 18:00:53 -0800 Subject: Wrappers for balance threshold filter + updated some tests to use the wrapper --- .../test/extensions/balance_threshold_filter.ts | 249 +++++++++++++++------ .../test/utils/balance_threshold_wrapper.ts | 243 ++++++++++++++++++++ .../test/utils/compliant_forwarder_wrapper.ts | 0 3 files changed, 429 insertions(+), 63 deletions(-) create mode 100644 packages/contracts/test/utils/balance_threshold_wrapper.ts delete mode 100644 packages/contracts/test/utils/compliant_forwarder_wrapper.ts (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index db4fea77a..9b48cdf93 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -6,6 +6,7 @@ import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; +import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; @@ -24,6 +25,7 @@ import { ExchangeWrapper } from '../utils/exchange_wrapper'; import { OrderFactory } from '../utils/order_factory'; import { orderUtils } from '../utils/order_utils'; import { TransactionFactory } from '../utils/transaction_factory'; +import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; @@ -37,6 +39,10 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const DECIMALS_DEFAULT = 18; +interface ValidatedAddressesLog { + args: {addresses: string[]} +} + describe.only(ContractName.BalanceThresholdFilter, () => { let compliantMakerAddress: string; let owner: string; @@ -53,18 +59,35 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let orderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; + let balanceThresholdWrapper: BalanceThresholdWrapper; let takerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; let noncompliantSignedFillOrderTx: SignedTransaction; + let logDecoder: LogDecoder; + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); let compliantForwarderInstance: BalanceThresholdFilterContract; + const assertValidatedAddressesLog = async (txReceipt: TransactionReceiptWithDecodedLogs, expectedValidatedAddresses: string[]) => { + expect(txReceipt.logs.length).to.be.gte(1); + const validatedAddressesLog = (txReceipt.logs[0] as any) as ValidatedAddressesLog; + const validatedAddresses = validatedAddressesLog.args.addresses; + // @HACK-hysz: Nested addresses are not translated to lower-case but this will change once + // the new ABI Encoder/Decoder is used by the contract templates. + let validatedAddressesNormalized: string[] = []; + _.each(validatedAddresses, (address) => { + const normalizedAddress = _.toLower(address); + validatedAddressesNormalized.push(normalizedAddress); + }); + expect(validatedAddressesNormalized).to.be.deep.equal(expectedValidatedAddresses); + }; + before(async () => { // Create accounts await blockchainLifecycle.startAsync(); @@ -190,17 +213,30 @@ describe.only(ContractName.BalanceThresholdFilter, () => { compliantSignedOrderWithoutExchangeAddressData, ); - /* generate selectors for every exchange method + /* _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { try { const method = new Method(abiDefinition as MethodAbi); - console.log('\n', `// ${method.getDataItem().name}`); - console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); - console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); + console.log(method.getSignature()); + if (!method.getSignature().startsWith('matchOrders')) { + return; + } + console.log(`FOUND IT`); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + compliantSignedOrder, + ); + const args = [signedOrderWithoutExchangeAddress, signedOrderWithoutExchangeAddress, compliantSignedOrder.signature, compliantSignedOrder.signature]; + console.log(method.encode(args, {annotate: true})); + //console.log('\n', `// ${method.getDataItem().name}`); + //console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); + //console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); } catch(e) { - _.noop(); + console.log(`encoding failed: ${e}`); } - });*/ + }); + throw new Error(`w`);*/ + logDecoder = new LogDecoder(web3Wrapper); + balanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -208,21 +244,101 @@ describe.only(ContractName.BalanceThresholdFilter, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe.only('fillOrder', () => { + + describe('General Sanity Checks', () => { + it('should revert if the signed transaction is not intended for supported', async () => { + // Create signed order without the fillOrder function selector + const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); + const selectorLengthInBytes = 4; + const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); + const badSelector = '0x00000000'; + const badSelectorBuf = ethUtil.toBuffer(badSelector); + const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); + const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + txDataBufWithBadSelectorHex, + compliantSignedFillOrderTx.signature, + )); + }); + it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { + // Create signed order with incorrect senderAddress + const notBalanceThresholdFilterAddress = zrxToken.address; + const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: notBalanceThresholdFilterAddress, + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadSenderAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + )); + }); + // @TODO - greater than 1 balance + }); + + + + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); it('should transfer the correct amounts when maker and taker are compliant', async () => { + let order2 = _.cloneDeep(compliantSignedOrder); + order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; + const orders = [compliantSignedOrder, order2]; + const fillAmounts = [new BigNumber(4), new BigNumber(4)]; + const signatures = ["0xabcd", "0xabcd"]; + const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); + console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, - compliantSignedFillOrderTx.data, + exchangeCalldata, compliantSignedFillOrderTx.signature, ); const decoder = new LogDecoder(web3Wrapper); const tx = await decoder.getTxWithDecodedLogsAsync(txHash); console.log(JSON.stringify(tx, null, 4)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + }); + }); + + describe('batchFillOrdersNoThrow', () => { + }); + + describe('batchFillOrKillOrders', () => { + }); + + describe('batchFillOrKillOrders', () => { + }); + + describe.only('fillOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const txReceipt = await balanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantSignedFillOrderTx.signerAddress, {takerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) @@ -255,31 +371,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), ); }); - it('should revert if the signed transaction is not intended for fillOrder', async () => { - // Create signed order without the fillOrder function selector - const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); - const selectorLengthInBytes = 4; - const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); - const badSelector = '0x00000000'; - const badSelectorBuf = ethUtil.toBuffer(badSelector); - const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); - const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - txDataBufWithBadSelectorHex, - compliantSignedFillOrderTx.signature, - )); - }); - it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { - // Create signed order with incorrect senderAddress - const notBalanceThresholdFilterAddress = zrxToken.address; - const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: notBalanceThresholdFilterAddress, + it('should revert if maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress }); const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadSenderAddress, + signedOrderWithBadMakerAddress, ); const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( signedOrderWithoutExchangeAddress, @@ -290,14 +389,17 @@ describe.only(ContractName.BalanceThresholdFilter, () => { signedOrderWithoutExchangeAddressData, ); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, - )); + return expectTransactionFailedAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); }); - it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { + it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, @@ -308,7 +410,16 @@ describe.only(ContractName.BalanceThresholdFilter, () => { RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); - it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + }); + + describe('fillOrderNoThrow', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + + }); + it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, @@ -336,32 +447,44 @@ describe.only(ContractName.BalanceThresholdFilter, () => { RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + nonCompliantAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - describe('batchFillOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { - let order2 = _.cloneDeep(compliantSignedOrder); - order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; - const orders = [compliantSignedOrder, order2]; - const fillAmounts = [new BigNumber(4), new BigNumber(4)]; - const signatures = ["0xabcd", "0xabcd"]; - const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); - console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + describe('fillOrKillOrder', () => { + }); - const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - exchangeCalldata, - compliantSignedFillOrderTx.signature, - ); - const decoder = new LogDecoder(web3Wrapper); - const tx = await decoder.getTxWithDecodedLogsAsync(txHash); - console.log(JSON.stringify(tx, null, 4)); - }); + describe('marketBuyOrders', () => { + }); + + describe('marketBuyOrdersNoThrow', () => { + }); + + describe('marketSellOrders', () => { + }); + + describe('marketSellOrdersNoThrow', () => { + }); + + describe('matchOrders', () => { + }); + + describe('cancelOrder', () => { + }); + + describe('batchCancelOrders', () => { + }); + + describe('cancelOrdersUpTo', () => { }); }); // tslint:disable:max-file-line-count diff --git a/packages/contracts/test/utils/balance_threshold_wrapper.ts b/packages/contracts/test/utils/balance_threshold_wrapper.ts new file mode 100644 index 000000000..ac7bdd593 --- /dev/null +++ b/packages/contracts/test/utils/balance_threshold_wrapper.ts @@ -0,0 +1,243 @@ +import { SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { ExchangeContract } from '../../generated-wrappers/exchange'; +import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; + +import { formatters } from './formatters'; +import { LogDecoder } from './log_decoder'; +import { orderUtils } from './order_utils'; +import { TransactionFactory } from '../utils/transaction_factory'; +import { OrderInfo } from './types'; + +export class BalanceThresholdWrapper { + private readonly _balanceThresholdFilter: BalanceThresholdFilterContract; + private readonly _signerTransactionFactory: TransactionFactory; + private readonly _exchange: ExchangeContract; + private readonly _web3Wrapper: Web3Wrapper; + private readonly _logDecoder: LogDecoder; + constructor(balanceThresholdFilter: BalanceThresholdFilterContract, exchangeContract: ExchangeContract, signerTransactionFactory: TransactionFactory, provider: Provider) { + this._balanceThresholdFilter = balanceThresholdFilter; + this._exchange = exchangeContract; + this._signerTransactionFactory = signerTransactionFactory; + this._web3Wrapper = new Web3Wrapper(provider); + this._logDecoder = new LogDecoder(this._web3Wrapper); + } + public async fillOrderAsync( + signedOrder: SignedOrder, + from: string, + opts: { takerAssetFillAmount?: BigNumber } = {}, + ): Promise { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const data = this._exchange.fillOrder.getABIEncodedTransactionData( + params.order, + params.takerAssetFillAmount, + params.signature, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async fillOrKillOrderAsync( + signedOrder: SignedOrder, + from: string, + opts: { takerAssetFillAmount?: BigNumber } = {}, + ): Promise { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const data = this._exchange.fillOrKillOrder.getABIEncodedTransactionData( + params.order, + params.takerAssetFillAmount, + params.signature, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async fillOrderNoThrowAsync( + signedOrder: SignedOrder, + from: string, + opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {}, + ): Promise { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const data = this._exchange.fillOrderNoThrow.getABIEncodedTransactionData( + params.order, + params.takerAssetFillAmount, + params.signature, + ); + const txReceipt = this._executeTransaction(data, from, opts.gas); + return txReceipt; + } + public async batchFillOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmounts?: BigNumber[] } = {}, + ): Promise { + const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); + const data = this._exchange.batchFillOrders.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmounts, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async batchFillOrKillOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmounts?: BigNumber[] } = {}, + ): Promise { + const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); + const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmounts, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async batchFillOrdersNoThrowAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {}, + ): Promise { + const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); + const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmounts, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from, opts.gas); + return txReceipt; + } + public async marketSellOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmount: BigNumber }, + ): Promise { + const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); + const data = this._exchange.marketSellOrders.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmount, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async marketSellOrdersNoThrowAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmount: BigNumber; gas?: number }, + ): Promise { + const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); + const data = this._exchange.marketSellOrdersNoThrow.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmount, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from, opts.gas); + return txReceipt; + } + public async marketBuyOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { makerAssetFillAmount: BigNumber }, + ): Promise { + const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); + const data = this._exchange.marketBuyOrders.getABIEncodedTransactionData( + params.orders, + params.makerAssetFillAmount, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async marketBuyOrdersNoThrowAsync( + orders: SignedOrder[], + from: string, + opts: { makerAssetFillAmount: BigNumber; gas?: number }, + ): Promise { + const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); + const data = this._exchange.marketBuyOrdersNoThrow.getABIEncodedTransactionData( + params.orders, + params.makerAssetFillAmount, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from, opts.gas); + return txReceipt; + } + public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise { + const params = orderUtils.createCancel(signedOrder); + const data = this._exchange.cancelOrder.getABIEncodedTransactionData(params.order); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async batchCancelOrdersAsync( + orders: SignedOrder[], + from: string, + ): Promise { + const params = formatters.createBatchCancel(orders); + const data = this._exchange.batchCancelOrders.getABIEncodedTransactionData(params.orders); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise { + const data = this._exchange.cancelOrdersUpTo.getABIEncodedTransactionData(salt); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise { + const filledAmount = await this._exchange.filled.callAsync(orderHashHex); + return filledAmount; + } + public async isCancelledAsync(orderHashHex: string): Promise { + const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex); + return isCancelled; + } + public async getOrderEpochAsync(makerAddress: string, senderAddress: string): Promise { + const orderEpoch = await this._exchange.orderEpoch.callAsync(makerAddress, senderAddress); + return orderEpoch; + } + public async getOrderInfoAsync(signedOrder: SignedOrder): Promise { + const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo; + return orderInfo; + } + public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise { + const ordersInfo = (await this._exchange.getOrdersInfo.callAsync(signedOrders)) as OrderInfo[]; + return ordersInfo; + } + public async matchOrdersAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + from: string, + ): Promise { + const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight); + const data = await this._exchange.matchOrders.getABIEncodedTransactionData( + params.left, + params.right, + params.leftSignature, + params.rightSignature + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public getBalanceThresholdAddress(): string { + return this._balanceThresholdFilter.address; + } + public getExchangeAddress(): string { + return this._exchange.address; + } + private async _executeTransaction(abiEncodedExchangeTxData: string, from: string, gas?: number): Promise { + const signedExchangeTx = this._signerTransactionFactory.newSignedTransaction(abiEncodedExchangeTxData); + const txOpts = _.isUndefined(gas) ? {from} : {from, gas}; + const txHash = await this._balanceThresholdFilter.executeTransaction.sendTransactionAsync( + signedExchangeTx.salt, + signedExchangeTx.signerAddress, + signedExchangeTx.data, + signedExchangeTx.signature, + txOpts, + ); + const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return txReceipt; + } +} diff --git a/packages/contracts/test/utils/compliant_forwarder_wrapper.ts b/packages/contracts/test/utils/compliant_forwarder_wrapper.ts deleted file mode 100644 index e69de29bb..000000000 -- cgit v1.2.3 From 7af2c751dceb52adc84ded417b260666e6a96c29 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 14:21:29 -0800 Subject: fillOrder tests with new wrapper --- .../test/extensions/balance_threshold_filter.ts | 37 ++++++++-------------- 1 file changed, 13 insertions(+), 24 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 9b48cdf93..cfa1dcbdf 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -60,11 +60,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; let balanceThresholdWrapper: BalanceThresholdWrapper; + let nonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; let takerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; - let noncompliantSignedFillOrderTx: SignedTransaction; let logDecoder: LogDecoder; @@ -237,6 +237,8 @@ describe.only(ContractName.BalanceThresholdFilter, () => { throw new Error(`w`);*/ logDecoder = new LogDecoder(web3Wrapper); balanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); + const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; + nonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -334,7 +336,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await balanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantSignedFillOrderTx.signerAddress, {takerAssetFillAmount}); + const txReceipt = await balanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -377,35 +379,22 @@ describe.only(ContractName.BalanceThresholdFilter, () => { senderAddress: compliantForwarderInstance.address, makerAddress: nonCompliantAddress }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadMakerAddress, - ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, - ); - // Call compliant forwarder + // Execute transaction return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, + balanceThresholdWrapper.fillOrderAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - nonCompliantAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature, + nonCompliantBalanceThresholdWrapper.fillOrderAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); -- cgit v1.2.3 From a655f4b193d032cea7654f982b2f012220a0e77f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 14:54:24 -0800 Subject: tests for fillOrder variants --- .../test/extensions/balance_threshold_filter.ts | 155 +++++++++++++++++---- 1 file changed, 131 insertions(+), 24 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index cfa1dcbdf..778aee1a1 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -298,6 +298,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); }); it('should transfer the correct amounts when maker and taker are compliant', async () => { let order2 = _.cloneDeep(compliantSignedOrder); @@ -333,6 +334,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill @@ -401,12 +403,49 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('fillOrderNoThrow', () => { + describe.only('fillOrderNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - + // Execute a valid fill + const txReceipt = await balanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); }); it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address @@ -414,42 +453,110 @@ describe.only(ContractName.BalanceThresholdFilter, () => { senderAddress: compliantForwarderInstance.address, makerAddress: nonCompliantAddress }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadMakerAddress, + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrderNoThrowAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, + }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, + }); + }); + + describe.only('fillOrKillOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; + const txReceipt = await balanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount_ + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - // Call compliant forwarder + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); + it('should revert if maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, + balanceThresholdWrapper.fillOrKillOrderAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - nonCompliantAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature, + nonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); - }); - - describe('fillOrKillOrder', () => { + it('should revert if order is not fully filled', async () => { + const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrKillOrderAsync( + compliantSignedOrder, + compliantTakerAddress, + {takerAssetFillAmount: tooBigTakerAssetFillAmount} + ), + RevertReason.FailedExecution + ); + }); }); describe('marketBuyOrders', () => { -- cgit v1.2.3 From 4b0d01ad72b93e8ccb01ca2eabe521cdf78eeea2 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 15:35:49 -0800 Subject: tests for batchFillOrdes --- .../test/extensions/balance_threshold_filter.ts | 326 ++++++++++++++++++--- 1 file changed, 287 insertions(+), 39 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 778aee1a1..c91d4880e 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -45,6 +45,7 @@ interface ValidatedAddressesLog { describe.only(ContractName.BalanceThresholdFilter, () => { let compliantMakerAddress: string; + let compliantMakerAddress2: string; let owner: string; let compliantTakerAddress: string; let feeRecipientAddress: string; @@ -57,6 +58,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let exchangeWrapper: ExchangeWrapper; let orderFactory: OrderFactory; + let orderFactory2: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; let balanceThresholdWrapper: BalanceThresholdWrapper; @@ -64,6 +66,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let takerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; + let compliantSignedOrder2: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; let logDecoder: LogDecoder; @@ -95,6 +98,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const usedAddresses = ([ owner, compliantMakerAddress, + compliantMakerAddress2, compliantTakerAddress, feeRecipientAddress, nonCompliantAddress, @@ -134,10 +138,19 @@ describe.only(ContractName.BalanceThresholdFilter, () => { await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { from: owner, }); + // Deploy Compliant Forwarder + const erc721BalanceThreshold = new BigNumber(1); + compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( + artifacts.BalanceThresholdFilter, + provider, + txDefaults, + exchangeInstance.address, + yesTokenInstance.address, + erc721BalanceThreshold + ); // Default order parameters const defaultOrderParams = { exchangeAddress: exchangeInstance.address, - makerAddress: compliantMakerAddress, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), @@ -145,19 +158,22 @@ describe.only(ContractName.BalanceThresholdFilter, () => { takerAssetAmount, makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), + senderAddress: compliantForwarderInstance.address, }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - // Deploy Compliant Forwarder - const erc721BalanceThreshold = new BigNumber(1); - compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( - artifacts.BalanceThresholdFilter, - provider, - txDefaults, - exchangeInstance.address, - yesTokenInstance.address, - erc721BalanceThreshold - ); + const defaultOrderParams1 = { + makerAddress: compliantMakerAddress, + ... + defaultOrderParams, + } + const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; + orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams1); + const defaultOrderParams2 = { + makerAddress: compliantMakerAddress2, + ... + defaultOrderParams, + } + const secondMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress2)]; + orderFactory2 = new OrderFactory(secondMakerPrivateKey, defaultOrderParams2); /* const compliantForwarderContract = new BalanceThresholdFilterContract( compliantForwarderInstance.abi, @@ -195,6 +211,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { [compliantTakerYesMark], { from: owner }, ); + await yesTokenInstance.mint2.sendTransactionAsync( + compliantMakerAddress2, + compliantTakerEntityId, + addressesCanControlTheirToken, + compliantTakerCountryCode, + [compliantTakerYesMark], + { from: owner }, + ); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); @@ -293,45 +317,269 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // @TODO - greater than 1 balance }); + describe('batchFillOrdersNoThrow', () => { + }); + + describe('batchFillOrKillOrders', () => { + }); + describe('batchFillOrKillOrders', () => { + }); - describe('batchFillOrders', () => { + describe.only('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { - let order2 = _.cloneDeep(compliantSignedOrder); - order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; - const orders = [compliantSignedOrder, order2]; - const fillAmounts = [new BigNumber(4), new BigNumber(4)]; - const signatures = ["0xabcd", "0xabcd"]; - const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); - console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); - - const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - exchangeCalldata, - compliantSignedFillOrderTx.signature, + it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const txReceipt = await balanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) + .times(2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.batchFillOrdersAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.batchFillOrdersAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); - const decoder = new LogDecoder(web3Wrapper); - const tx = await decoder.getTxWithDecodedLogsAsync(txHash); - console.log(JSON.stringify(tx, null, 4)); }); }); - describe('batchFillOrdersNoThrow', () => { + /* + describe.only('fillOrderNoThrow', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const txReceipt = await balanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); + it('should revert if maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrderNoThrowAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - describe('batchFillOrKillOrders', () => { + describe.only('fillOrKillOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; + const txReceipt = await balanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount_ + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); + it('should revert if maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrKillOrderAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if order is not fully filled', async () => { + const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrKillOrderAsync( + compliantSignedOrder, + compliantTakerAddress, + {takerAssetFillAmount: tooBigTakerAssetFillAmount} + ), + RevertReason.FailedExecution + ); + }); }); - describe('batchFillOrKillOrders', () => { - }); + */ - describe.only('fillOrder', () => { + describe('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -403,7 +651,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrderNoThrow', () => { + describe('fillOrderNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -475,7 +723,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrKillOrder', () => { + describe('fillOrKillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); -- cgit v1.2.3 From 51355209a2a43a1a7b5a72c78aabefcdac5ede33 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 15:59:15 -0800 Subject: Tests for batchFill variants --- .../test/extensions/balance_threshold_filter.ts | 214 +++++++++++++++++++-- 1 file changed, 203 insertions(+), 11 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index c91d4880e..003bc2cdc 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -317,16 +317,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // @TODO - greater than 1 balance }); - describe('batchFillOrdersNoThrow', () => { - }); - - describe('batchFillOrKillOrders', () => { - }); - - describe('batchFillOrKillOrders', () => { - }); - - describe.only('batchFillOrders', () => { + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -420,6 +411,207 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); + describe('batchFillOrdersNoThrow', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const txReceipt = await balanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) + .times(2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.batchFillOrdersNoThrowAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + }); + + describe.only('batchFillOrKillOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const txReceipt = await balanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) + .times(2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.batchFillOrKillOrdersAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if one takerAssetFillAmount is not fully filled', async () => { + const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount]; + return expectTransactionFailedAsync( + balanceThresholdWrapper.batchFillOrKillOrdersAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmounts} + ), + RevertReason.FailedExecution + ); + }); + }); + /* describe.only('fillOrderNoThrow', () => { beforeEach(async () => { @@ -564,7 +756,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); - it('should revert if order is not fully filled', async () => { + it('should revert if takerAssetFillAmount is not fully filled', async () => { const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); return expectTransactionFailedAsync( balanceThresholdWrapper.fillOrKillOrderAsync( -- cgit v1.2.3 From e45a0ffdbfd5289b9e123a4b58c26d6dbbcfd8c7 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 16:03:39 -0800 Subject: All tests running so far --- .../MixinBalanceThresholdFilterCore.sol | 1 + .../mixins/MBalanceThresholdFilterCore.sol | 1 + .../test/extensions/balance_threshold_filter.ts | 173 +-------------------- 3 files changed, 9 insertions(+), 166 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 9bed7ba47..2e058742b 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -28,6 +28,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR /// the exchange function is a cancellation. /// Supported Exchange functions: + /// - batchFillOrders /// - batchFillOrdersNoThrow /// - batchFillOrKillOrders /// - fillOrder diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol index 046caecdd..612fd481c 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -43,6 +43,7 @@ contract MBalanceThresholdFilterCore { /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR /// the exchange function is a cancellation. /// Supported Exchange functions: + /// - batchFillOrders /// - batchFillOrdersNoThrow /// - batchFillOrKillOrders /// - fillOrder diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 003bc2cdc..efe1ce49c 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -271,7 +271,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { await blockchainLifecycle.revertAsync(); }); - describe('General Sanity Checks', () => { + describe.only('General Sanity Checks', () => { it('should revert if the signed transaction is not intended for supported', async () => { // Create signed order without the fillOrder function selector const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); @@ -317,7 +317,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // @TODO - greater than 1 balance }); - describe('batchFillOrders', () => { + describe.only('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -411,7 +411,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('batchFillOrdersNoThrow', () => { + describe.only('batchFillOrdersNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -612,166 +612,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - /* - describe.only('fillOrderNoThrow', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const txReceipt = await balanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrderNoThrowAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe.only('fillOrKillOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; - const txReceipt = await balanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount_ - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrKillOrderAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if takerAssetFillAmount is not fully filled', async () => { - const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); - return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrKillOrderAsync( - compliantSignedOrder, - compliantTakerAddress, - {takerAssetFillAmount: tooBigTakerAssetFillAmount} - ), - RevertReason.FailedExecution - ); - }); - }); - - */ - - describe('fillOrder', () => { + describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -843,7 +684,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('fillOrderNoThrow', () => { + describe.only('fillOrderNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -915,7 +756,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('fillOrKillOrder', () => { + describe.only('fillOrKillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -986,7 +827,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); - it('should revert if order is not fully filled', async () => { + it('should revert if takerAssetFillAmount is not fully filled', async () => { const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); return expectTransactionFailedAsync( balanceThresholdWrapper.fillOrKillOrderAsync( -- cgit v1.2.3 From a2df428afbd820b15ea108f721ab88f77a8d6dc1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 16:18:20 -0800 Subject: tests for marketSellOrders --- .../test/extensions/balance_threshold_filter.ts | 108 +++++++++++++++++++-- 1 file changed, 99 insertions(+), 9 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index efe1ce49c..4c927f1d1 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -271,7 +271,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { await blockchainLifecycle.revertAsync(); }); - describe.only('General Sanity Checks', () => { + describe('General Sanity Checks', () => { it('should revert if the signed transaction is not intended for supported', async () => { // Create signed order without the fillOrder function selector const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); @@ -317,7 +317,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // @TODO - greater than 1 balance }); - describe.only('batchFillOrders', () => { + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -411,7 +411,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('batchFillOrdersNoThrow', () => { + describe('batchFillOrdersNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -505,7 +505,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('batchFillOrKillOrders', () => { + describe('batchFillOrKillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -612,7 +612,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrder', () => { + describe('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -684,7 +684,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrderNoThrow', () => { + describe('fillOrderNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -756,7 +756,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrKillOrder', () => { + describe('fillOrKillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -840,11 +840,101 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('marketBuyOrders', () => { + describe.only('marketSellOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); + const txReceipt = await balanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount2 = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid2 = compliantSignedOrder2.makerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid2 = compliantSignedOrder2.takerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); + const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.marketSellOrdersAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.marketSellOrdersAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - describe('marketBuyOrdersNoThrow', () => { + describe.only('marketBuyOrdersNoThrow', () => { + }); + describe('marketSellOrders', () => { }); -- cgit v1.2.3 From 4f977aa51d6a5baa4cb602cbc1d71b1481aaaa3d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 16:18:59 -0800 Subject: marketSellNoThrow tests --- .../test/extensions/balance_threshold_filter.ts | 91 ++++++++++++++++++++++ 1 file changed, 91 insertions(+) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 4c927f1d1..776712088 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -931,6 +931,97 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); + describe.only('marketSellOrdersNoThrow', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); + const txReceipt = await balanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount2 = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid2 = compliantSignedOrder2.makerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid2 = compliantSignedOrder2.takerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); + const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.marketSellOrdersNoThrowAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + }); + describe.only('marketBuyOrdersNoThrow', () => { }); -- cgit v1.2.3 From 3ad72d96f416fdd2446681c41f0151986c0a22b8 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 16:28:01 -0800 Subject: Tests for marketBuy invariants --- .../test/extensions/balance_threshold_filter.ts | 195 +++++++++++++++++++-- 1 file changed, 185 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 776712088..d12c28235 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -840,7 +840,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketSellOrders', () => { + describe('marketSellOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -931,7 +931,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketSellOrdersNoThrow', () => { + describe('marketSellOrdersNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -1022,17 +1022,192 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketBuyOrdersNoThrow', () => { - - }); - - - describe('marketSellOrders', () => { + describe.only('marketBuyOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); + const makerAssetFillAmount2 = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + const txReceipt = await balanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerFeePaid2 = compliantSignedOrder2.makerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid2 = compliantSignedOrder2.takerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + const dummyMakerAssetFillAmount = new BigNumber(0); + return expectTransactionFailedAsync( + balanceThresholdWrapper.marketBuyOrdersAsync( + orders, + compliantTakerAddress, + {makerAssetFillAmount: dummyMakerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const dummyMakerAssetFillAmount = new BigNumber(0); + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync( + orders, + nonCompliantAddress, + {makerAssetFillAmount: dummyMakerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - describe('marketSellOrdersNoThrow', () => { + describe.only('marketBuyOrdersNoThrowAsync', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); + const makerAssetFillAmount2 = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + const txReceipt = await balanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerFeePaid2 = compliantSignedOrder2.makerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid2 = compliantSignedOrder2.takerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + const dummyMakerAssetFillAmount = new BigNumber(0); + return expectTransactionFailedAsync( + balanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + orders, + compliantTakerAddress, + {makerAssetFillAmount: dummyMakerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const dummyMakerAssetFillAmount = new BigNumber(0); + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + orders, + nonCompliantAddress, + {makerAssetFillAmount: dummyMakerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - + describe('matchOrders', () => { }); -- cgit v1.2.3 From 93b9c251ed735905d30a0daa4d90fc27c8625aa6 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 17:54:27 -0800 Subject: Tests for MatchOrders --- .../MixinBalanceThresholdFilterCore.sol | 8 +- .../test/extensions/balance_threshold_filter.ts | 106 ++++++++++++++++++++- .../test/utils/balance_threshold_wrapper.ts | 4 + 3 files changed, 111 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 2e058742b..303e1d9c2 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -133,7 +133,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { /// the running list of addresses to validate. /// @param orderParamIndex - Index of the order in the Exchange function's signature function recordMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(orderParamIndex) + let orderPtr := loadExchangeData(mul(orderParamIndex, 0x20)) let makerAddress := loadExchangeData(orderPtr) recordAddressToValidate(makerAddress) } @@ -143,7 +143,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { /// the running list of addresses to validate. /// @param orderArrayParamIndex - Index of the order array in the Exchange function's signature function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { - let orderArrayPtr := loadExchangeData(orderArrayParamIndex) + let orderArrayPtr := loadExchangeData(mul(orderArrayParamIndex, 0x20)) let orderArrayLength := loadExchangeData(orderArrayPtr) let orderArrayElementPtr := add(orderArrayPtr, 0x20) let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) @@ -258,7 +258,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // This is to avoid corruption when making calls in the loop below. let freeMemPtr := addressesToValidateElementEndPtr mstore(0x40, freeMemPtr) - +/* // Validate addresses let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) let thresholdBalance := sload(THRESHOLD_BALANCE_slot) @@ -307,7 +307,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. revert(0, 132) } - } + }*/ // Record validated addresses validatedAddresses := addressesToValidate diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index d12c28235..7ca8a8e98 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -22,17 +22,20 @@ import { chaiSetup } from '../utils/chai_setup'; import { constants } from '../utils/constants'; import { ERC20Wrapper } from '../utils/erc20_wrapper'; import { ExchangeWrapper } from '../utils/exchange_wrapper'; +import { MatchOrderTester } from '../utils/match_order_tester'; import { OrderFactory } from '../utils/order_factory'; import { orderUtils } from '../utils/order_utils'; import { TransactionFactory } from '../utils/transaction_factory'; import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; +import { TestExchangeInternalsContract } from '../../generated-wrappers/test_exchange_internals'; import { MethodAbi, AbiDefinition } from 'ethereum-types'; import { AbiEncoder } from '@0x/utils'; import { Method } from '@0x/utils/lib/src/abi_encoder'; import { LogDecoder } from '../utils/log_decoder'; +import { ERC721Wrapper } from '../utils/erc721_wrapper'; chaiSetup.configure(); const expect = chai.expect; @@ -70,6 +73,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let compliantSignedFillOrderTx: SignedTransaction; let logDecoder: LogDecoder; + let exchangeInternals: TestExchangeInternalsContract; const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); @@ -263,6 +267,13 @@ describe.only(ContractName.BalanceThresholdFilter, () => { balanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; nonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); + + // Instantiate internal exchange contract + exchangeInternals = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( + artifacts.TestExchangeInternals, + provider, + txDefaults, + ); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -1022,7 +1033,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketBuyOrders', () => { + describe('marketBuyOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -1115,7 +1126,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketBuyOrdersNoThrowAsync', () => { + describe('marketBuyOrdersNoThrowAsync', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -1208,7 +1219,96 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('matchOrders', () => { + describe.only('matchOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it.only('Should transfer correct amounts when both makers and taker meet the balance threshold', async () => { + // Test values/results taken from Match Orders test: + // 'Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorFloor but fail isRoundingErrorCeil' + // Create orders to match + const signedOrderLeft = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(17), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(98), 0), + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), + feeRecipientAddress: feeRecipientAddress, + }); + const signedOrderRight = await orderFactory2.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), + feeRecipientAddress: feeRecipientAddress, + }); + // Compute expected transfer amounts + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), // 76.47% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 0), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + const txReceipt = await balanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); + // Assert validated addresses + const expectedValidatedAddresseses = [signedOrderLeft.makerAddress, signedOrderRight.makerAddress, compliantTakerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect( + newBalances[signedOrderLeft.makerAddress][defaultMakerAssetAddress], + 'Checking left maker egress ERC20 account balance', + ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][defaultMakerAssetAddress].sub(expectedTransferAmounts.amountSoldByLeftMaker)); + expect( + newBalances[signedOrderRight.makerAddress][defaultTakerAssetAddress], + 'Checking right maker ingress ERC20 account balance', + ).to.be.bignumber.equal(erc20Balances[signedOrderRight.makerAddress][defaultTakerAssetAddress].sub(expectedTransferAmounts.amountSoldByRightMaker)); + expect( + newBalances[compliantTakerAddress][defaultMakerAssetAddress], + 'Checking taker ingress ERC20 account balance', + ).to.be.bignumber.equal(erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(expectedTransferAmounts.amountReceivedByTaker)); + expect( + newBalances[signedOrderLeft.makerAddress][defaultTakerAssetAddress], + 'Checking left maker ingress ERC20 account balance', + ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][defaultTakerAssetAddress].add(expectedTransferAmounts.amountBoughtByLeftMaker)); + expect( + newBalances[signedOrderRight.makerAddress][defaultMakerAssetAddress], + 'Checking right maker egress ERC20 account balance', + ).to.be.bignumber.equal( + erc20Balances[signedOrderRight.makerAddress][defaultMakerAssetAddress].add(expectedTransferAmounts.amountBoughtByRightMaker), + ); + // Paid fees + expect( + newBalances[signedOrderLeft.makerAddress][zrxToken.address], + 'Checking left maker egress ERC20 account fees', + ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByLeftMaker)); + expect( + newBalances[signedOrderRight.makerAddress][zrxToken.address], + 'Checking right maker egress ERC20 account fees', + ).to.be.bignumber.equal(erc20Balances[signedOrderRight.makerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByRightMaker)); + expect( + newBalances[compliantTakerAddress][zrxToken.address], + 'Checking taker egress ERC20 account fees', + ).to.be.bignumber.equal(erc20Balances[compliantTakerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByTakerLeft).sub(expectedTransferAmounts.feePaidByTakerRight)); + // Received fees + expect( + newBalances[signedOrderLeft.feeRecipientAddress][zrxToken.address], + 'Checking left fee recipient ingress ERC20 account fees', + ).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(expectedTransferAmounts.feePaidByLeftMaker).add(expectedTransferAmounts.feePaidByRightMaker).add(expectedTransferAmounts.feePaidByTakerLeft).add(expectedTransferAmounts.feePaidByTakerRight), + ); + }); }); describe('cancelOrder', () => { diff --git a/packages/contracts/test/utils/balance_threshold_wrapper.ts b/packages/contracts/test/utils/balance_threshold_wrapper.ts index ac7bdd593..cff40aa52 100644 --- a/packages/contracts/test/utils/balance_threshold_wrapper.ts +++ b/packages/contracts/test/utils/balance_threshold_wrapper.ts @@ -227,6 +227,10 @@ export class BalanceThresholdWrapper { public getExchangeAddress(): string { return this._exchange.address; } + // Exchange functions + //abiEncodeFillOrder + //getFillOrderResultsAsync + // private async _executeTransaction(abiEncodedExchangeTxData: string, from: string, gas?: number): Promise { const signedExchangeTx = this._signerTransactionFactory.newSignedTransaction(abiEncodedExchangeTxData); const txOpts = _.isUndefined(gas) ? {from} : {from, gas}; -- cgit v1.2.3 From 1883f4d2726e1a879be42b7bb6168f30afd486d9 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 18:01:16 -0800 Subject: matchOrders test cases for balance threshold filter contract --- .../MixinBalanceThresholdFilterCore.sol | 4 +- .../test/extensions/balance_threshold_filter.ts | 44 +++++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 303e1d9c2..0ad8ccddf 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -258,7 +258,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // This is to avoid corruption when making calls in the loop below. let freeMemPtr := addressesToValidateElementEndPtr mstore(0x40, freeMemPtr) -/* + // Validate addresses let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) let thresholdBalance := sload(THRESHOLD_BALANCE_slot) @@ -307,7 +307,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. revert(0, 132) } - }*/ + } // Record validated addresses validatedAddresses := addressesToValidate diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 7ca8a8e98..0a03678b1 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -1225,7 +1225,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { compliantSignedOrder = await orderFactory.newSignedOrderAsync(); compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); }); - it.only('Should transfer correct amounts when both makers and taker meet the balance threshold', async () => { + it('Should transfer correct amounts when both makers and taker meet the balance threshold', async () => { // Test values/results taken from Match Orders test: // 'Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorFloor but fail isRoundingErrorCeil' // Create orders to match @@ -1309,6 +1309,48 @@ describe.only(ContractName.BalanceThresholdFilter, () => { erc20Balances[feeRecipientAddress][zrxToken.address].add(expectedTransferAmounts.feePaidByLeftMaker).add(expectedTransferAmounts.feePaidByRightMaker).add(expectedTransferAmounts.feePaidByTakerLeft).add(expectedTransferAmounts.feePaidByTakerRight), ); }); + it('should revert if left maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.matchOrdersAsync( + compliantSignedOrder, + signedOrderWithBadMakerAddress, + compliantTakerAddress, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if right maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.matchOrdersAsync( + signedOrderWithBadMakerAddress, + compliantSignedOrder, + compliantTakerAddress, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.matchOrdersAsync( + compliantSignedOrder, + compliantSignedOrder, + nonCompliantAddress, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); describe('cancelOrder', () => { -- cgit v1.2.3 From cb9ec18f962d3a44e75f291795fc59494a6226de Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 10 Dec 2018 15:11:00 -0800 Subject: Tests finished for balance threshold wrapper --- .../test/extensions/balance_threshold_filter.ts | 220 ++++++++++++++++++--- 1 file changed, 189 insertions(+), 31 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 0a03678b1..d0e902eda 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -27,7 +27,7 @@ import { OrderFactory } from '../utils/order_factory'; import { orderUtils } from '../utils/order_utils'; import { TransactionFactory } from '../utils/transaction_factory'; import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper'; -import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; +import { ContractName, ERC20BalancesByOwner, SignedTransaction, OrderStatus } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; import { TestExchangeInternalsContract } from '../../generated-wrappers/test_exchange_internals'; @@ -62,12 +62,15 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let orderFactory: OrderFactory; let orderFactory2: OrderFactory; + let nonCompliantOrderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; - let balanceThresholdWrapper: BalanceThresholdWrapper; + let takerBalanceThresholdWrapper: BalanceThresholdWrapper; + let makerBalanceThresholdWrapper: BalanceThresholdWrapper; let nonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; let takerTransactionFactory: TransactionFactory; + let makerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; let compliantSignedOrder2: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; @@ -170,6 +173,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { defaultOrderParams, } const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; + takerTransactionFactory = new TransactionFactory(makerPrivateKey, exchangeInstance.address); orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams1); const defaultOrderParams2 = { makerAddress: compliantMakerAddress2, @@ -178,6 +182,15 @@ describe.only(ContractName.BalanceThresholdFilter, () => { } const secondMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress2)]; orderFactory2 = new OrderFactory(secondMakerPrivateKey, defaultOrderParams2); + + const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; + const defaultNonCompliantOrderParams = { + makerAddress: nonCompliantAddress, + ... + defaultOrderParams, + }; + nonCompliantOrderFactory = new OrderFactory(nonCompliantPrivateKey, defaultNonCompliantOrderParams); + /* const compliantForwarderContract = new BalanceThresholdFilterContract( compliantForwarderInstance.abi, @@ -264,10 +277,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); throw new Error(`w`);*/ logDecoder = new LogDecoder(web3Wrapper); - balanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); - const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; + takerBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); + makerBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(makerPrivateKey, exchangeInstance.address), provider); + nonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); - + + // Instantiate internal exchange contract exchangeInternals = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( artifacts.TestExchangeInternals, @@ -338,7 +353,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await balanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await takerBalanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -400,7 +415,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.batchFillOrdersAsync( + takerBalanceThresholdWrapper.batchFillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -432,7 +447,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await balanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await takerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -494,7 +509,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.batchFillOrdersNoThrowAsync( + takerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -526,7 +541,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await balanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -588,7 +603,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.batchFillOrKillOrdersAsync( + takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -613,7 +628,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount]; return expectTransactionFailedAsync( - balanceThresholdWrapper.batchFillOrKillOrdersAsync( + takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -630,7 +645,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await balanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -675,7 +690,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrderAsync( + takerBalanceThresholdWrapper.fillOrderAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -702,7 +717,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await balanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -747,7 +762,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrderNoThrowAsync( + takerBalanceThresholdWrapper.fillOrderNoThrowAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -775,7 +790,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; - const txReceipt = await balanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); + const txReceipt = await takerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -820,7 +835,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrKillOrderAsync( + takerBalanceThresholdWrapper.fillOrKillOrderAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -841,7 +856,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if takerAssetFillAmount is not fully filled', async () => { const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrKillOrderAsync( + takerBalanceThresholdWrapper.fillOrKillOrderAsync( compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: tooBigTakerAssetFillAmount} @@ -861,7 +876,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await balanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -921,7 +936,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.marketSellOrdersAsync( + takerBalanceThresholdWrapper.marketSellOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmount} @@ -952,7 +967,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await balanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1012,7 +1027,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.marketSellOrdersNoThrowAsync( + takerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( orders, compliantTakerAddress, {takerAssetFillAmount} @@ -1047,7 +1062,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await balanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1104,7 +1119,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - balanceThresholdWrapper.marketBuyOrdersAsync( + takerBalanceThresholdWrapper.marketBuyOrdersAsync( orders, compliantTakerAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1140,7 +1155,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await balanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1197,7 +1212,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - balanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + takerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( orders, compliantTakerAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1219,7 +1234,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('matchOrders', () => { + describe('matchOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -1260,7 +1275,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% }; - const txReceipt = await balanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); + const txReceipt = await takerBalanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); // Assert validated addresses const expectedValidatedAddresseses = [signedOrderLeft.makerAddress, signedOrderRight.makerAddress, compliantTakerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1317,7 +1332,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.matchOrdersAsync( + takerBalanceThresholdWrapper.matchOrdersAsync( compliantSignedOrder, signedOrderWithBadMakerAddress, compliantTakerAddress, @@ -1333,7 +1348,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.matchOrdersAsync( + takerBalanceThresholdWrapper.matchOrdersAsync( signedOrderWithBadMakerAddress, compliantSignedOrder, compliantTakerAddress, @@ -1354,12 +1369,155 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); describe('cancelOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('Should successfully cancel order if maker meets balance threshold', async () => { + // Verify order is not cancelled + const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + // Cancel + const txReceipt = await makerBalanceThresholdWrapper.cancelOrderAsync(compliantSignedOrder, compliantSignedOrder.makerAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + }); + it('Should successfully cancel order if maker does not meet balance threshold', async () => { + // Create order where maker does not meet balance threshold + const signedOrderWithBadMakerAddress = await nonCompliantOrderFactory.newSignedOrderAsync({}); + // Verify order is not cancelled + const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) + expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + // Cancel + const txReceipt = await nonCompliantBalanceThresholdWrapper.cancelOrderAsync(signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) + expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + }); }); describe('batchCancelOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('Should successfully batch cancel orders if maker meets balance threshold', async () => { + // Create orders to cancel + const compliantSignedOrders = [ + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync(), + ]; + // Verify orders are not cancelled + await _.each(compliantSignedOrders, async (compliantSignedOrder) => { + const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + }); + // Cancel + const txReceipt = await makerBalanceThresholdWrapper.batchCancelOrdersAsync(compliantSignedOrders, compliantSignedOrder.makerAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + await _.each(compliantSignedOrders, async (compliantSignedOrder) => { + const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + }); + }); + it('Should successfully batch cancel order if maker does not meet balance threshold', async () => { + // Create orders to cancel + const nonCompliantSignedOrders = [ + await nonCompliantOrderFactory.newSignedOrderAsync(), + await nonCompliantOrderFactory.newSignedOrderAsync(), + await nonCompliantOrderFactory.newSignedOrderAsync(), + ]; + // Verify orders are not cancelled + await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { + const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + }); + // Cancel + const txReceipt = await nonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync(nonCompliantSignedOrders, nonCompliantAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { + const orderInfoAfterCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + }); + }); }); describe('cancelOrdersUpTo', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('Should successfully batch cancel orders if maker meets balance threshold', async () => { + // Create orders to cancel + const compliantSignedOrders = [ + await orderFactory.newSignedOrderAsync({salt: new BigNumber(0)}), + await orderFactory.newSignedOrderAsync({salt: new BigNumber(1)}), + await orderFactory.newSignedOrderAsync({salt: new BigNumber(2)}), + ]; + // Verify orders are not cancelled + await _.each(compliantSignedOrders, async (compliantSignedOrder) => { + const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + }); + // Cancel + const cancelOrdersUpToThisSalt = new BigNumber(1); + const txReceipt = await makerBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, compliantSignedOrder.makerAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + await _.each(compliantSignedOrders, async (compliantSignedOrder, salt: number) => { + const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const saltAsBigNumber = new BigNumber(salt); + if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + } else { + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + } + }); + }); + it('Should successfully batch cancel order if maker does not meet balance threshold', async () => { + // Create orders to cancel + const nonCompliantSignedOrders = [ + await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(0)}), + await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(1)}), + await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(2)}), + ]; + // Verify orders are not cancelled + await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { + const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + }); + // Cancel + const cancelOrdersUpToThisSalt = new BigNumber(1); + const txReceipt = await nonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, nonCompliantAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder, salt: number) => { + const orderInfoAfterCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const saltAsBigNumber = new BigNumber(salt); + if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + } else { + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + } + }); + }); }); }); // tslint:disable:max-file-line-count -- cgit v1.2.3 From 8d6219296a4ac0c2ec46ae077eb87cebb19f8b55 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 10 Dec 2018 17:08:16 -0800 Subject: Removed Yes Token - its no longer needed to test Balance Threshold Filter --- .../YesComplianceToken/IYesComplianceToken.sol | 119 ---- .../YesComplianceToken/WyreERC721Token/ERC721.sol | 40 -- .../WyreERC721Token/ERC721Basic.sol | 47 -- .../WyreERC721Token/ERC721BasicToken.sol | 343 ----------- .../WyreERC721Token/ERC721Token.sol | 209 ------- .../YesComplianceToken/YesComplianceToken.sol | 648 --------------------- .../test/extensions/balance_threshold_filter.ts | 59 +- 7 files changed, 12 insertions(+), 1453 deletions(-) delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol (limited to 'packages') diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol deleted file mode 100644 index a1c9b9671..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol +++ /dev/null @@ -1,119 +0,0 @@ -pragma solidity ^0.4.24; - -import "./WyreERC721Token/ERC721Token.sol"; - -/** - * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific - * compliance-related queries with YES. (attestations) - * - * primarily ERC721 is useful for the self-management of claiming addresses. a single token is more useful - * than a non-ERC721 interface because of interop with other 721-supporting systems/ui; it allows users to - * manage their financial stamp with flexibility using a well-established simple concept of non-fungible tokens. - * this interface is for anyone needing to carry around and otherwise manage their proof of compliance. - * - * the financial systems these users authenticate against have a different set of API requirements. they need - * more contextualization ability than a balance check to support distinctions of attestations, as well as geographic - * distinction. these integrations are made simpler as the language of the query more closely match the language of compliance. - * - * this interface describes, beyond 721, these simple compliance-specific interfaces (and their management tools) - * - * notes: - * - no address can be associated with more than one identity (though addresses may have more than token). issuance - * in this circumstance will fail - * - one person or business = one entity - * - one entity may have many tokens across many addresses; they can mint and burn tokens tied to their identity at will - * - two token types: control & non-control. both carry compliance proof - * - control tokens let their holders mint and burn (within the same entity) - * - non-control tokens are solely for compliance queries - * - a lock on the entity is used instead of token revocation to remove the cash burden assumed by a customer to - * redistribute a fleet of coins - * - all country codes should be via ISO-3166-1 - * - * any (non-view) methods not explicitly marked idempotent are not idempotent. - */ -contract YesComplianceTokenV1 is ERC721Token /*, ERC165 :should: */ { - - uint256 public constant OWNER_ENTITY_ID = 1; - - uint8 public constant YESMARK_OWNER = 128; - uint8 public constant YESMARK_VALIDATOR = 129; - - /* - todo events: entity updated, destroyed, ???? - Finalized - Attested - - */ - - /** - * @notice query api: returns true if the specified address has the given country/yes attestation. this - * is the primary method partners will use to query the active qualifications of any particular - * address. - */ - function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) ; - - /** @notice same as isYes except as an imperative */ - function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view ; - - /** - * @notice retrieve all YES marks for an address in a particular country - * @param _validatorEntityId the validator ID to consider. or, use 0 for any of them - * @param _address the validator ID to consider, or 0 for any of them - * @param _countryCode the ISO-3166-1 country code - * @return (non-duplicate) array of YES marks present - */ - function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] /* memory */); - - // function getCountries(uint256 _validatorEntityId, address _address) external view returns(uint16[] /* memory */); - - /** - * @notice create new tokens. fail if _to already - * belongs to a different entity and caller is not validator - * @param _control true if the new token is a control token (can mint, burn). aka NOT limited. - * @param _entityId the entity to mint for, supply 0 to use the entity tied to the caller - * @return the newly created token ID - */ - function mint(address _to, uint256 _entityId, bool _control) external returns (uint256); - - /** @notice shortcut to mint() + setYes() in one call, for a single country */ - function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256); - - /** @notice destroys a specific token */ - function burn(uint256 _tokenId) external; - - /** @notice destroys the entire entity and all tokens */ - function burnEntity(uint256 _entityId) external; - - /** - * @notice adds a specific attestations (yes) to an entity. idempotent: will return normally even if the mark - * was already set by this validator - */ - function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; - - /** - * @notice removes a attestation(s) from a specific validator for an entity. idempotent - */ - function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; - - /** @notice removes all attestations in a given country for a particular entity. idempotent */ - function clearYes(uint256 _entityId, uint16 _countryCode) external; - - /** @notice removes all attestations for a particular entity. idempotent */ - function clearYes(uint256 _entityId) external; - - /** @notice assigns a lock to an entity, rendering all isYes queries false. idempotent */ - function setLocked(uint256 _entityId, bool _lock) external; - - /** @notice checks whether or not a particular entity is locked */ - function isLocked(uint256 _entityId) external view returns(bool); - - /** @notice returns true if the specified token has been finalized (cannot be moved) */ - function isFinalized(uint256 _tokenId) external view returns(bool); - - /** @notice finalizes a token by ID preventing it from getting moved. idempotent */ - function finalize(uint256 _tokenId) external; - - /** @return the entity ID associated with an address (or fail if there is not one) */ - function getEntityId(address _address) external view returns(uint256); - -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol deleted file mode 100644 index 5b4907f13..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol +++ /dev/null @@ -1,40 +0,0 @@ -pragma solidity ^0.4.21; - -import "./ERC721Basic.sol"; - - -/** - * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension - * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721Enumerable is ERC721Basic { - function totalSupply() public view returns (uint256); - function tokenOfOwnerByIndex( - address _owner, - uint256 _index - ) - public - view - returns (uint256 _tokenId); - - function tokenByIndex(uint256 _index) public view returns (uint256); -} - - -/** - * @title ERC-721 Non-Fungible Token Standard, optional metadata extension - * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721Metadata is ERC721Basic { - function name() external view returns (string _name); - function symbol() external view returns (string _symbol); - function tokenURI(uint256 _tokenId) public view returns (string); -} - - -/** - * @title ERC-721 Non-Fungible Token Standard, full implementation interface - * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721 is ERC721Basic, ERC721Enumerable, ERC721Metadata { -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol deleted file mode 100644 index 20f3c5812..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol +++ /dev/null @@ -1,47 +0,0 @@ -pragma solidity ^0.4.21; - -/** - * @title ERC721 Non-Fungible Token Standard basic interface - * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721Basic { - event Transfer( - address indexed _from, - address indexed _to, - uint256 indexed _tokenId - ); - event Approval( - address indexed _owner, - address indexed _approved, - uint256 indexed _tokenId - ); - event ApprovalForAll( - address indexed _owner, - address indexed _operator, - bool _approved - ); - - function balanceOf(address _owner) public view returns (uint256 _balance); - function ownerOf(uint256 _tokenId) public view returns (address _owner); - function exists(uint256 _tokenId) public view returns (bool _exists); - - function approve(address _to, uint256 _tokenId) public; - function getApproved(uint256 _tokenId) - public view returns (address _operator); - - function setApprovalForAll(address _operator, bool _approved) public; - function isApprovedForAll(address _owner, address _operator) - public view returns (bool); - - function transferFrom(address _from, address _to, uint256 _tokenId) public; - function safeTransferFrom(address _from, address _to, uint256 _tokenId) - public; - - function safeTransferFrom( - address _from, - address _to, - uint256 _tokenId, - bytes _data - ) - public; -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol deleted file mode 100644 index 788e31580..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol +++ /dev/null @@ -1,343 +0,0 @@ -pragma solidity ^0.4.21; - -import "./ERC721Basic.sol"; -import "../../ERC721Token/IERC721Receiver.sol"; -import "../../../utils/SafeMath/SafeMath.sol"; - - -/** - * @title ERC721 Non-Fungible Token Standard basic implementation - * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721BasicToken is ERC721Basic, SafeMath { - - bytes4 private constant InterfaceId_ERC721 = 0x80ac58cd; - /* - * 0x80ac58cd === - * bytes4(keccak256('balanceOf(address)')) ^ - * bytes4(keccak256('ownerOf(uint256)')) ^ - * bytes4(keccak256('approve(address,uint256)')) ^ - * bytes4(keccak256('getApproved(uint256)')) ^ - * bytes4(keccak256('setApprovalForAll(address,bool)')) ^ - * bytes4(keccak256('isApprovedForAll(address,address)')) ^ - * bytes4(keccak256('transferFrom(address,address,uint256)')) ^ - * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) ^ - * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) - */ - - bytes4 private constant InterfaceId_ERC721Exists = 0x4f558e79; - /* - * 0x4f558e79 === - * bytes4(keccak256('exists(uint256)')) - */ - - // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` - // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` - bytes4 private constant ERC721_RECEIVED = 0x150b7a02; - - // Mapping from token ID to owner - mapping (uint256 => address) internal tokenOwner; - - // Mapping from token ID to approved address - mapping (uint256 => address) internal tokenApprovals; - - // Mapping from owner to number of owned token - mapping (address => uint256) internal ownedTokensCount; - - // Mapping from owner to operator approvals - mapping (address => mapping (address => bool)) internal operatorApprovals; - - /** - * @dev Guarantees msg.sender is owner of the given token - * @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender - */ - modifier onlyOwnerOf(uint256 _tokenId) { - require(ownerOf(_tokenId) == msg.sender); - _; - } - - /** - * @dev Checks msg.sender can transfer a token, by being owner, approved, or operator - * @param _tokenId uint256 ID of the token to validate - */ - modifier canTransfer(uint256 _tokenId) { - require(isApprovedOrOwner(msg.sender, _tokenId)); - _; - } - - /** - * @dev Gets the balance of the specified address - * @param _owner address to query the balance of - * @return uint256 representing the amount owned by the passed address - */ - function balanceOf(address _owner) public view returns (uint256) { - require(_owner != address(0)); - return ownedTokensCount[_owner]; - } - - /** - * @dev Gets the owner of the specified token ID - * @param _tokenId uint256 ID of the token to query the owner of - * @return owner address currently marked as the owner of the given token ID - */ - function ownerOf(uint256 _tokenId) public view returns (address) { - address owner = tokenOwner[_tokenId]; - require(owner != address(0)); - return owner; - } - - /** - * @dev Returns whether the specified token exists - * @param _tokenId uint256 ID of the token to query the existence of - * @return whether the token exists - */ - function exists(uint256 _tokenId) public view returns (bool) { - address owner = tokenOwner[_tokenId]; - return owner != address(0); - } - - /** - * @dev Approves another address to transfer the given token ID - * The zero address indicates there is no approved address. - * There can only be one approved address per token at a given time. - * Can only be called by the token owner or an approved operator. - * @param _to address to be approved for the given token ID - * @param _tokenId uint256 ID of the token to be approved - */ - function approve(address _to, uint256 _tokenId) public { - address owner = ownerOf(_tokenId); - require(_to != owner); - require(msg.sender == owner || isApprovedForAll(owner, msg.sender)); - - tokenApprovals[_tokenId] = _to; - emit Approval(owner, _to, _tokenId); - } - - /** - * @dev Gets the approved address for a token ID, or zero if no address set - * @param _tokenId uint256 ID of the token to query the approval of - * @return address currently approved for the given token ID - */ - function getApproved(uint256 _tokenId) public view returns (address) { - return tokenApprovals[_tokenId]; - } - - /** - * @dev Sets or unsets the approval of a given operator - * An operator is allowed to transfer all tokens of the sender on their behalf - * @param _to operator address to set the approval - * @param _approved representing the status of the approval to be set - */ - function setApprovalForAll(address _to, bool _approved) public { - require(_to != msg.sender); - operatorApprovals[msg.sender][_to] = _approved; - emit ApprovalForAll(msg.sender, _to, _approved); - } - - /** - * @dev Tells whether an operator is approved by a given owner - * @param _owner owner address which you want to query the approval of - * @param _operator operator address which you want to query the approval of - * @return bool whether the given operator is approved by the given owner - */ - function isApprovedForAll( - address _owner, - address _operator - ) - public - view - returns (bool) - { - return operatorApprovals[_owner][_operator]; - } - - /** - * @dev Transfers the ownership of a given token ID to another address - * Usage of this method is discouraged, use `safeTransferFrom` whenever possible - * Requires the msg sender to be the owner, approved, or operator - * @param _from current owner of the token - * @param _to address to receive the ownership of the given token ID - * @param _tokenId uint256 ID of the token to be transferred - */ - function transferFrom( - address _from, - address _to, - uint256 _tokenId - ) - public - canTransfer(_tokenId) - { - require(_from != address(0)); - require(_to != address(0)); - - clearApproval(_from, _tokenId); - removeTokenFrom(_from, _tokenId); - addTokenTo(_to, _tokenId); - - emit Transfer(_from, _to, _tokenId); - } - - /** - * @dev Safely transfers the ownership of a given token ID to another address - * If the target address is a contract, it must implement `onERC721Received`, - * which is called upon a safe transfer, and return the magic value - * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, - * the transfer is reverted. - * - * Requires the msg sender to be the owner, approved, or operator - * @param _from current owner of the token - * @param _to address to receive the ownership of the given token ID - * @param _tokenId uint256 ID of the token to be transferred - */ - function safeTransferFrom( - address _from, - address _to, - uint256 _tokenId - ) - public - canTransfer(_tokenId) - { - // solium-disable-next-line arg-overflow - safeTransferFrom(_from, _to, _tokenId, ""); - } - - /** - * @dev Safely transfers the ownership of a given token ID to another address - * If the target address is a contract, it must implement `onERC721Received`, - * which is called upon a safe transfer, and return the magic value - * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, - * the transfer is reverted. - * Requires the msg sender to be the owner, approved, or operator - * @param _from current owner of the token - * @param _to address to receive the ownership of the given token ID - * @param _tokenId uint256 ID of the token to be transferred - * @param _data bytes data to send along with a safe transfer check - */ - function safeTransferFrom( - address _from, - address _to, - uint256 _tokenId, - bytes _data - ) - public - canTransfer(_tokenId) - { - transferFrom(_from, _to, _tokenId); - // solium-disable-next-line arg-overflow - require(checkAndCallSafeTransfer(_from, _to, _tokenId, _data)); - } - - /** - * @dev Returns whether the given spender can transfer a given token ID - * @param _spender address of the spender to query - * @param _tokenId uint256 ID of the token to be transferred - * @return bool whether the msg.sender is approved for the given token ID, - * is an operator of the owner, or is the owner of the token - */ - function isApprovedOrOwner( - address _spender, - uint256 _tokenId - ) - internal - view - returns (bool) - { - address owner = ownerOf(_tokenId); - // Disable solium check because of - // https://github.com/duaraghav8/Solium/issues/175 - // solium-disable-next-line operator-whitespace - return ( - _spender == owner || - getApproved(_tokenId) == _spender || - isApprovedForAll(owner, _spender) - ); - } - - /** - * @dev Internal function to mint a new token - * Reverts if the given token ID already exists - * @param _to The address that will own the minted token - * @param _tokenId uint256 ID of the token to be minted by the msg.sender - */ - function _mint(address _to, uint256 _tokenId) internal { - require(_to != address(0)); - addTokenTo(_to, _tokenId); - emit Transfer(address(0), _to, _tokenId); - } - - /** - * @dev Internal function to burn a specific token - * Reverts if the token does not exist - * @param _tokenId uint256 ID of the token being burned by the msg.sender - */ - function _burn(address _owner, uint256 _tokenId) internal { - clearApproval(_owner, _tokenId); - removeTokenFrom(_owner, _tokenId); - emit Transfer(_owner, address(0), _tokenId); - } - - /** - * @dev Internal function to clear current approval of a given token ID - * Reverts if the given address is not indeed the owner of the token - * @param _owner owner of the token - * @param _tokenId uint256 ID of the token to be transferred - */ - function clearApproval(address _owner, uint256 _tokenId) internal { - require(ownerOf(_tokenId) == _owner); - if (tokenApprovals[_tokenId] != address(0)) { - tokenApprovals[_tokenId] = address(0); - } - } - - /** - * @dev Internal function to add a token ID to the list of a given address - * @param _to address representing the new owner of the given token ID - * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address - */ - function addTokenTo(address _to, uint256 _tokenId) internal { - require(tokenOwner[_tokenId] == address(0)); - tokenOwner[_tokenId] = _to; - ownedTokensCount[_to] = safeAdd(ownedTokensCount[_to], 1); - } - - /** - * @dev Internal function to remove a token ID from the list of a given address - * @param _from address representing the previous owner of the given token ID - * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address - */ - function removeTokenFrom(address _from, uint256 _tokenId) internal { - require(ownerOf(_tokenId) == _from); - ownedTokensCount[_from] = safeSub(ownedTokensCount[_from], 1); - tokenOwner[_tokenId] = address(0); - } - - /** - * @dev Internal function to invoke `onERC721Received` on a target address - * The call is not executed if the target address is not a contract - * @param _from address representing the previous owner of the given token ID - * @param _to target address that will receive the tokens - * @param _tokenId uint256 ID of the token to be transferred - * @param _data bytes optional data to send along with the call - * @return whether the call correctly returned the expected magic value - */ - function checkAndCallSafeTransfer( - address _from, - address _to, - uint256 _tokenId, - bytes _data - ) - internal - returns (bool) - { - uint256 receiverCodeSize; - assembly { - receiverCodeSize := extcodesize(_to) - } - if (receiverCodeSize == 0) { - return true; - } - bytes4 retval = IERC721Receiver(_to).onERC721Received( - msg.sender, _from, _tokenId, _data); - return (retval == ERC721_RECEIVED); - } -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol deleted file mode 100644 index 832ff3784..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol +++ /dev/null @@ -1,209 +0,0 @@ -pragma solidity ^0.4.21; - -import "./ERC721.sol"; -import "./ERC721BasicToken.sol"; - - -/** - * @title Full ERC721 Token - * This implementation includes all the required and some optional functionality of the ERC721 standard - * Moreover, it includes approve all functionality using operator terminology - * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721Token is ERC721BasicToken, ERC721 { - - bytes4 private constant InterfaceId_ERC721Enumerable = 0x780e9d63; - /** - * 0x780e9d63 === - * bytes4(keccak256('totalSupply()')) ^ - * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) ^ - * bytes4(keccak256('tokenByIndex(uint256)')) - */ - - bytes4 private constant InterfaceId_ERC721Metadata = 0x5b5e139f; - /** - * 0x5b5e139f === - * bytes4(keccak256('name()')) ^ - * bytes4(keccak256('symbol()')) ^ - * bytes4(keccak256('tokenURI(uint256)')) - */ - - // Token name - string internal name_; - - // Token symbol - string internal symbol_; - - // Mapping from owner to list of owned token IDs - mapping(address => uint256[]) internal ownedTokens; - - // Mapping from token ID to index of the owner tokens list - mapping(uint256 => uint256) internal ownedTokensIndex; - - // Array with all token ids, used for enumeration - uint256[] internal allTokens; - - // Mapping from token id to position in the allTokens array - mapping(uint256 => uint256) internal allTokensIndex; - - // Optional mapping for token URIs - mapping(uint256 => string) internal tokenURIs; - - /** - * @dev Constructor function - */ - function initialize(string _name, string _symbol) public { - name_ = _name; - symbol_ = _symbol; - } - - /** - * @dev Gets the token name - * @return string representing the token name - */ - function name() external view returns (string) { - return name_; - } - - /** - * @dev Gets the token symbol - * @return string representing the token symbol - */ - function symbol() external view returns (string) { - return symbol_; - } - - /** - * @dev Returns an URI for a given token ID - * Throws if the token ID does not exist. May return an empty string. - * @param _tokenId uint256 ID of the token to query - */ - function tokenURI(uint256 _tokenId) public view returns (string) { - require(exists(_tokenId)); - return tokenURIs[_tokenId]; - } - - /** - * @dev Gets the token ID at a given index of the tokens list of the requested owner - * @param _owner address owning the tokens list to be accessed - * @param _index uint256 representing the index to be accessed of the requested tokens list - * @return uint256 token ID at the given index of the tokens list owned by the requested address - */ - function tokenOfOwnerByIndex( - address _owner, - uint256 _index - ) - public - view - returns (uint256) - { - require(_index < balanceOf(_owner)); - return ownedTokens[_owner][_index]; - } - - /** - * @dev Gets the total amount of tokens stored by the contract - * @return uint256 representing the total amount of tokens - */ - function totalSupply() public view returns (uint256) { - return allTokens.length; - } - - /** - * @dev Gets the token ID at a given index of all the tokens in this contract - * Reverts if the index is greater or equal to the total number of tokens - * @param _index uint256 representing the index to be accessed of the tokens list - * @return uint256 token ID at the given index of the tokens list - */ - function tokenByIndex(uint256 _index) public view returns (uint256) { - require(_index < totalSupply()); - return allTokens[_index]; - } - - /** - * @dev Internal function to set the token URI for a given token - * Reverts if the token ID does not exist - * @param _tokenId uint256 ID of the token to set its URI - * @param _uri string URI to assign - */ - function _setTokenURI(uint256 _tokenId, string _uri) internal { - require(exists(_tokenId)); - tokenURIs[_tokenId] = _uri; - } - - /** - * @dev Internal function to add a token ID to the list of a given address - * @param _to address representing the new owner of the given token ID - * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address - */ - function addTokenTo(address _to, uint256 _tokenId) internal { - super.addTokenTo(_to, _tokenId); - uint256 length = ownedTokens[_to].length; - ownedTokens[_to].push(_tokenId); - ownedTokensIndex[_tokenId] = length; - } - - /** - * @dev Internal function to remove a token ID from the list of a given address - * @param _from address representing the previous owner of the given token ID - * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address - */ - function removeTokenFrom(address _from, uint256 _tokenId) internal { - super.removeTokenFrom(_from, _tokenId); - - uint256 tokenIndex = ownedTokensIndex[_tokenId]; - uint256 lastTokenIndex = safeSub(ownedTokens[_from].length, 1); - uint256 lastToken = ownedTokens[_from][lastTokenIndex]; - - ownedTokens[_from][tokenIndex] = lastToken; - ownedTokens[_from][lastTokenIndex] = 0; - // Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to - // be zero. Then we can make sure that we will remove _tokenId from the ownedTokens list since we are first swapping - // the lastToken to the first position, and then dropping the element placed in the last position of the list - - ownedTokens[_from].length--; - ownedTokensIndex[_tokenId] = 0; - ownedTokensIndex[lastToken] = tokenIndex; - } - - /** - * @dev Internal function to mint a new token - * Reverts if the given token ID already exists - * @param _to address the beneficiary that will own the minted token - * @param _tokenId uint256 ID of the token to be minted by the msg.sender - */ - function _mint(address _to, uint256 _tokenId) internal { - super._mint(_to, _tokenId); - - allTokensIndex[_tokenId] = allTokens.length; - allTokens.push(_tokenId); - } - - /** - * @dev Internal function to burn a specific token - * Reverts if the token does not exist - * @param _owner owner of the token to burn - * @param _tokenId uint256 ID of the token being burned by the msg.sender - */ - function _burn(address _owner, uint256 _tokenId) internal { - super._burn(_owner, _tokenId); - - // Clear metadata (if any) - if (bytes(tokenURIs[_tokenId]).length != 0) { - delete tokenURIs[_tokenId]; - } - - // Reorg all tokens array - uint256 tokenIndex = allTokensIndex[_tokenId]; - uint256 lastTokenIndex = safeSub(allTokens.length, 1); - uint256 lastToken = allTokens[lastTokenIndex]; - - allTokens[tokenIndex] = lastToken; - allTokens[lastTokenIndex] = 0; - - allTokens.length--; - allTokensIndex[_tokenId] = 0; - allTokensIndex[lastToken] = tokenIndex; - } - -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol deleted file mode 100644 index 65ea99d0c..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol +++ /dev/null @@ -1,648 +0,0 @@ -pragma solidity ^0.4.24; - -import "./IYesComplianceToken.sol"; - -/** - * draft implementation of YES compliance token - * - * NOTE: i have done relatively few gas optimization tweaks (beyond using the sturctures necessary to avoid any - * linear time procedures). - * in some cases i am using a call structure which replicates some checks. this is for code clarity/security - - * i marked a few obvious ones which could be optimized for gas, but :meh: - * - * todo static owner should follow owner token? remove static owner? :security: :should: - * @author Tyson Malchow - */ -contract YesComplianceToken is YesComplianceTokenV1 { - - uint64 private constant MAX_TOKENS_PER_ENTITY = 10240; // completely arbitrary limit - uint64 private constant MAX_ENTITIES = 2**32-1; // bc using 32 bit index tracking - uint64 private constant MAX_VALIDATORS_PER_MARK = 2**32-1; // bc using 32 bit index tracking - uint64 private constant TOTAL_YES_MARKS = 255; // bc 'uint8 yes' - - // todo could shorten the entity IDs to anything 160+ to make this cheaper? - - /** @notice a single YES attestation */ - struct YesMark { - - /** @notice ISO-3166-1 country codes */ - uint16 countryCode; - - /** @notice the possibly-country-speicifc YES being marked. */ - uint8 yes; - - // 8 bits more space in this slot.. could upgrade yes to uint16? - - /** @notice the index of this mark in EntityRecord.yesMarks */ - uint32 yesMarkIdx; - - /** a list of the validator entities which have attested to this mark */ - uint256[] validatorEntityIds; - - /** @notice index of each validator entity ID in validatorEntityIds */ - mapping(uint256 => uint32) validatorEntityIdIdx; - - // uint8 entityListIdx; - } - - /** - * tracks the state for a single recognized entity - */ - struct EntityRecord { - - /** true marking this entity ID has been encountered */ - bool init; - - /** when true, this entity is effectively useless */ - bool locked; - - // 30 bits more space in this slot - - /** position of the entityId in allEntityIds */ - uint32 entityIdIdx; - - /** used for creating reliable token IDs, monotonically increasing */ - uint64 tokenIdCounter; - - /** indexed YES mark lookups */ - mapping(bytes4 => YesMark) yesMarkByKey; - - /** raw collection of all marks keys */ - bytes4[] yesMarkKeys; - - /** all tokens associated with this identity */ - uint256[] tokenIds; - - // trellis/tower connection ? - // civic connection ? - // erc725/735 connection ? - } - - /** - * @notice all fields we want to add per-token. - * - * there may never be more than just control flag, in which case it may make sense to collapse this - * to just a mapping(uint256 => bool) ? - */ - struct TokenRecord { - - /** position of the tokenId in EntityRecord.tokenIds */ - uint32 tokenIdIdx; - - /** true if this token has administrative superpowers (aka is _not_ limited) */ - bool control; - - /** true if this token cannot move */ - bool finalized; - - // 30 bits more in this slot - - // limitations: in/out? - } - - address public ownerAddress; - - mapping(uint256 => TokenRecord) public tokenRecordById; - mapping(uint256 => EntityRecord) public entityRecordById; - mapping(uint256 => uint256) public entityIdByTokenId; - - /** for entity enumeration. maximum of 2^256-1 total entities (i think we'll be ok) */ - uint256[] entityIds; - - constructor() public { - /* this space intentionally left blank */ - } - - /** - * constructor alternative: first-time initialization the contract/token (required because of upgradeability) - */ - function initialize(string _name, string _symbol) { - // require(super._symbol.length == 0 || _symbol == super._symbol); // cannot change symbol after first init bc that could fuck shit up - super.initialize(_name, _symbol); // init token info - - // grant the owner token - mint_I(ownerAddress, OWNER_ENTITY_ID, true); - - // ecosystem owner gets both owner and validator marks (self-attested) - setYes_I(OWNER_ENTITY_ID, OWNER_ENTITY_ID, 0, YESMARK_OWNER); - setYes_I(OWNER_ENTITY_ID, OWNER_ENTITY_ID, 0, YESMARK_VALIDATOR); - } - - /** - * executed in lieu of a constructor in a delegated context - */ - function _upgradeable_initialize() public { - - // some things are still tied to the owner (instead of the yesmark_owner :notsureif:) - ownerAddress = msg.sender; - } - - // YesComplianceTokenV1 Interface Methods -------------------------------------------------------------------------- - - function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) { - return isYes_I(_validatorEntityId, _address, _countryCode, _yes); - } - - function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view { - require(isYes_I(_validatorEntityId, _address, _countryCode, _yes)); - } - - function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] memory) { - if(balanceOf(_address) == 0) - return new uint8[](0); - - uint256 entityId = entityIdByTokenId[tokenOfOwnerByIndex(_address, 0)]; - EntityRecord storage e = entityRecordById[entityId]; - uint256 j = 0; - uint256 i; - - // locked always bails - if(e.locked) - return new uint8[](0); - - uint8[] memory r = new uint8[](e.yesMarkKeys.length); - - for(i = 0; i < e.yesMarkKeys.length; i++) { - YesMark storage m = e.yesMarkByKey[e.yesMarkKeys[i]]; - - // filter country code - if(m.countryCode != _countryCode) - continue; - - // filter explicit validator entity - if(_validatorEntityId > 0 - && m.validatorEntityIdIdx[_validatorEntityId] == 0 - && (m.validatorEntityIds.length == 0 || m.validatorEntityIds[0] == _validatorEntityId)) - continue; - - // matched, chyess - r[j++] = m.yes; - } - - // reduce array length - assembly { mstore(r, j) } - - return r; - } - - function mint(address _to, uint256 _entityId, bool _control) external returns (uint256) /* internally protected */{ - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - - // make sure caller has a control token, at the least - require(tokenRecordById[callerTokenId].control, 'control token required'); - - // determine/validate the entity being minted for - uint256 realEntityId; - if(_entityId == 0 || _entityId == callerEntityId) { - // unspecified entity, or caller entity, can do! - realEntityId = callerEntityId; - - } else { - // otherwise make sure caller is a VALIDATOR, else fail - require(senderIsControlValidator(), 'illegal entity id'); // some duplicate checks/lookups, gas leak - realEntityId = _entityId; - } - - return mint_I(_to, realEntityId, _control); - } - - function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256) /* internally protected */ { - // lazy warning: this is a 90% copy/paste job from the mint directly above this - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - - // make sure caller has a control token, at the least - require(tokenRecordById[callerTokenId].control, 'control token required'); - - // determine/validate the entity being minted for - uint256 realEntityId; - if(_entityId == 0 || _entityId == callerEntityId) { - // unspecified entity, or caller entity, can do! - realEntityId = callerEntityId; - - } else { - // otherwise make sure caller is a VALIDATOR, else fail - require(senderIsControlValidator()); // some duplicate checks/lookups, gas leak - realEntityId = _entityId; - } - - // mint the coin - uint256 tokenId = mint_I(_to, realEntityId, _control); - - // now set the attestations - require(_yes.length <= TOTAL_YES_MARKS); // safety - for(uint256 i = 0; i<_yes.length; i++) { - setYes_I(_entityId, _countryCode, _yes[i]); - } - - return tokenId; - } - - function getEntityId(address _address) external view returns (uint256) { - return entityIdByTokenId[tokenOfOwnerByIndex(_address, 0)]; - } - - function burn(uint256 _tokenId) external permission_control_tokenId(_tokenId) { - uint256 entityId = entityIdByTokenId[_tokenId]; - - EntityRecord storage e = entity(entityId); - TokenRecord storage t = tokenRecordById[_tokenId]; - - // remove token from entity - e.tokenIds[t.tokenIdIdx] = e.tokenIds[e.tokenIds.length - 1]; - e.tokenIds.length--; - - // update tracked index (of swapped, if present) - if(e.tokenIds.length > t.tokenIdIdx) - tokenRecordById[e.tokenIds[t.tokenIdIdx]].tokenIdIdx = t.tokenIdIdx; - - // remove token record - delete tokenRecordById[_tokenId]; - - // burn the actual token - super._burn(tokenOwner[_tokenId], _tokenId); - } - - function burnEntity(uint256 _entityId) external permission_control_entityId(_entityId) { // self-burn allowed - EntityRecord storage e = entity(_entityId); - - // burn all the tokens - for(uint256 i = 0; i < e.tokenIds.length; i++) { - uint256 tokenId = e.tokenIds[i]; - super._burn(tokenOwner[tokenId], tokenId); - } - - // clear all the marks - clearYes_I(_entityId); - - // clear out entity record - e.init = false; - e.locked = false; - e.entityIdIdx = 0; - e.tokenIdCounter = 0; - - assert(e.yesMarkKeys.length == 0); - assert(e.tokenIds.length == 0); - } - - function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external permission_validator { - setYes_I(_entityId, _countryCode, _yes); - } - - function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external permission_validator { - require(_yes > 0); - require(_yes != 128); - - // special check against reserved country code 0 - if(_countryCode == 0) - require(senderIsEcosystemControl(), 'not authorized as ecosystem control'); // this is duplicating some things, gas leak - - EntityRecord storage e = entity(_entityId); - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - bytes4 key = yesKey(_countryCode, _yes); - - YesMark storage mark = e.yesMarkByKey[key]; - if(mark.yes == 0) - return; // not set by anyone, bail happily - - if(mark.validatorEntityIdIdx[callerEntityId] == 0 && - (mark.validatorEntityIds.length == 0 || mark.validatorEntityIds[0] != callerEntityId)) { - // set, but not by this validator, bail happily - return; - } - - clearYes_I(mark, e, callerEntityId); - } - - function clearYes(uint256 _entityId, uint16 _countryCode) external permission_validator { - // special check against 129 validator mark - if(_countryCode == 0) - require(senderIsEcosystemControl(), 'not authorized as ecosystem control (129)'); // this is duplicating some things, gas leak - - EntityRecord storage e = entity(_entityId); - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - uint256 i; - - for(i =0; i idx) - mark.validatorEntityIdIdx[mark.validatorEntityIds[idx]] = idx; - - // check if the entire mark needs deleting - if(mark.validatorEntityIds.length == 0) { - // yes, it does. swap/delete - idx = mark.yesMarkIdx; - e.yesMarkKeys[idx] = e.yesMarkKeys[e.yesMarkKeys.length - 1]; - e.yesMarkKeys.length--; - - // remap - if(e.yesMarkKeys.length > idx) - e.yesMarkByKey[e.yesMarkKeys[idx]].yesMarkIdx = idx; - - // delete mark - mark.countryCode = 0; - mark.yes = 0; - mark.yesMarkIdx = 0; - // assert(mark.validatorEntityIds.length == 0); - - return true; - } - - return false; - } - - function clearYes_I(uint256 _entityId) internal { - require(_entityId != OWNER_ENTITY_ID); - - EntityRecord storage e = entity(_entityId); - - // only ecosystem control can touch validators - if(!senderIsEcosystemControl()) - require(e.yesMarkByKey[yesKey(0, YESMARK_VALIDATOR)].yes == 0); - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - uint256 i; - - for(i =0; i 0 - || m.validatorEntityIds.length > 0 && m.validatorEntityIds[0] == _validatorEntityId; - } - - function setYes_I(uint256 _entityId, uint16 _countryCode, uint8 _yes) internal { - require(_yes > 0); - require(_yes != 128); - - // special check against 129 validator mark - if(_yes == 129) - require(senderIsEcosystemControl()); // this is duplicating some checks, gas leak - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - - setYes_I(callerEntityId, _entityId, _countryCode, _yes); - } - - function setYes_I(uint256 _validatorEntityId, uint256 _entityId, uint16 _countryCode, uint8 _yes) internal { - // assert(_yes > 0); - EntityRecord storage targetEntity = entity(_entityId); - - // locate existing mark - bytes4 key = yesKey(_countryCode, _yes); - YesMark storage mark = targetEntity.yesMarkByKey[key]; - - if(mark.yes == 0) { - require(targetEntity.yesMarkKeys.length < TOTAL_YES_MARKS); - - // new mark on the entity - mark.countryCode = _countryCode; - mark.yes = _yes; - mark.yesMarkIdx = uint32(targetEntity.yesMarkKeys.length); - targetEntity.yesMarkKeys.push(key); - - } else if(mark.validatorEntityIdIdx[_validatorEntityId] > 0 || - (mark.validatorEntityIds.length > 0 && mark.validatorEntityIds[0] == _validatorEntityId)) { - - // existing mark and the caller is already on it - /* - i'm inclined to make it do nothing in this case (instead of failing) since i'm not at this point positive how best - to distinguish error types to a caller, which would be required for a caller to know wtf to do in this case - (otherwise they need to query blockchain again) - (but that costs gas... :notsureif:) - */ - return; - } - - require(mark.validatorEntityIds.length < MAX_VALIDATORS_PER_MARK); - - // add this validator to the mark - mark.validatorEntityIdIdx[_validatorEntityId] = uint32(mark.validatorEntityIds.length); - mark.validatorEntityIds.push(_validatorEntityId); - } - - /** non-permissed internal minting impl */ - function mint_I(address _to, uint256 _entityId, bool _control) internal returns (uint256) { - EntityRecord storage e = entity(_entityId); - require(e.tokenIds.length < MAX_TOKENS_PER_ENTITY, 'token limit reached'); - require(e.tokenIdCounter < 2**64-1); // kind of ridiculous but whatever, safety first! - uint256 tokenId = uint256(keccak256(abi.encodePacked(_entityId, e.tokenIdCounter++))); - super._mint(_to, tokenId); - tokenRecordById[tokenId].tokenIdIdx = uint32(e.tokenIds.length); - tokenRecordById[tokenId].control = _control; - e.tokenIds.push(tokenId); - entityIdByTokenId[tokenId] = _entityId; - return tokenId; - } - - /** entity resolution (creation when needed) */ - function entity(uint256 _entityId) internal returns (EntityRecord storage) { - require(_entityId > 0); - EntityRecord storage e = entityRecordById[_entityId]; - if(e.init) return e; - require(entityIds.length < MAX_ENTITIES); - e.init = true; - e.entityIdIdx = uint32(entityIds.length); - entityIds.push(_entityId); - return e; - } - - /** override default addTokenTo for additional transaction limitations */ - function addTokenTo(address _to, uint256 _tokenId) internal { - uint256 entityId = entityIdByTokenId[_tokenId]; - - // ensure one owner cannot be associated with multiple entities - // NOTE: this breaks hotwallet integrations, at this point necessarily so - if(balanceOf(_to) > 0) { - uint256 prevEntityId = entityIdByTokenId[tokenOfOwnerByIndex(_to, 0)]; - require(prevEntityId == entityId, 'conflicting entities'); - } - - require(!tokenRecordById[_tokenId].finalized, 'token is finalized'); - - super.addTokenTo(_to, _tokenId); - } - - /** the sender is the same entity as the one specified */ - function senderIsEntity_ByEntityId(uint256 _entityId) internal view returns (bool) { - return _entityId == entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; - } - - /** the sender is the same entity as the one specified, and the sender is a control for that entity */ - function senderIsControl_ByEntityId(uint256 _entityId) internal view returns (bool) { - if(balanceOf(msg.sender) == 0) - return false; - uint256 tokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 senderEntityId = entityIdByTokenId[tokenId]; - return _entityId == senderEntityId && tokenRecordById[tokenId].control; - } - - /** the sender is a non-locked validator via control token */ - function senderIsControlValidator() internal view returns (bool) { - if(balanceOf(msg.sender) == 0) - return false; - uint256 tokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 senderEntityId = entityIdByTokenId[tokenId]; - EntityRecord storage e = entityRecordById[senderEntityId]; - return tokenRecordById[tokenId].control - && !e.locked - && entityRecordById[senderEntityId].yesMarkByKey[yesKey(0, YESMARK_VALIDATOR)].yes > 0; - } - - /** the sender is the same entity as the one tied to the token specified */ - function senderIsEntity_ByTokenId(uint256 _tokenId) internal view returns (bool) { - if(balanceOf(msg.sender) == 0) - return false; - return entityIdByTokenId[_tokenId] == entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; - } - - /** the sender is the same entity as the one tied to the token specified, and the sender is a control for that entity */ - function senderIsControl_ByTokenId(uint256 _tokenId) internal view returns (bool) { - if(balanceOf(msg.sender) == 0) - return false; - uint256 senderEntityId = entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; - return entityIdByTokenId[_tokenId] == senderEntityId && tokenRecordById[_tokenId].control; - } - - /** checks if sender is the singular ecosystem owner */ - function senderIsEcosystemControl() internal view returns (bool) { - // todo deprecate ownerAddress ?! - return msg.sender == ownerAddress || senderIsControl_ByEntityId(OWNER_ENTITY_ID); - } - - /** a key for a YES attestation mark */ - function yesKey(uint16 _countryCode, uint8 _yes) internal pure returns(bytes4) { - return bytes4(keccak256(abi.encodePacked(_countryCode, _yes))); - } - - // PERMISSIONS MODIFIERS ---------------------------------------------------------------- - - modifier permission_validator { - require(senderIsControlValidator(), 'not authorized as validator'); - _; - } - - modifier permission_super { - require(senderIsEcosystemControl(), 'not authorized as ecosystem control'); - _; - } - -// modifier permission_access_entityId(uint256 _entityId) { -// require(senderIsEcosystemControl() || senderIsEntity_ByEntityId(_entityId)); -// _; -// } - - modifier permission_control_entityId(uint256 _entityId) { - require(senderIsEcosystemControl() || senderIsControl_ByEntityId(_entityId), 'not authorized entity controller'); - _; - } - - modifier permission_access_tokenId(uint256 _tokenId) { - require(senderIsEcosystemControl() || senderIsEntity_ByTokenId(_tokenId)); - _; - } - - modifier permission_control_tokenId(uint256 _tokenId) { - require(senderIsEcosystemControl() || senderIsControl_ByTokenId(_tokenId), 'not authorized token controller'); - _; - } - -} \ No newline at end of file diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index d0e902eda..8e78ed992 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -11,7 +11,6 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; -import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; import { artifacts } from '../../src/artifacts'; import { @@ -112,6 +111,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ] = accounts); // Create wrappers erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + let compliantAddresses = _.cloneDeepWith(usedAddresses); + _.remove(compliantAddresses, (address: string) => { + return address === nonCompliantAddress; + }); + const erc721Wrapper = new ERC721Wrapper(provider, compliantAddresses, owner); // Deploy ERC20 tokens const numDummyErc20ToDeploy = 3; let erc20TokenA: DummyERC20TokenContract; @@ -123,12 +127,6 @@ describe.only(ContractName.BalanceThresholdFilter, () => { defaultMakerAssetAddress = erc20TokenA.address; defaultTakerAssetAddress = erc20TokenB.address; zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Deploy Yes Token - const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( - artifacts.YesComplianceToken, - provider, - txDefaults, - ); // Create proxies const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); @@ -146,14 +144,18 @@ describe.only(ContractName.BalanceThresholdFilter, () => { from: owner, }); // Deploy Compliant Forwarder - const erc721BalanceThreshold = new BigNumber(1); + const balanceThreshold = new BigNumber(1); + await erc721Wrapper.deployProxyAsync(); + const [balanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync(); + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const balance = await balanceThresholdAsset.balanceOf.callAsync(compliantTakerAddress); compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( artifacts.BalanceThresholdFilter, provider, txDefaults, exchangeInstance.address, - yesTokenInstance.address, - erc721BalanceThreshold + balanceThresholdAsset.address, + balanceThreshold ); // Default order parameters const defaultOrderParams = { @@ -199,43 +201,6 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ); forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ - // Initialize Yes Token - await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({ from: owner }); - const yesTokenName = 'YesToken'; - const yesTokenTicker = 'YEET'; - await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); - // Verify Maker / Taker - const addressesCanControlTheirToken = true; - const compliantMakerCountryCode = new BigNumber(519); - const compliantMakerYesMark = new BigNumber(1); - const compliantMakerEntityId = new BigNumber(2); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantMakerAddress, - compliantMakerEntityId, - addressesCanControlTheirToken, - compliantMakerCountryCode, - [compliantMakerYesMark], - { from: owner }, - ); - const compliantTakerCountryCode = new BigNumber(519); - const compliantTakerYesMark = new BigNumber(1); - const compliantTakerEntityId = new BigNumber(2); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantTakerAddress, - compliantTakerEntityId, - addressesCanControlTheirToken, - compliantTakerCountryCode, - [compliantTakerYesMark], - { from: owner }, - ); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantMakerAddress2, - compliantTakerEntityId, - addressesCanControlTheirToken, - compliantTakerCountryCode, - [compliantTakerYesMark], - { from: owner }, - ); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); -- cgit v1.2.3 From 6d673ac942fc1d0df7f8eeb56168ba3a4029878e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 10 Dec 2018 17:28:20 -0800 Subject: Exchange Selectors - sorted --- .../utils/ExchangeSelectors/ExchangeSelectors.sol | 203 ++++++++++----------- 1 file changed, 96 insertions(+), 107 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol index d4c9bef28..c361fd075 100644 --- a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol +++ b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol @@ -20,143 +20,132 @@ pragma solidity 0.4.24; contract ExchangeSelectors { - // filled - bytes4 constant filledSelector = 0x288cdc91; - bytes4 constant filledSelectorGenerator = bytes4(keccak256('filled(bytes32)')); - // batchFillOrders - bytes4 constant batchFillOrdersSelector = 0x297bb70b; - bytes4 constant batchFillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + // allowedValidators + bytes4 constant allowedValidatorsSelector = 0x7b8e3514; + bytes4 constant allowedValidatorsSelectorGenerator = bytes4(keccak256('allowedValidators(address,address)')); - // cancelled - bytes4 constant cancelledSelector = 0x2ac12622; - bytes4 constant cancelledSelectorGenerator = bytes4(keccak256('cancelled(bytes32)')); + // assetProxies + bytes4 constant assetProxiesSelector = 0x3fd3c997; + bytes4 constant assetProxiesSelectorGenerator = bytes4(keccak256('assetProxies(bytes4)')); - // preSign - bytes4 constant preSignSelector = 0x3683ef8e; - bytes4 constant preSignSelectorGenerator = bytes4(keccak256('preSign(bytes32,address,bytes)')); + // batchCancelOrders + bytes4 constant batchCancelOrdersSelector = 0x4ac14782; + bytes4 constant batchCancelOrdersSelectorGenerator = bytes4(keccak256('batchCancelOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); - // matchOrders - bytes4 constant matchOrdersSelector = 0x3c28d861; - bytes4 constant matchOrdersSelectorGenerator = bytes4(keccak256('matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)')); + // batchFillOrKillOrders + bytes4 constant batchFillOrKillOrdersSelector = 0x4d0ae546; + bytes4 constant batchFillOrKillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrKillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - // fillOrderNoThrow - bytes4 constant fillOrderNoThrowSelector = 0x3e228bae; - bytes4 constant fillOrderNoThrowSelectorGenerator = bytes4(keccak256('fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + // batchFillOrders + bytes4 constant batchFillOrdersSelector = 0x297bb70b; + bytes4 constant batchFillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - // assetProxies - bytes4 constant assetProxiesSelector = 0x3fd3c997; - bytes4 constant assetProxiesSelectorGenerator = bytes4(keccak256('assetProxies(bytes4)')); + // batchFillOrdersNoThrow + bytes4 constant batchFillOrdersNoThrowSelector = 0x50dde190; + bytes4 constant batchFillOrdersNoThrowSelectorGenerator = bytes4(keccak256('batchFillOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - // batchCancelOrders - bytes4 constant batchCancelOrdersSelector = 0x4ac14782; - bytes4 constant batchCancelOrdersSelectorGenerator = bytes4(keccak256('batchCancelOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); + // cancelOrder + bytes4 constant cancelOrderSelector = 0xd46b02c3; + bytes4 constant cancelOrderSelectorGenerator = bytes4(keccak256('cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); - // batchFillOrKillOrders - bytes4 constant batchFillOrKillOrdersSelector = 0x4d0ae546; - bytes4 constant batchFillOrKillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrKillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + // cancelOrdersUpTo + bytes4 constant cancelOrdersUpToSelector = 0x4f9559b1; + bytes4 constant cancelOrdersUpToSelectorGenerator = bytes4(keccak256('cancelOrdersUpTo(uint256)')); - // cancelOrdersUpTo - bytes4 constant cancelOrdersUpToSelector = 0x4f9559b1; - bytes4 constant cancelOrdersUpToSelectorGenerator = bytes4(keccak256('cancelOrdersUpTo(uint256)')); + // cancelled + bytes4 constant cancelledSelector = 0x2ac12622; + bytes4 constant cancelledSelectorGenerator = bytes4(keccak256('cancelled(bytes32)')); - // batchFillOrdersNoThrow - bytes4 constant batchFillOrdersNoThrowSelector = 0x50dde190; - bytes4 constant batchFillOrdersNoThrowSelectorGenerator = bytes4(keccak256('batchFillOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + // currentContextAddress + bytes4 constant currentContextAddressSelector = 0xeea086ba; + bytes4 constant currentContextAddressSelectorGenerator = bytes4(keccak256('currentContextAddress()')); - // getAssetProxy - bytes4 constant getAssetProxySelector = 0x60704108; - bytes4 constant getAssetProxySelectorGenerator = bytes4(keccak256('getAssetProxy(bytes4)')); + // executeTransaction + bytes4 constant executeTransactionSelector = 0xbfc8bfce; + bytes4 constant executeTransactionSelectorGenerator = bytes4(keccak256('executeTransaction(uint256,address,bytes,bytes)')); - // transactions - bytes4 constant transactionsSelector = 0x642f2eaf; - bytes4 constant transactionsSelectorGenerator = bytes4(keccak256('transactions(bytes32)')); + // fillOrKillOrder + bytes4 constant fillOrKillOrderSelector = 0x64a3bc15; + bytes4 constant fillOrKillOrderSelectorGenerator = bytes4(keccak256('fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - // fillOrKillOrder - bytes4 constant fillOrKillOrderSelector = 0x64a3bc15; - bytes4 constant fillOrKillOrderSelectorGenerator = bytes4(keccak256('fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + // fillOrder + bytes4 constant fillOrderSelector = 0xb4be83d5; + bytes4 constant fillOrderSelectorGenerator = bytes4(keccak256('fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - // setSignatureValidatorApproval - bytes4 constant setSignatureValidatorApprovalSelector = 0x77fcce68; - bytes4 constant setSignatureValidatorApprovalSelectorGenerator = bytes4(keccak256('setSignatureValidatorApproval(address,bool)')); + // fillOrderNoThrow + bytes4 constant fillOrderNoThrowSelector = 0x3e228bae; + bytes4 constant fillOrderNoThrowSelectorGenerator = bytes4(keccak256('fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - // allowedValidators - bytes4 constant allowedValidatorsSelector = 0x7b8e3514; - bytes4 constant allowedValidatorsSelectorGenerator = bytes4(keccak256('allowedValidators(address,address)')); + // filled + bytes4 constant filledSelector = 0x288cdc91; + bytes4 constant filledSelectorGenerator = bytes4(keccak256('filled(bytes32)')); - // marketSellOrders - bytes4 constant marketSellOrdersSelector = 0x7e1d9808; - bytes4 constant marketSellOrdersSelectorGenerator = bytes4(keccak256('marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + // getAssetProxy + bytes4 constant getAssetProxySelector = 0x60704108; + bytes4 constant getAssetProxySelectorGenerator = bytes4(keccak256('getAssetProxy(bytes4)')); - // getOrdersInfo - bytes4 constant getOrdersInfoSelector = 0x7e9d74dc; - bytes4 constant getOrdersInfoSelectorGenerator = bytes4(keccak256('getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); + // getOrderInfo + bytes4 constant getOrderInfoSelector = 0xc75e0a81; + bytes4 constant getOrderInfoSelectorGenerator = bytes4(keccak256('getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); - // preSigned - bytes4 constant preSignedSelector = 0x82c174d0; - bytes4 constant preSignedSelectorGenerator = bytes4(keccak256('preSigned(bytes32,address)')); + // getOrdersInfo + bytes4 constant getOrdersInfoSelector = 0x7e9d74dc; + bytes4 constant getOrdersInfoSelectorGenerator = bytes4(keccak256('getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); - // owner - bytes4 constant ownerSelector = 0x8da5cb5b; - bytes4 constant ownerSelectorGenerator = bytes4(keccak256('owner()')); + // isValidSignature + bytes4 constant isValidSignatureSelector = 0x93634702; + bytes4 constant isValidSignatureSelectorGenerator = bytes4(keccak256('isValidSignature(bytes32,address,bytes)')); - // isValidSignature - bytes4 constant isValidSignatureSelector = 0x93634702; - bytes4 constant isValidSignatureSelectorGenerator = bytes4(keccak256('isValidSignature(bytes32,address,bytes)')); + // marketBuyOrders + bytes4 constant marketBuyOrdersSelector = 0xe5fa431b; + bytes4 constant marketBuyOrdersSelectorGenerator = bytes4(keccak256('marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - // marketBuyOrdersNoThrow - bytes4 constant marketBuyOrdersNoThrowSelector = 0xa3e20380; - bytes4 constant marketBuyOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketBuyOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + // marketBuyOrdersNoThrow + bytes4 constant marketBuyOrdersNoThrowSelector = 0xa3e20380; + bytes4 constant marketBuyOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketBuyOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - // fillOrder - bytes4 constant fillOrderSelector = 0xb4be83d5; - bytes4 constant fillOrderSelectorGenerator = bytes4(keccak256('fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + // marketSellOrders + bytes4 constant marketSellOrdersSelector = 0x7e1d9808; + bytes4 constant marketSellOrdersSelectorGenerator = bytes4(keccak256('marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - // executeTransaction - bytes4 constant executeTransactionSelector = 0xbfc8bfce; - bytes4 constant executeTransactionSelectorGenerator = bytes4(keccak256('executeTransaction(uint256,address,bytes,bytes)')); + // marketSellOrdersNoThrow + bytes4 constant marketSellOrdersNoThrowSelector = 0xdd1c7d18; + bytes4 constant marketSellOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - // registerAssetProxy - bytes4 constant registerAssetProxySelector = 0xc585bb93; - bytes4 constant registerAssetProxySelectorGenerator = bytes4(keccak256('registerAssetProxy(address)')); + // matchOrders + bytes4 constant matchOrdersSelector = 0x3c28d861; + bytes4 constant matchOrdersSelectorGenerator = bytes4(keccak256('matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)')); - // getOrderInfo - bytes4 constant getOrderInfoSelector = 0xc75e0a81; - bytes4 constant getOrderInfoSelectorGenerator = bytes4(keccak256('getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); + // orderEpoch + bytes4 constant orderEpochSelector = 0xd9bfa73e; + bytes4 constant orderEpochSelectorGenerator = bytes4(keccak256('orderEpoch(address,address)')); - // cancelOrder - bytes4 constant cancelOrderSelector = 0xd46b02c3; - bytes4 constant cancelOrderSelectorGenerator = bytes4(keccak256('cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); + // owner + bytes4 constant ownerSelector = 0x8da5cb5b; + bytes4 constant ownerSelectorGenerator = bytes4(keccak256('owner()')); - // orderEpoch - bytes4 constant orderEpochSelector = 0xd9bfa73e; - bytes4 constant orderEpochSelectorGenerator = bytes4(keccak256('orderEpoch(address,address)')); + // preSign + bytes4 constant preSignSelector = 0x3683ef8e; + bytes4 constant preSignSelectorGenerator = bytes4(keccak256('preSign(bytes32,address,bytes)')); - // ZRX_ASSET_DATA - bytes4 constant ZRX_ASSET_DATASelector = 0xdb123b1a; - bytes4 constant ZRX_ASSET_DATASelectorGenerator = bytes4(keccak256('ZRX_ASSET_DATA()')); + // preSigned + bytes4 constant preSignedSelector = 0x82c174d0; + bytes4 constant preSignedSelectorGenerator = bytes4(keccak256('preSigned(bytes32,address)')); - // marketSellOrdersNoThrow - bytes4 constant marketSellOrdersNoThrowSelector = 0xdd1c7d18; - bytes4 constant marketSellOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + // registerAssetProxy + bytes4 constant registerAssetProxySelector = 0xc585bb93; + bytes4 constant registerAssetProxySelectorGenerator = bytes4(keccak256('registerAssetProxy(address)')); - // EIP712_DOMAIN_HASH - bytes4 constant EIP712_DOMAIN_HASHSelector = 0xe306f779; - bytes4 constant EIP712_DOMAIN_HASHSelectorGenerator = bytes4(keccak256('EIP712_DOMAIN_HASH()')); + // setSignatureValidatorApproval + bytes4 constant setSignatureValidatorApprovalSelector = 0x77fcce68; + bytes4 constant setSignatureValidatorApprovalSelectorGenerator = bytes4(keccak256('setSignatureValidatorApproval(address,bool)')); - // marketBuyOrders - bytes4 constant marketBuyOrdersSelector = 0xe5fa431b; - bytes4 constant marketBuyOrdersSelectorGenerator = bytes4(keccak256('marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + // transactions + bytes4 constant transactionsSelector = 0x642f2eaf; + bytes4 constant transactionsSelectorGenerator = bytes4(keccak256('transactions(bytes32)')); - // currentContextAddress - bytes4 constant currentContextAddressSelector = 0xeea086ba; - bytes4 constant currentContextAddressSelectorGenerator = bytes4(keccak256('currentContextAddress()')); - - // transferOwnership - bytes4 constant transferOwnershipSelector = 0xf2fde38b; - bytes4 constant transferOwnershipSelectorGenerator = bytes4(keccak256('transferOwnership(address)')); - - // VERSION - bytes4 constant VERSIONSelector = 0xffa1ad74; - bytes4 constant VERSIONSelectorGenerator = bytes4(keccak256('VERSION()')); + // transferOwnership + bytes4 constant transferOwnershipSelector = 0xf2fde38b; + bytes4 constant transferOwnershipSelectorGenerator = bytes4(keccak256('transferOwnership(address)')); } \ No newline at end of file -- cgit v1.2.3 From 8799f9bb90c6bcfffc74550d74ed87422467f9e1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 10 Dec 2018 18:25:19 -0800 Subject: Test for ERC20 balance threshold --- .../test/extensions/balance_threshold_filter.ts | 260 ++++++++++++--------- 1 file changed, 146 insertions(+), 114 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 8e78ed992..c4723e7aa 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -1,6 +1,6 @@ import { BlockchainLifecycle } from '@0x/dev-utils'; import { assetDataUtils } from '@0x/order-utils'; -import { RevertReason, SignedOrder } from '@0x/types'; +import { RevertReason, SignedOrder, Order } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; @@ -64,9 +64,10 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let nonCompliantOrderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; - let takerBalanceThresholdWrapper: BalanceThresholdWrapper; - let makerBalanceThresholdWrapper: BalanceThresholdWrapper; - let nonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; + let erc20TakerBalanceThresholdWrapper: BalanceThresholdWrapper; + let erc721TakerBalanceThresholdWrapper: BalanceThresholdWrapper; + let erc721MakerBalanceThresholdWrapper: BalanceThresholdWrapper; + let erc721NonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; let takerTransactionFactory: TransactionFactory; let makerTransactionFactory: TransactionFactory; @@ -77,11 +78,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let logDecoder: LogDecoder; let exchangeInternals: TestExchangeInternalsContract; + let defaultOrderParams: Partial; + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); - let compliantForwarderInstance: BalanceThresholdFilterContract; + let erc721CompliantForwarderInstance: BalanceThresholdFilterContract; + let erc20CompliantForwarderInstance: BalanceThresholdFilterContract; const assertValidatedAddressesLog = async (txReceipt: TransactionReceiptWithDecodedLogs, expectedValidatedAddresses: string[]) => { expect(txReceipt.logs.length).to.be.gte(1); @@ -117,10 +121,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); const erc721Wrapper = new ERC721Wrapper(provider, compliantAddresses, owner); // Deploy ERC20 tokens - const numDummyErc20ToDeploy = 3; + const numDummyErc20ToDeploy = 4; let erc20TokenA: DummyERC20TokenContract; let erc20TokenB: DummyERC20TokenContract; - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + let erc20BalanceThresholdAsset: DummyERC20TokenContract; + [erc20TokenA, erc20TokenB, zrxToken, erc20BalanceThresholdAsset] = await erc20Wrapper.deployDummyTokensAsync( numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS, ); @@ -144,21 +149,30 @@ describe.only(ContractName.BalanceThresholdFilter, () => { from: owner, }); // Deploy Compliant Forwarder - const balanceThreshold = new BigNumber(1); + const erc721alanceThreshold = new BigNumber(1); await erc721Wrapper.deployProxyAsync(); - const [balanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync(); + const [erc721BalanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync(); await erc721Wrapper.setBalancesAndAllowancesAsync(); - const balance = await balanceThresholdAsset.balanceOf.callAsync(compliantTakerAddress); - compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( + erc721CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( + artifacts.BalanceThresholdFilter, + provider, + txDefaults, + exchangeInstance.address, + erc721BalanceThresholdAsset.address, + erc721alanceThreshold + ); + const erc20BalanceThreshold = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 10); + erc20CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( artifacts.BalanceThresholdFilter, provider, txDefaults, exchangeInstance.address, - balanceThresholdAsset.address, - balanceThreshold + erc20BalanceThresholdAsset.address, + erc20BalanceThreshold ); + // Default order parameters - const defaultOrderParams = { + defaultOrderParams = { exchangeAddress: exchangeInstance.address, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), @@ -167,7 +181,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { takerAssetAmount, makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, }; const defaultOrderParams1 = { makerAddress: compliantMakerAddress, @@ -192,20 +206,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { defaultOrderParams, }; nonCompliantOrderFactory = new OrderFactory(nonCompliantPrivateKey, defaultNonCompliantOrderParams); - - /* - const compliantForwarderContract = new BalanceThresholdFilterContract( - compliantForwarderInstance.abi, - compliantForwarderInstance.address, - provider, - ); - forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); - */ // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); compliantSignedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, }); const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( compliantSignedOrder, @@ -219,35 +224,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { compliantSignedOrderWithoutExchangeAddressData, ); - /* - _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { - try { - const method = new Method(abiDefinition as MethodAbi); - console.log(method.getSignature()); - if (!method.getSignature().startsWith('matchOrders')) { - return; - } - console.log(`FOUND IT`); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - compliantSignedOrder, - ); - const args = [signedOrderWithoutExchangeAddress, signedOrderWithoutExchangeAddress, compliantSignedOrder.signature, compliantSignedOrder.signature]; - console.log(method.encode(args, {annotate: true})); - //console.log('\n', `// ${method.getDataItem().name}`); - //console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); - //console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); - } catch(e) { - console.log(`encoding failed: ${e}`); - } - }); - throw new Error(`w`);*/ logDecoder = new LogDecoder(web3Wrapper); - takerBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); - makerBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(makerPrivateKey, exchangeInstance.address), provider); + erc20TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc20CompliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); + erc721TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); + erc721MakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(makerPrivateKey, exchangeInstance.address), provider); + erc721NonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); - nonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); - - // Instantiate internal exchange contract exchangeInternals = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( artifacts.TestExchangeInternals, @@ -262,7 +244,57 @@ describe.only(ContractName.BalanceThresholdFilter, () => { await blockchainLifecycle.revertAsync(); }); - describe('General Sanity Checks', () => { + describe.only('General Sanity Checks', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it.only('should transfer the correct amounts and validate both maker/taker when both maker and taker exceed the balance threshold of an ERC20 token', async () => { + const compliantSignedOrderERC20Sender = await orderFactory.newSignedOrderAsync({ + ... + defaultOrderParams, + makerAddress: compliantMakerAddress, + senderAddress: erc20TakerBalanceThresholdWrapper.getBalanceThresholdAddress(), + }); + // Execute a valid fill + const txReceipt = await erc20TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrderERC20Sender, compliantTakerAddress, {takerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); it('should revert if the signed transaction is not intended for supported', async () => { // Create signed order without the fillOrder function selector const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); @@ -273,7 +305,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, txDataBufWithBadSelectorHex, @@ -298,7 +330,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { signedOrderWithoutExchangeAddressData, ); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync( signedFillOrderTx.salt, signedFillOrderTx.signerAddress, signedFillOrderTx.data, @@ -318,7 +350,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await takerBalanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -380,7 +412,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.batchFillOrdersAsync( + erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -392,7 +424,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.batchFillOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersAsync( orders, nonCompliantAddress, {takerAssetFillAmounts} @@ -412,7 +444,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await takerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -474,7 +506,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( + erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -486,7 +518,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( + erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( orders, nonCompliantAddress, {takerAssetFillAmounts} @@ -506,7 +538,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -568,7 +600,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( + erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -580,7 +612,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, nonCompliantAddress, {takerAssetFillAmounts} @@ -593,7 +625,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount]; return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( + erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -610,7 +642,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await takerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -650,12 +682,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.fillOrderAsync( + erc721TakerBalanceThresholdWrapper.fillOrderAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -665,7 +697,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrderAsync( + erc721NonCompliantBalanceThresholdWrapper.fillOrderAsync( compliantSignedOrder, nonCompliantAddress, {takerAssetFillAmount} @@ -682,7 +714,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await takerBalanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -722,12 +754,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.fillOrderNoThrowAsync( + erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -737,7 +769,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( + erc721NonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( compliantSignedOrder, nonCompliantAddress, {takerAssetFillAmount} @@ -755,7 +787,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; - const txReceipt = await takerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -795,12 +827,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.fillOrKillOrderAsync( + erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -810,7 +842,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( + erc721NonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( compliantSignedOrder, nonCompliantAddress, {takerAssetFillAmount} @@ -821,7 +853,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if takerAssetFillAmount is not fully filled', async () => { const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.fillOrKillOrderAsync( + erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: tooBigTakerAssetFillAmount} @@ -841,7 +873,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await takerBalanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -901,7 +933,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.marketSellOrdersAsync( + erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmount} @@ -912,7 +944,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if taker does not meet the balance threshold', async () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.marketSellOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersAsync( orders, nonCompliantAddress, {takerAssetFillAmount} @@ -932,7 +964,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await takerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -992,7 +1024,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( + erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( orders, compliantTakerAddress, {takerAssetFillAmount} @@ -1003,7 +1035,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if taker does not meet the balance threshold', async () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( + erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( orders, nonCompliantAddress, {takerAssetFillAmount} @@ -1027,7 +1059,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await takerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1084,7 +1116,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.marketBuyOrdersAsync( + erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync( orders, compliantTakerAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1096,7 +1128,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync( orders, nonCompliantAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1120,7 +1152,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await takerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1177,7 +1209,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( orders, compliantTakerAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1189,7 +1221,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( orders, nonCompliantAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1240,7 +1272,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% }; - const txReceipt = await takerBalanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); + const txReceipt = await erc721TakerBalanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); // Assert validated addresses const expectedValidatedAddresseses = [signedOrderLeft.makerAddress, signedOrderRight.makerAddress, compliantTakerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1292,12 +1324,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if left maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.matchOrdersAsync( + erc721TakerBalanceThresholdWrapper.matchOrdersAsync( compliantSignedOrder, signedOrderWithBadMakerAddress, compliantTakerAddress, @@ -1308,12 +1340,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if right maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.matchOrdersAsync( + erc721TakerBalanceThresholdWrapper.matchOrdersAsync( signedOrderWithBadMakerAddress, compliantSignedOrder, compliantTakerAddress, @@ -1323,7 +1355,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.matchOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.matchOrdersAsync( compliantSignedOrder, compliantSignedOrder, nonCompliantAddress, @@ -1341,30 +1373,30 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('Should successfully cancel order if maker meets balance threshold', async () => { // Verify order is not cancelled - const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); // Cancel - const txReceipt = await makerBalanceThresholdWrapper.cancelOrderAsync(compliantSignedOrder, compliantSignedOrder.makerAddress); + const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrderAsync(compliantSignedOrder, compliantSignedOrder.makerAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); it('Should successfully cancel order if maker does not meet balance threshold', async () => { // Create order where maker does not meet balance threshold const signedOrderWithBadMakerAddress = await nonCompliantOrderFactory.newSignedOrderAsync({}); // Verify order is not cancelled - const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) + const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); // Cancel - const txReceipt = await nonCompliantBalanceThresholdWrapper.cancelOrderAsync(signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress); + const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrderAsync(signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); }); @@ -1382,17 +1414,17 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ]; // Verify orders are not cancelled await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel - const txReceipt = await makerBalanceThresholdWrapper.batchCancelOrdersAsync(compliantSignedOrders, compliantSignedOrder.makerAddress); + const txReceipt = await erc721MakerBalanceThresholdWrapper.batchCancelOrdersAsync(compliantSignedOrders, compliantSignedOrder.makerAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); }); @@ -1405,17 +1437,17 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ]; // Verify orders are not cancelled await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel - const txReceipt = await nonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync(nonCompliantSignedOrders, nonCompliantAddress); + const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync(nonCompliantSignedOrders, nonCompliantAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoAfterCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); }); @@ -1434,18 +1466,18 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ]; // Verify orders are not cancelled await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await makerBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, compliantSignedOrder.makerAddress); + const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, compliantSignedOrder.makerAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled await _.each(compliantSignedOrders, async (compliantSignedOrder, salt: number) => { - const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) const saltAsBigNumber = new BigNumber(salt); if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); @@ -1463,18 +1495,18 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ]; // Verify orders are not cancelled await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await nonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, nonCompliantAddress); + const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, nonCompliantAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder, salt: number) => { - const orderInfoAfterCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) const saltAsBigNumber = new BigNumber(salt); if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); -- cgit v1.2.3 From 4417c76b130655da3b14209148191d43037e8f8a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Dec 2018 14:47:52 -0800 Subject: Ported Balance Threshold Filter to new contract directory structure --- .../BalanceThresholdFilter.sol | 44 - .../MixinBalanceThresholdFilterCore.sol | 319 ---- .../interfaces/IThresholdAsset.sol | 30 - .../mixins/MBalanceThresholdFilterCore.sol | 81 -- .../test/extensions/balance_threshold_filter.ts | 1521 -------------------- .../test/utils/balance_threshold_wrapper.ts | 247 ---- 6 files changed, 2242 deletions(-) delete mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol delete mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol delete mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol delete mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol delete mode 100644 packages/contracts/test/extensions/balance_threshold_filter.ts delete mode 100644 packages/contracts/test/utils/balance_threshold_wrapper.ts (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol deleted file mode 100644 index bf4a94509..000000000 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol +++ /dev/null @@ -1,44 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; - -import "../../protocol/Exchange/interfaces/IExchange.sol"; -import "./interfaces/IThresholdAsset.sol"; -import "./MixinBalanceThresholdFilterCore.sol"; - - -contract BalanceThresholdFilter is MixinBalanceThresholdFilterCore { - - /// @dev Constructs BalanceThresholdFilter. - /// @param exchange Address of 0x exchange. - /// @param thresholdAsset The asset that must be held by makers/takers. - /// @param thresholdBalance The minimum balance of `thresholdAsset` that must be held by makers/takers. - constructor( - address exchange, - address thresholdAsset, - uint256 thresholdBalance - ) - public - { - EXCHANGE = IExchange(exchange); - THRESHOLD_ASSET = IThresholdAsset(thresholdAsset); - THRESHOLD_BALANCE = thresholdBalance; - } -} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol deleted file mode 100644 index 0ad8ccddf..000000000 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ /dev/null @@ -1,319 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; - -import "./mixins/MBalanceThresholdFilterCore.sol"; - - -contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { - - /// @dev Executes an Exchange transaction iff the maker and taker meet - /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR - /// the exchange function is a cancellation. - /// Supported Exchange functions: - /// - batchFillOrders - /// - batchFillOrdersNoThrow - /// - batchFillOrKillOrders - /// - fillOrder - /// - fillOrderNoThrow - /// - fillOrKillOrder - /// - marketBuyOrders - /// - marketBuyOrdersNoThrow - /// - marketSellOrders - /// - marketSellOrdersNoThrow - /// - matchOrders - /// - cancelOrder - /// - batchCancelOrders - /// - cancelOrdersUpTo - /// Trying to call any other exchange function will throw. - /// @param salt Arbitrary number to ensure uniqueness of transaction hash. - /// @param signerAddress Address of transaction signer. - /// @param signedExchangeTransaction AbiV2 encoded calldata. - /// @param signature Proof of signer transaction by signer. - function executeTransaction( - uint256 salt, - address signerAddress, - bytes signedExchangeTransaction, - bytes signature - ) - external - { - // Validate addresses. - validateBalanceThresholdsOrRevert(); - - // All addresses are valid. Execute fillOrder. - EXCHANGE.executeTransaction( - salt, - signerAddress, - signedExchangeTransaction, - signature - ); - } - - /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD` - /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold - /// then this function will revert. Which addresses are validated depends on - /// which Exchange function is to be called (defined by `signedExchangeTransaction` above). - /// No parameters are taken as this function reads arguments directly from calldata, to save gas. - /// If all addresses are valid then this function emits a ValidatedAddresses event, listing all - /// of the addresses whose balance thresholds it checked. - function validateBalanceThresholdsOrRevert() - internal - { - // Addresses that are validated below. - address[] memory validatedAddresses; - - ///// Do not add variables after this point. ///// - ///// The assembly block may overwrite their values. ///// - - // Validate addresses - assembly { - /// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata, - /// which is accessed through `signedExchangeTransaction`. - /// @param offset - Offset into the Exchange calldata. - /// @return value - Corresponding 32 byte value stored at `offset`. - function exchangeCalldataload(offset) -> value { - // Pointer to exchange transaction - // 0x04 for calldata selector - // 0x40 to access `signedExchangeTransaction`, which is the third parameter - let exchangeTxPtr := calldataload(0x44) - - // Offset into Exchange calldata - // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. - // 0x04 for calldata selector - // 0x20 for length field of `signedExchangeTransaction` - let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeCalldataOffset) - } - - /// @dev Convenience function that skips the 4 byte selector when loading - /// from the embedded Exchange calldata. - /// @param offset - Offset into the Exchange calldata (minus the 4 byte selector) - /// @return value - Corresponding 32 byte value stored at `offset` + 4. - function loadExchangeData(offset) -> value { - value := exchangeCalldataload(add(offset, 0x4)) - } - - /// @dev A running list is maintained of addresses to validate. - /// This function records an address in this array. - /// @param addressToValidate - Address to record for validation. - function recordAddressToValidate(addressToValidate) { - // Compute `addressesToValidate` memory offset - let addressesToValidate_ := mload(0x40) - let nAddressesToValidate_ := mload(addressesToValidate_) - - // Increment length - nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) - mstore(addressesToValidate_, nAddressesToValidate_) - - // Append address to validate - let offset := mul(nAddressesToValidate_, 0x20) - mstore(add(addressesToValidate_, offset), addressToValidate) - } - - /// @dev Extracts the maker address from an order stored in the Exchange calldata - /// (which is embedded in `signedExchangeTransaction`), and records it in - /// the running list of addresses to validate. - /// @param orderParamIndex - Index of the order in the Exchange function's signature - function recordMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(mul(orderParamIndex, 0x20)) - let makerAddress := loadExchangeData(orderPtr) - recordAddressToValidate(makerAddress) - } - - /// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata - /// (which is embedded in `signedExchangeTransaction`), and records them in - /// the running list of addresses to validate. - /// @param orderArrayParamIndex - Index of the order array in the Exchange function's signature - function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { - let orderArrayPtr := loadExchangeData(mul(orderArrayParamIndex, 0x20)) - let orderArrayLength := loadExchangeData(orderArrayPtr) - let orderArrayElementPtr := add(orderArrayPtr, 0x20) - let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) - for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { - let orderPtr := loadExchangeData(orderPtrOffset) - let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) - recordAddressToValidate(makerAddress) - } - } - - /// @dev Records address of signer in the running list of addresses to validate. - /// @note: We cannot access `signerAddress` directly from within the asm function, - /// so it is loaded from the calldata. - function recordSignerAddress() { - // Load the signer address from calldata - // 0x04 for selector - // 0x20 to access `signerAddress`, which is the second parameter. - let signerAddress_ := calldataload(0x24) - recordAddressToValidate(signerAddress_) - } - - /// @dev Records addresses to be validated when Exchange transaction is a batch fill variant. - /// This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow - /// Reference signature: (Order[],uint256[],bytes[]) - function recordAddressesForBatchFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /// @dev Records addresses to be validated when Exchange transaction is a fill order variant. - /// This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow - /// Reference signature: (Order,uint256,bytes) - function recordAddressesForFillOrderVariant() { - // Record maker address from the order (param index 0) - // The signer is the taker for this order and must also be validated. - recordMakerAddressFromOrder(0) - recordSignerAddress() - } - - /// @dev Records addresses to be validated when Exchange transaction is a market fill variant. - /// This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow - /// Reference signature: (Order[],uint256,bytes[]) - function recordAddressesForMarketFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /// @dev Records addresses to be validated when Exchange transaction is matchOrders. - /// Reference signature: matchOrders(Order,Order) - function recordAddressesForMatchOrders() { - // Record maker address from both orders (param indices 0 & 1). - // The signer is the taker and must also be validated. - recordMakerAddressFromOrder(0) - recordMakerAddressFromOrder(1) - recordSignerAddress() - } - - ///// Record Addresses to Validate ///// - - // Addresses needing validation depends on which Exchange function is being called. - // Step 1/2 Read the exchange function selector. - let exchangeFunctionSelector := and( - exchangeCalldataload(0x0), - 0xffffffff00000000000000000000000000000000000000000000000000000000 - ) - - // Step 2/2 Extract addresses to validate based on this selector. - // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors - switch exchangeFunctionSelector - case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders - case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow - case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder - case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow - case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder - case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders - case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow - case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders - case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow - case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders - case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder - case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders - case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo - default { - // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) - mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. - revert(0, 132) - } - - ///// Validate Recorded Addresses ///// - - // Load from memory the addresses to validate - let addressesToValidate := mload(0x40) - let addressesToValidateLength := mload(addressesToValidate) - let addressesToValidateElementPtr := add(addressesToValidate, 0x20) - let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) - - // Set free memory pointer to after `addressesToValidate` array. - // This is to avoid corruption when making calls in the loop below. - let freeMemPtr := addressesToValidateElementEndPtr - mstore(0x40, freeMemPtr) - - // Validate addresses - let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) - let thresholdBalance := sload(THRESHOLD_BALANCE_slot) - for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { - // Construct calldata for `THRESHOLD_ASSET.balanceOf` - mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(freeMemPtr, 0x04), mload(addressToValidate)) - - // call `THRESHOLD_ASSET.balanceOf` - let success := call( - gas, // forward all gas - thresholdAssetAddress, // call address of asset proxy - 0, // don't send any ETH - freeMemPtr, // pointer to start of input - 0x24, // length of input (one padded address) - freeMemPtr, // write output to next free memory offset - 0x20 // reserve space for return balance (0x20 bytes) - ) - if eq(success, 0) { - // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) - mstore(0x60, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. - revert(0, 100) - } - - // Revert if balance not held - let addressBalance := mload(freeMemPtr) - if lt(addressBalance, thresholdBalance) { - // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) - mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. - revert(0, 132) - } - } - - // Record validated addresses - validatedAddresses := addressesToValidate - } - - ///// If we hit this point then all addresses are valid ///// - emit ValidatedAddresses(validatedAddresses); - } -} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol deleted file mode 100644 index 61acaba0a..000000000 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol +++ /dev/null @@ -1,30 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - -contract IThresholdAsset { - - /// @param _owner The address from which the balance will be retrieved - /// @return Balance of owner - function balanceOf(address _owner) - external - view - returns (uint256); - -} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol deleted file mode 100644 index 612fd481c..000000000 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ /dev/null @@ -1,81 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; - -import "../../../protocol/Exchange/interfaces/IExchange.sol"; -import "../interfaces/IThresholdAsset.sol"; - - -contract MBalanceThresholdFilterCore { - - // Points to 0x exchange contract - IExchange internal EXCHANGE; - - // The asset that must be held by makers/takers - IThresholdAsset internal THRESHOLD_ASSET; - - // The minimum balance of `THRESHOLD_ASSET` that must be held by makers/takers - uint256 internal THRESHOLD_BALANCE; - - // Addresses that hold at least `THRESHOLD_BALANCE` of `THRESHOLD_ASSET` - event ValidatedAddresses ( - address[] addresses - ); - - /// @dev Executes an Exchange transaction iff the maker and taker meet - /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR - /// the exchange function is a cancellation. - /// Supported Exchange functions: - /// - batchFillOrders - /// - batchFillOrdersNoThrow - /// - batchFillOrKillOrders - /// - fillOrder - /// - fillOrderNoThrow - /// - fillOrKillOrder - /// - marketBuyOrders - /// - marketBuyOrdersNoThrow - /// - marketSellOrders - /// - marketSellOrdersNoThrow - /// - matchOrders - /// - cancelOrder - /// - batchCancelOrders - /// - cancelOrdersUpTo - /// Trying to call any other exchange function will throw. - /// @param salt Arbitrary number to ensure uniqueness of transaction hash. - /// @param signerAddress Address of transaction signer. - /// @param signedExchangeTransaction AbiV2 encoded calldata. - /// @param signature Proof of signer transaction by signer. - function executeTransaction( - uint256 salt, - address signerAddress, - bytes signedExchangeTransaction, - bytes signature - ) - external; - - /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD` - /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold - /// then this function will revert. Which addresses are validated depends on - /// which Exchange function is to be called (defined by `signedExchangeTransaction` above). - /// No parameters are taken as this function reads arguments directly from calldata, to save gas. - /// If all addresses are valid then this function emits a ValidatedAddresses event, listing all - /// of the addresses whose balance thresholds it checked. - function validateBalanceThresholdsOrRevert() internal; -} \ No newline at end of file diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts deleted file mode 100644 index c4723e7aa..000000000 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ /dev/null @@ -1,1521 +0,0 @@ -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { RevertReason, SignedOrder, Order } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; -import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; - -import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; - -import { artifacts } from '../../src/artifacts'; -import { - expectTransactionFailedAsync, - expectTransactionFailedWithoutReasonAsync, -} from '../utils/assertions'; -import { chaiSetup } from '../utils/chai_setup'; -import { constants } from '../utils/constants'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; -import { MatchOrderTester } from '../utils/match_order_tester'; -import { OrderFactory } from '../utils/order_factory'; -import { orderUtils } from '../utils/order_utils'; -import { TransactionFactory } from '../utils/transaction_factory'; -import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper'; -import { ContractName, ERC20BalancesByOwner, SignedTransaction, OrderStatus } from '../utils/types'; -import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; -import { TestExchangeInternalsContract } from '../../generated-wrappers/test_exchange_internals'; - -import { MethodAbi, AbiDefinition } from 'ethereum-types'; -import { AbiEncoder } from '@0x/utils'; -import { Method } from '@0x/utils/lib/src/abi_encoder'; -import { LogDecoder } from '../utils/log_decoder'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -const DECIMALS_DEFAULT = 18; - -interface ValidatedAddressesLog { - args: {addresses: string[]} -} - -describe.only(ContractName.BalanceThresholdFilter, () => { - let compliantMakerAddress: string; - let compliantMakerAddress2: string; - let owner: string; - let compliantTakerAddress: string; - let feeRecipientAddress: string; - let nonCompliantAddress: string; - let defaultMakerAssetAddress: string; - let defaultTakerAssetAddress: string; - let zrxAssetData: string; - let zrxToken: DummyERC20TokenContract; - let exchangeInstance: ExchangeContract; - let exchangeWrapper: ExchangeWrapper; - - let orderFactory: OrderFactory; - let orderFactory2: OrderFactory; - let nonCompliantOrderFactory: OrderFactory; - let erc20Wrapper: ERC20Wrapper; - let erc20Balances: ERC20BalancesByOwner; - let erc20TakerBalanceThresholdWrapper: BalanceThresholdWrapper; - let erc721TakerBalanceThresholdWrapper: BalanceThresholdWrapper; - let erc721MakerBalanceThresholdWrapper: BalanceThresholdWrapper; - let erc721NonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; - - let takerTransactionFactory: TransactionFactory; - let makerTransactionFactory: TransactionFactory; - let compliantSignedOrder: SignedOrder; - let compliantSignedOrder2: SignedOrder; - let compliantSignedFillOrderTx: SignedTransaction; - - let logDecoder: LogDecoder; - let exchangeInternals: TestExchangeInternalsContract; - - let defaultOrderParams: Partial; - - const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); - const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); - - let erc721CompliantForwarderInstance: BalanceThresholdFilterContract; - let erc20CompliantForwarderInstance: BalanceThresholdFilterContract; - - const assertValidatedAddressesLog = async (txReceipt: TransactionReceiptWithDecodedLogs, expectedValidatedAddresses: string[]) => { - expect(txReceipt.logs.length).to.be.gte(1); - const validatedAddressesLog = (txReceipt.logs[0] as any) as ValidatedAddressesLog; - const validatedAddresses = validatedAddressesLog.args.addresses; - // @HACK-hysz: Nested addresses are not translated to lower-case but this will change once - // the new ABI Encoder/Decoder is used by the contract templates. - let validatedAddressesNormalized: string[] = []; - _.each(validatedAddresses, (address) => { - const normalizedAddress = _.toLower(address); - validatedAddressesNormalized.push(normalizedAddress); - }); - expect(validatedAddressesNormalized).to.be.deep.equal(expectedValidatedAddresses); - }; - - before(async () => { - // Create accounts - await blockchainLifecycle.startAsync(); - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([ - owner, - compliantMakerAddress, - compliantMakerAddress2, - compliantTakerAddress, - feeRecipientAddress, - nonCompliantAddress, - ] = accounts); - // Create wrappers - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - let compliantAddresses = _.cloneDeepWith(usedAddresses); - _.remove(compliantAddresses, (address: string) => { - return address === nonCompliantAddress; - }); - const erc721Wrapper = new ERC721Wrapper(provider, compliantAddresses, owner); - // Deploy ERC20 tokens - const numDummyErc20ToDeploy = 4; - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - let erc20BalanceThresholdAsset: DummyERC20TokenContract; - [erc20TokenA, erc20TokenB, zrxToken, erc20BalanceThresholdAsset] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - defaultMakerAssetAddress = erc20TokenA.address; - defaultTakerAssetAddress = erc20TokenB.address; - zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Create proxies - const erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - // Deploy Exchange congtract - exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); - // Register proxies - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { - from: owner, - }); - // Deploy Compliant Forwarder - const erc721alanceThreshold = new BigNumber(1); - await erc721Wrapper.deployProxyAsync(); - const [erc721BalanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - erc721CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( - artifacts.BalanceThresholdFilter, - provider, - txDefaults, - exchangeInstance.address, - erc721BalanceThresholdAsset.address, - erc721alanceThreshold - ); - const erc20BalanceThreshold = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 10); - erc20CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( - artifacts.BalanceThresholdFilter, - provider, - txDefaults, - exchangeInstance.address, - erc20BalanceThresholdAsset.address, - erc20BalanceThreshold - ); - - // Default order parameters - defaultOrderParams = { - exchangeAddress: exchangeInstance.address, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - makerAssetAmount, - takerAssetAmount, - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), - senderAddress: erc721CompliantForwarderInstance.address, - }; - const defaultOrderParams1 = { - makerAddress: compliantMakerAddress, - ... - defaultOrderParams, - } - const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; - takerTransactionFactory = new TransactionFactory(makerPrivateKey, exchangeInstance.address); - orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams1); - const defaultOrderParams2 = { - makerAddress: compliantMakerAddress2, - ... - defaultOrderParams, - } - const secondMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress2)]; - orderFactory2 = new OrderFactory(secondMakerPrivateKey, defaultOrderParams2); - - const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; - const defaultNonCompliantOrderParams = { - makerAddress: nonCompliantAddress, - ... - defaultOrderParams, - }; - nonCompliantOrderFactory = new OrderFactory(nonCompliantPrivateKey, defaultNonCompliantOrderParams); - // Create Valid/Invalid orders - const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; - takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); - compliantSignedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - }); - const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - compliantSignedOrder, - ); - const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - compliantSignedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( - compliantSignedOrderWithoutExchangeAddressData, - ); - - logDecoder = new LogDecoder(web3Wrapper); - erc20TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc20CompliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); - erc721TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); - erc721MakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(makerPrivateKey, exchangeInstance.address), provider); - erc721NonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); - - // Instantiate internal exchange contract - exchangeInternals = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( - artifacts.TestExchangeInternals, - provider, - txDefaults, - ); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - - describe.only('General Sanity Checks', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it.only('should transfer the correct amounts and validate both maker/taker when both maker and taker exceed the balance threshold of an ERC20 token', async () => { - const compliantSignedOrderERC20Sender = await orderFactory.newSignedOrderAsync({ - ... - defaultOrderParams, - makerAddress: compliantMakerAddress, - senderAddress: erc20TakerBalanceThresholdWrapper.getBalanceThresholdAddress(), - }); - // Execute a valid fill - const txReceipt = await erc20TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrderERC20Sender, compliantTakerAddress, {takerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if the signed transaction is not intended for supported', async () => { - // Create signed order without the fillOrder function selector - const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); - const selectorLengthInBytes = 4; - const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); - const badSelector = '0x00000000'; - const badSelectorBuf = ethUtil.toBuffer(badSelector); - const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); - const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - txDataBufWithBadSelectorHex, - compliantSignedFillOrderTx.signature, - )); - }); - it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { - // Create signed order with incorrect senderAddress - const notBalanceThresholdFilterAddress = zrxToken.address; - const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: notBalanceThresholdFilterAddress, - }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadSenderAddress, - ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, - ); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, - )); - }); - // @TODO - greater than 1 balance - }); - - describe('batchFillOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) - .times(2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('batchFillOrdersNoThrow', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) - .times(2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('batchFillOrKillOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) - .times(2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if one takerAssetFillAmount is not fully filled', async () => { - const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount]; - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.FailedExecution - ); - }); - }); - - describe('fillOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrderAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.fillOrderAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('fillOrderNoThrow', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('fillOrKillOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; - const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount_ - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if takerAssetFillAmount is not fully filled', async () => { - const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( - compliantSignedOrder, - compliantTakerAddress, - {takerAssetFillAmount: tooBigTakerAssetFillAmount} - ), - RevertReason.FailedExecution - ); - }); - }); - - describe('marketSellOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid2 = compliantSignedOrder2.makerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('marketSellOrdersNoThrow', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid2 = compliantSignedOrder2.makerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('marketBuyOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerFeePaid2 = compliantSignedOrder2.makerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - const dummyMakerAssetFillAmount = new BigNumber(0); - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync( - orders, - compliantTakerAddress, - {makerAssetFillAmount: dummyMakerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const dummyMakerAssetFillAmount = new BigNumber(0); - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync( - orders, - nonCompliantAddress, - {makerAssetFillAmount: dummyMakerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('marketBuyOrdersNoThrowAsync', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerFeePaid2 = compliantSignedOrder2.makerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - const dummyMakerAssetFillAmount = new BigNumber(0); - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( - orders, - compliantTakerAddress, - {makerAssetFillAmount: dummyMakerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const dummyMakerAssetFillAmount = new BigNumber(0); - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( - orders, - nonCompliantAddress, - {makerAssetFillAmount: dummyMakerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('matchOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('Should transfer correct amounts when both makers and taker meet the balance threshold', async () => { - // Test values/results taken from Match Orders test: - // 'Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorFloor but fail isRoundingErrorCeil' - // Create orders to match - const signedOrderLeft = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(17), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(98), 0), - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - feeRecipientAddress: feeRecipientAddress, - }); - const signedOrderRight = await orderFactory2.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - feeRecipientAddress: feeRecipientAddress, - }); - // Compute expected transfer amounts - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), // 76.47% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - const txReceipt = await erc721TakerBalanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); - // Assert validated addresses - const expectedValidatedAddresseses = [signedOrderLeft.makerAddress, signedOrderRight.makerAddress, compliantTakerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect( - newBalances[signedOrderLeft.makerAddress][defaultMakerAssetAddress], - 'Checking left maker egress ERC20 account balance', - ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][defaultMakerAssetAddress].sub(expectedTransferAmounts.amountSoldByLeftMaker)); - expect( - newBalances[signedOrderRight.makerAddress][defaultTakerAssetAddress], - 'Checking right maker ingress ERC20 account balance', - ).to.be.bignumber.equal(erc20Balances[signedOrderRight.makerAddress][defaultTakerAssetAddress].sub(expectedTransferAmounts.amountSoldByRightMaker)); - expect( - newBalances[compliantTakerAddress][defaultMakerAssetAddress], - 'Checking taker ingress ERC20 account balance', - ).to.be.bignumber.equal(erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(expectedTransferAmounts.amountReceivedByTaker)); - expect( - newBalances[signedOrderLeft.makerAddress][defaultTakerAssetAddress], - 'Checking left maker ingress ERC20 account balance', - ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][defaultTakerAssetAddress].add(expectedTransferAmounts.amountBoughtByLeftMaker)); - expect( - newBalances[signedOrderRight.makerAddress][defaultMakerAssetAddress], - 'Checking right maker egress ERC20 account balance', - ).to.be.bignumber.equal( - erc20Balances[signedOrderRight.makerAddress][defaultMakerAssetAddress].add(expectedTransferAmounts.amountBoughtByRightMaker), - ); - // Paid fees - expect( - newBalances[signedOrderLeft.makerAddress][zrxToken.address], - 'Checking left maker egress ERC20 account fees', - ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByLeftMaker)); - expect( - newBalances[signedOrderRight.makerAddress][zrxToken.address], - 'Checking right maker egress ERC20 account fees', - ).to.be.bignumber.equal(erc20Balances[signedOrderRight.makerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByRightMaker)); - expect( - newBalances[compliantTakerAddress][zrxToken.address], - 'Checking taker egress ERC20 account fees', - ).to.be.bignumber.equal(erc20Balances[compliantTakerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByTakerLeft).sub(expectedTransferAmounts.feePaidByTakerRight)); - // Received fees - expect( - newBalances[signedOrderLeft.feeRecipientAddress][zrxToken.address], - 'Checking left fee recipient ingress ERC20 account fees', - ).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(expectedTransferAmounts.feePaidByLeftMaker).add(expectedTransferAmounts.feePaidByRightMaker).add(expectedTransferAmounts.feePaidByTakerLeft).add(expectedTransferAmounts.feePaidByTakerRight), - ); - }); - it('should revert if left maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.matchOrdersAsync( - compliantSignedOrder, - signedOrderWithBadMakerAddress, - compliantTakerAddress, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if right maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.matchOrdersAsync( - signedOrderWithBadMakerAddress, - compliantSignedOrder, - compliantTakerAddress, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.matchOrdersAsync( - compliantSignedOrder, - compliantSignedOrder, - nonCompliantAddress, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('cancelOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('Should successfully cancel order if maker meets balance threshold', async () => { - // Verify order is not cancelled - const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - // Cancel - const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrderAsync(compliantSignedOrder, compliantSignedOrder.makerAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - }); - it('Should successfully cancel order if maker does not meet balance threshold', async () => { - // Create order where maker does not meet balance threshold - const signedOrderWithBadMakerAddress = await nonCompliantOrderFactory.newSignedOrderAsync({}); - // Verify order is not cancelled - const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) - expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - // Cancel - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrderAsync(signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) - expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - }); - }); - - describe('batchCancelOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('Should successfully batch cancel orders if maker meets balance threshold', async () => { - // Create orders to cancel - const compliantSignedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - ]; - // Verify orders are not cancelled - await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - }); - // Cancel - const txReceipt = await erc721MakerBalanceThresholdWrapper.batchCancelOrdersAsync(compliantSignedOrders, compliantSignedOrder.makerAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - }); - }); - it('Should successfully batch cancel order if maker does not meet balance threshold', async () => { - // Create orders to cancel - const nonCompliantSignedOrders = [ - await nonCompliantOrderFactory.newSignedOrderAsync(), - await nonCompliantOrderFactory.newSignedOrderAsync(), - await nonCompliantOrderFactory.newSignedOrderAsync(), - ]; - // Verify orders are not cancelled - await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) - return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - }); - // Cancel - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync(nonCompliantSignedOrders, nonCompliantAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - }); - }); - }); - - describe('cancelOrdersUpTo', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('Should successfully batch cancel orders if maker meets balance threshold', async () => { - // Create orders to cancel - const compliantSignedOrders = [ - await orderFactory.newSignedOrderAsync({salt: new BigNumber(0)}), - await orderFactory.newSignedOrderAsync({salt: new BigNumber(1)}), - await orderFactory.newSignedOrderAsync({salt: new BigNumber(2)}), - ]; - // Verify orders are not cancelled - await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - }); - // Cancel - const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, compliantSignedOrder.makerAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - await _.each(compliantSignedOrders, async (compliantSignedOrder, salt: number) => { - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - const saltAsBigNumber = new BigNumber(salt); - if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - } else { - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - } - }); - }); - it('Should successfully batch cancel order if maker does not meet balance threshold', async () => { - // Create orders to cancel - const nonCompliantSignedOrders = [ - await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(0)}), - await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(1)}), - await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(2)}), - ]; - // Verify orders are not cancelled - await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) - return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - }); - // Cancel - const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, nonCompliantAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder, salt: number) => { - const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) - const saltAsBigNumber = new BigNumber(salt); - if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - } else { - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - } - }); - }); - }); -}); -// tslint:disable:max-file-line-count -// tslint:enable:no-unnecessary-type-assertion diff --git a/packages/contracts/test/utils/balance_threshold_wrapper.ts b/packages/contracts/test/utils/balance_threshold_wrapper.ts deleted file mode 100644 index cff40aa52..000000000 --- a/packages/contracts/test/utils/balance_threshold_wrapper.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; - -import { formatters } from './formatters'; -import { LogDecoder } from './log_decoder'; -import { orderUtils } from './order_utils'; -import { TransactionFactory } from '../utils/transaction_factory'; -import { OrderInfo } from './types'; - -export class BalanceThresholdWrapper { - private readonly _balanceThresholdFilter: BalanceThresholdFilterContract; - private readonly _signerTransactionFactory: TransactionFactory; - private readonly _exchange: ExchangeContract; - private readonly _web3Wrapper: Web3Wrapper; - private readonly _logDecoder: LogDecoder; - constructor(balanceThresholdFilter: BalanceThresholdFilterContract, exchangeContract: ExchangeContract, signerTransactionFactory: TransactionFactory, provider: Provider) { - this._balanceThresholdFilter = balanceThresholdFilter; - this._exchange = exchangeContract; - this._signerTransactionFactory = signerTransactionFactory; - this._web3Wrapper = new Web3Wrapper(provider); - this._logDecoder = new LogDecoder(this._web3Wrapper); - } - public async fillOrderAsync( - signedOrder: SignedOrder, - from: string, - opts: { takerAssetFillAmount?: BigNumber } = {}, - ): Promise { - const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); - const data = this._exchange.fillOrder.getABIEncodedTransactionData( - params.order, - params.takerAssetFillAmount, - params.signature, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async fillOrKillOrderAsync( - signedOrder: SignedOrder, - from: string, - opts: { takerAssetFillAmount?: BigNumber } = {}, - ): Promise { - const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); - const data = this._exchange.fillOrKillOrder.getABIEncodedTransactionData( - params.order, - params.takerAssetFillAmount, - params.signature, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async fillOrderNoThrowAsync( - signedOrder: SignedOrder, - from: string, - opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {}, - ): Promise { - const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); - const data = this._exchange.fillOrderNoThrow.getABIEncodedTransactionData( - params.order, - params.takerAssetFillAmount, - params.signature, - ); - const txReceipt = this._executeTransaction(data, from, opts.gas); - return txReceipt; - } - public async batchFillOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmounts?: BigNumber[] } = {}, - ): Promise { - const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); - const data = this._exchange.batchFillOrders.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmounts, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async batchFillOrKillOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmounts?: BigNumber[] } = {}, - ): Promise { - const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); - const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmounts, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async batchFillOrdersNoThrowAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {}, - ): Promise { - const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); - const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmounts, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from, opts.gas); - return txReceipt; - } - public async marketSellOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmount: BigNumber }, - ): Promise { - const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); - const data = this._exchange.marketSellOrders.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmount, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async marketSellOrdersNoThrowAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmount: BigNumber; gas?: number }, - ): Promise { - const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); - const data = this._exchange.marketSellOrdersNoThrow.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmount, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from, opts.gas); - return txReceipt; - } - public async marketBuyOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { makerAssetFillAmount: BigNumber }, - ): Promise { - const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); - const data = this._exchange.marketBuyOrders.getABIEncodedTransactionData( - params.orders, - params.makerAssetFillAmount, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async marketBuyOrdersNoThrowAsync( - orders: SignedOrder[], - from: string, - opts: { makerAssetFillAmount: BigNumber; gas?: number }, - ): Promise { - const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); - const data = this._exchange.marketBuyOrdersNoThrow.getABIEncodedTransactionData( - params.orders, - params.makerAssetFillAmount, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from, opts.gas); - return txReceipt; - } - public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise { - const params = orderUtils.createCancel(signedOrder); - const data = this._exchange.cancelOrder.getABIEncodedTransactionData(params.order); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async batchCancelOrdersAsync( - orders: SignedOrder[], - from: string, - ): Promise { - const params = formatters.createBatchCancel(orders); - const data = this._exchange.batchCancelOrders.getABIEncodedTransactionData(params.orders); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise { - const data = this._exchange.cancelOrdersUpTo.getABIEncodedTransactionData(salt); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise { - const filledAmount = await this._exchange.filled.callAsync(orderHashHex); - return filledAmount; - } - public async isCancelledAsync(orderHashHex: string): Promise { - const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex); - return isCancelled; - } - public async getOrderEpochAsync(makerAddress: string, senderAddress: string): Promise { - const orderEpoch = await this._exchange.orderEpoch.callAsync(makerAddress, senderAddress); - return orderEpoch; - } - public async getOrderInfoAsync(signedOrder: SignedOrder): Promise { - const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo; - return orderInfo; - } - public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise { - const ordersInfo = (await this._exchange.getOrdersInfo.callAsync(signedOrders)) as OrderInfo[]; - return ordersInfo; - } - public async matchOrdersAsync( - signedOrderLeft: SignedOrder, - signedOrderRight: SignedOrder, - from: string, - ): Promise { - const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight); - const data = await this._exchange.matchOrders.getABIEncodedTransactionData( - params.left, - params.right, - params.leftSignature, - params.rightSignature - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public getBalanceThresholdAddress(): string { - return this._balanceThresholdFilter.address; - } - public getExchangeAddress(): string { - return this._exchange.address; - } - // Exchange functions - //abiEncodeFillOrder - //getFillOrderResultsAsync - // - private async _executeTransaction(abiEncodedExchangeTxData: string, from: string, gas?: number): Promise { - const signedExchangeTx = this._signerTransactionFactory.newSignedTransaction(abiEncodedExchangeTxData); - const txOpts = _.isUndefined(gas) ? {from} : {from, gas}; - const txHash = await this._balanceThresholdFilter.executeTransaction.sendTransactionAsync( - signedExchangeTx.salt, - signedExchangeTx.signerAddress, - signedExchangeTx.data, - signedExchangeTx.signature, - txOpts, - ); - const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return txReceipt; - } -} -- cgit v1.2.3 From 6a0f5f39eea5fef285a1e2e962be03c2fa8847ab Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Dec 2018 15:55:36 -0800 Subject: Prettier / Linter fixes for TS --- packages/types/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 4d5b6e1f2..4470dd501 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -246,7 +246,7 @@ export enum RevertReason { // Balance Threshold Filter InvalidOrBlockedExchangeSelector = 'INVALID_OR_BLOCKED_EXCHANGE_SELECTOR', BalanceQueryFailed = 'BALANCE_QUERY_FAILED', - AtLeastOneAddressDoesNotMeetBalanceThreshold= 'AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD', + AtLeastOneAddressDoesNotMeetBalanceThreshold = 'AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD', } export enum StatusCodes { -- cgit v1.2.3 From 34ff7fae9cffa7aaba5f0ba0060ed063a855f8e9 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Dec 2018 16:07:19 -0800 Subject: Removed deprecated README + comments --- packages/contracts/contracts/tokens/README.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 packages/contracts/contracts/tokens/README.md (limited to 'packages') diff --git a/packages/contracts/contracts/tokens/README.md b/packages/contracts/contracts/tokens/README.md deleted file mode 100644 index b54f0e046..000000000 --- a/packages/contracts/contracts/tokens/README.md +++ /dev/null @@ -1,2 +0,0 @@ -Contracts from https://github.com/sendwyre/yes-compliance-token -Modified to compile in our codebase. -- cgit v1.2.3 From f91781a0605f46ee1a40bf979d22ff510f48d464 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 13 Dec 2018 13:52:20 -0800 Subject: Less Assembly. More Solidity. Less Efficiency. More Readability. --- .../utils/ExchangeSelectors/ExchangeSelectors.sol | 151 --------------------- 1 file changed, 151 deletions(-) delete mode 100644 packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol (limited to 'packages') diff --git a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol deleted file mode 100644 index c361fd075..000000000 --- a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol +++ /dev/null @@ -1,151 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - - -contract ExchangeSelectors { - - // allowedValidators - bytes4 constant allowedValidatorsSelector = 0x7b8e3514; - bytes4 constant allowedValidatorsSelectorGenerator = bytes4(keccak256('allowedValidators(address,address)')); - - // assetProxies - bytes4 constant assetProxiesSelector = 0x3fd3c997; - bytes4 constant assetProxiesSelectorGenerator = bytes4(keccak256('assetProxies(bytes4)')); - - // batchCancelOrders - bytes4 constant batchCancelOrdersSelector = 0x4ac14782; - bytes4 constant batchCancelOrdersSelectorGenerator = bytes4(keccak256('batchCancelOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); - - // batchFillOrKillOrders - bytes4 constant batchFillOrKillOrdersSelector = 0x4d0ae546; - bytes4 constant batchFillOrKillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrKillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - - // batchFillOrders - bytes4 constant batchFillOrdersSelector = 0x297bb70b; - bytes4 constant batchFillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - - // batchFillOrdersNoThrow - bytes4 constant batchFillOrdersNoThrowSelector = 0x50dde190; - bytes4 constant batchFillOrdersNoThrowSelectorGenerator = bytes4(keccak256('batchFillOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - - // cancelOrder - bytes4 constant cancelOrderSelector = 0xd46b02c3; - bytes4 constant cancelOrderSelectorGenerator = bytes4(keccak256('cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); - - // cancelOrdersUpTo - bytes4 constant cancelOrdersUpToSelector = 0x4f9559b1; - bytes4 constant cancelOrdersUpToSelectorGenerator = bytes4(keccak256('cancelOrdersUpTo(uint256)')); - - // cancelled - bytes4 constant cancelledSelector = 0x2ac12622; - bytes4 constant cancelledSelectorGenerator = bytes4(keccak256('cancelled(bytes32)')); - - // currentContextAddress - bytes4 constant currentContextAddressSelector = 0xeea086ba; - bytes4 constant currentContextAddressSelectorGenerator = bytes4(keccak256('currentContextAddress()')); - - // executeTransaction - bytes4 constant executeTransactionSelector = 0xbfc8bfce; - bytes4 constant executeTransactionSelectorGenerator = bytes4(keccak256('executeTransaction(uint256,address,bytes,bytes)')); - - // fillOrKillOrder - bytes4 constant fillOrKillOrderSelector = 0x64a3bc15; - bytes4 constant fillOrKillOrderSelectorGenerator = bytes4(keccak256('fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - - // fillOrder - bytes4 constant fillOrderSelector = 0xb4be83d5; - bytes4 constant fillOrderSelectorGenerator = bytes4(keccak256('fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - - // fillOrderNoThrow - bytes4 constant fillOrderNoThrowSelector = 0x3e228bae; - bytes4 constant fillOrderNoThrowSelectorGenerator = bytes4(keccak256('fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - - // filled - bytes4 constant filledSelector = 0x288cdc91; - bytes4 constant filledSelectorGenerator = bytes4(keccak256('filled(bytes32)')); - - // getAssetProxy - bytes4 constant getAssetProxySelector = 0x60704108; - bytes4 constant getAssetProxySelectorGenerator = bytes4(keccak256('getAssetProxy(bytes4)')); - - // getOrderInfo - bytes4 constant getOrderInfoSelector = 0xc75e0a81; - bytes4 constant getOrderInfoSelectorGenerator = bytes4(keccak256('getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); - - // getOrdersInfo - bytes4 constant getOrdersInfoSelector = 0x7e9d74dc; - bytes4 constant getOrdersInfoSelectorGenerator = bytes4(keccak256('getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); - - // isValidSignature - bytes4 constant isValidSignatureSelector = 0x93634702; - bytes4 constant isValidSignatureSelectorGenerator = bytes4(keccak256('isValidSignature(bytes32,address,bytes)')); - - // marketBuyOrders - bytes4 constant marketBuyOrdersSelector = 0xe5fa431b; - bytes4 constant marketBuyOrdersSelectorGenerator = bytes4(keccak256('marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - - // marketBuyOrdersNoThrow - bytes4 constant marketBuyOrdersNoThrowSelector = 0xa3e20380; - bytes4 constant marketBuyOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketBuyOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - - // marketSellOrders - bytes4 constant marketSellOrdersSelector = 0x7e1d9808; - bytes4 constant marketSellOrdersSelectorGenerator = bytes4(keccak256('marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - - // marketSellOrdersNoThrow - bytes4 constant marketSellOrdersNoThrowSelector = 0xdd1c7d18; - bytes4 constant marketSellOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - - // matchOrders - bytes4 constant matchOrdersSelector = 0x3c28d861; - bytes4 constant matchOrdersSelectorGenerator = bytes4(keccak256('matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)')); - - // orderEpoch - bytes4 constant orderEpochSelector = 0xd9bfa73e; - bytes4 constant orderEpochSelectorGenerator = bytes4(keccak256('orderEpoch(address,address)')); - - // owner - bytes4 constant ownerSelector = 0x8da5cb5b; - bytes4 constant ownerSelectorGenerator = bytes4(keccak256('owner()')); - - // preSign - bytes4 constant preSignSelector = 0x3683ef8e; - bytes4 constant preSignSelectorGenerator = bytes4(keccak256('preSign(bytes32,address,bytes)')); - - // preSigned - bytes4 constant preSignedSelector = 0x82c174d0; - bytes4 constant preSignedSelectorGenerator = bytes4(keccak256('preSigned(bytes32,address)')); - - // registerAssetProxy - bytes4 constant registerAssetProxySelector = 0xc585bb93; - bytes4 constant registerAssetProxySelectorGenerator = bytes4(keccak256('registerAssetProxy(address)')); - - // setSignatureValidatorApproval - bytes4 constant setSignatureValidatorApprovalSelector = 0x77fcce68; - bytes4 constant setSignatureValidatorApprovalSelectorGenerator = bytes4(keccak256('setSignatureValidatorApproval(address,bool)')); - - // transactions - bytes4 constant transactionsSelector = 0x642f2eaf; - bytes4 constant transactionsSelectorGenerator = bytes4(keccak256('transactions(bytes32)')); - - // transferOwnership - bytes4 constant transferOwnershipSelector = 0xf2fde38b; - bytes4 constant transferOwnershipSelectorGenerator = bytes4(keccak256('transferOwnership(address)')); -} \ No newline at end of file -- cgit v1.2.3