diff options
Diffstat (limited to 'packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol')
-rw-r--r-- | packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol | 648 |
1 files changed, 0 insertions, 648 deletions
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<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'); - _; - } - -}
\ No newline at end of file |