Removed Yes Token - its no longer needed to test Balance Threshold Filter
This commit is contained in:
parent
cb9ec18f96
commit
8d6219296a
@ -84,7 +84,6 @@ export enum ContractName {
|
||||
MultiSigWalletWithTimeLock = 'MultiSigWalletWithTimeLock',
|
||||
Exchange = 'Exchange',
|
||||
ZRXToken = 'ZRXToken',
|
||||
YesComplianceToken = 'YesComplianceToken',
|
||||
DummyERC20Token = 'DummyERC20Token',
|
||||
EtherToken = 'WETH9',
|
||||
DutchAuction = 'DutchAuction',
|
||||
|
@ -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);
|
||||
|
||||
}
|
@ -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 {
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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<e.yesMarkKeys.length; i++) {
|
||||
YesMark storage mark = e.yesMarkByKey[e.yesMarkKeys[i]];
|
||||
|
||||
if(mark.countryCode != _countryCode)
|
||||
continue;
|
||||
|
||||
if(mark.validatorEntityIdIdx[callerEntityId] == 0 &&
|
||||
(mark.validatorEntityIds.length == 0 || mark.validatorEntityIds[0] != callerEntityId)) {
|
||||
// set, but not by this validator, skip
|
||||
continue;
|
||||
}
|
||||
|
||||
if(clearYes_I(mark, e, callerEntityId)) {
|
||||
// mark was fully destroyed (and replaced in e.yesMarkKeys with the last one)
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearYes(uint256 _entityId) external permission_validator {
|
||||
clearYes_I(_entityId);
|
||||
}
|
||||
|
||||
function setLocked(uint256 _entityId, bool _lock) external permission_validator {
|
||||
EntityRecord storage e = entity(_entityId);
|
||||
|
||||
// can't fux with owner lock
|
||||
require(_entityId != OWNER_ENTITY_ID);
|
||||
|
||||
// if caller isn't ecosystem control, cannot target other validators
|
||||
if(!senderIsEcosystemControl())
|
||||
require(e.yesMarkByKey[yesKey(0, YESMARK_VALIDATOR)].yes == 0);
|
||||
|
||||
// lockzz
|
||||
e.locked = _lock;
|
||||
}
|
||||
|
||||
function isLocked(uint256 _entityId) external view returns (bool) {
|
||||
return entity(_entityId).locked;
|
||||
}
|
||||
|
||||
function isFinalized(uint256 _tokenId) external view returns (bool) {
|
||||
return tokenRecordById[_tokenId].finalized;
|
||||
}
|
||||
|
||||
function finalize(uint256 _tokenId) external permission_access_tokenId(_tokenId) {
|
||||
TokenRecord storage t = tokenRecordById[_tokenId];
|
||||
t.finalized = true;
|
||||
}
|
||||
|
||||
// Internal Methods ------------------------------------------------------------------------------------------------
|
||||
|
||||
function clearYes_I(YesMark storage mark, EntityRecord storage e, uint256 validatorEntityId) internal returns(bool) {
|
||||
uint32 idx = mark.validatorEntityIdIdx[validatorEntityId];
|
||||
mark.validatorEntityIds[idx] = mark.validatorEntityIds[mark.validatorEntityIds.length - 1];
|
||||
mark.validatorEntityIds.length--;
|
||||
delete mark.validatorEntityIdIdx[validatorEntityId];
|
||||
|
||||
// remap
|
||||
if(mark.validatorEntityIds.length > 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<e.yesMarkKeys.length; i++) {
|
||||
YesMark storage mark = e.yesMarkByKey[e.yesMarkKeys[i]];
|
||||
|
||||
if(mark.validatorEntityIdIdx[callerEntityId] == 0 &&
|
||||
(mark.validatorEntityIds.length == 0 || mark.validatorEntityIds[0] != callerEntityId)) {
|
||||
// set, but not by this validator
|
||||
continue;
|
||||
}
|
||||
|
||||
if(clearYes_I(mark, e, callerEntityId)) {
|
||||
// mark was fully destroyed (and replaced in e.yesMarkKeys with the last one)
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isYes_I(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) internal view returns(bool) {
|
||||
if(balanceOf(_address) == 0)
|
||||
return false;
|
||||
|
||||
uint256 entityId = entityIdByTokenId[tokenOfOwnerByIndex(_address, 0)];
|
||||
EntityRecord storage e = entityRecordById[entityId];
|
||||
|
||||
// locked always bails
|
||||
if(e.locked)
|
||||
return false;
|
||||
|
||||
// gate out definite nos
|
||||
YesMark storage m = e.yesMarkByKey[yesKey(_countryCode, _yes)];
|
||||
if(m.yes == 0)
|
||||
return false;
|
||||
|
||||
// no specific validators, we good
|
||||
if(_validatorEntityId == 0)
|
||||
return true;
|
||||
|
||||
// filter by validator
|
||||
return m.validatorEntityIdIdx[_validatorEntityId] > 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');
|
||||
_;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user