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