From 803020a0d3b8fd753b9a36f801430c9b30382b4b Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Wed, 7 Feb 2024 19:39:18 +0000 Subject: [PATCH 1/2] (Hub): stopped personal Circles --- src/circles/Circles.sol | 26 ++++++- src/hub/Hub.sol | 165 +++++++++++++++++++++++----------------- 2 files changed, 122 insertions(+), 69 deletions(-) diff --git a/src/circles/Circles.sol b/src/circles/Circles.sol index 8c003de..6ae8313 100644 --- a/src/circles/Circles.sol +++ b/src/circles/Circles.sol @@ -4,4 +4,28 @@ pragma solidity >=0.8.13; import "openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol"; import "openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol"; -abstract contract Circles is ERC1155 {} +contract Circles is ERC1155 { + // Constants + + /** + * @dev ERC1155 tokens MUST be 18 decimals. Used to calculate the issuance rate. + */ + uint8 public constant DECIMALS = uint8(18); + + /** + * @notice Issue one Circle per hour for each human. + */ + uint256 public constant ISSUANCE_PERIOD = 1 hours; + + /** + * @notice Upon claiming, the maximum claim is upto two weeks + * of history. Unclaimed older Circles are unclaimable. + */ + uint256 public constant MAX_CLAIM_DURATION = 2 weeks; + + // Constructor + + constructor(string memory uri_) ERC1155(uri_) {} + + // External functions +} diff --git a/src/hub/Hub.sol b/src/hub/Hub.sol index 2a946ac..49822dd 100644 --- a/src/hub/Hub.sol +++ b/src/hub/Hub.sol @@ -77,9 +77,9 @@ contract Hub is Circles { address public constant CIRCLES_STOPPED_V1 = address(0x1); /** - * @notice Indefinitely, or approximate future infinity with uint96.max + * @notice Indefinite future, or approximated with uint96.max */ - uint96 public constant INDEFINITELY = type(uint96).max; + uint96 public constant INDEFINITE_FUTURE = type(uint96).max; // State variables @@ -124,8 +124,6 @@ contract Hub is Circles { */ mapping(address => MintTime) public mintTimes; - mapping(address => bool) public stopped; - mapping(uint256 => WrappedERC20) public tokenIDToInfERC20; // Mint policy registered by avatar. @@ -182,7 +180,7 @@ contract Hub is Circles { * (todo: eg. "https://fallback.aboutcircles.com/v1/circles/{id}.json") */ constructor(IHubV1 _hubV1, address _standardTreasury, uint256 _bootstrapTime, string memory _fallbackUri) - ERC1155(_fallbackUri) + Circles(_fallbackUri) { require(address(_hubV1) != address(0), "Hub v1 address can not be zero."); require(_standardTreasury != address(0), "Standard treasury address can not be zero."); @@ -242,7 +240,7 @@ contract Hub is Circles { _mint(_human, _toTokenId(_human), WELCOME_BONUS, ""); // set trust to indefinite future, but avatar can edit this later - _trust(msg.sender, _human, INDEFINITELY); + _trust(msg.sender, _human, INDEFINITE_FUTURE); emit InviteHuman(msg.sender, _human); } @@ -320,6 +318,11 @@ contract Hub is Circles { emit CidV0(msg.sender, _cidV0Digest); } + /** + * Register organization allows to register an organization avatar. + * @param _name name of the organization + * @param _cidV0Digest IPFS CIDv0 digest for the organization metadata + */ function registerOrganization(string calldata _name, bytes32 _cidV0Digest) external { require(_isValidName(_name), "Invalid organization name."); _insertAvatar(msg.sender); @@ -387,6 +390,20 @@ contract Hub is Circles { _mint(msg.sender, _toTokenId(_group), sumAmounts, ""); } + function stop() external { + require(isHuman(msg.sender), "Only human can call stop."); + MintTime storage mintTime = mintTimes[msg.sender]; + // stop future mints of personal Circles + // by setting the last mint time to indefinite future. + mintTime.lastMintTime = INDEFINITE_FUTURE; + } + + function stopped(address _human) external view returns (bool) { + require(isHuman(_human), "Only personal Circles can stopped or not stopped."); + MintTime storage mintTime = mintTimes[msg.sender]; + return (mintTime.lastMintTime == INDEFINITE_FUTURE); + } + // check if path transfer can be fully ERC1155 compatible // note: matrix math needs to consider mints, otherwise it won't add up @@ -469,14 +486,26 @@ contract Hub is Circles { // Public functions + /** + * Checks if an avatar is registered as a human. + * @param _human address of the human to check + */ function isHuman(address _human) public view returns (bool) { return mintTimes[_human].lastMintTime > 0; } + /** + * Checks if an avatar is registered as a group. + * @param _group address of the group to check + */ function isGroup(address _group) public view returns (bool) { return mintPolicies[_group] != address(0); } + /** + * Checks if an avatar is registered as an organization. + * @param _organization address of the organization to check + */ function isOrganization(address _organization) public view returns (bool) { return avatars[_organization] != address(0) && mintPolicies[_organization] == address(0) && mintTimes[_organization].lastMintTime == uint256(0); @@ -494,6 +523,67 @@ contract Hub is Circles { return super.uri(_id); } + /** + * @dev checks whether string is a valid name by checking + * the length as max 32 bytes and the allowed characters: 0-9, A-Z, a-z, space, + * hyphen, underscore, period, parentheses, apostrophe, + * ampersand, plus and hash. + * This restricts the contract name to a subset of ASCII characters, + * and excludes unicode characters for other alphabets and emoticons. + * Instead the default ERC1155 metadata read from the IPFS CID registry, + * should provide the full display name with unicode characters. + * Names are not checked for uniqueness. + */ + function _isValidName(string memory _name) public pure returns (bool) { + bytes memory nameBytes = bytes(_name); + if (nameBytes.length > 32 || nameBytes.length == 0) return false; // Check length + + for (uint256 i = 0; i < nameBytes.length; i++) { + bytes1 char = nameBytes[i]; + if ( + !(char >= 0x30 && char <= 0x39) // 0-9 + && !(char >= 0x41 && char <= 0x5A) // A-Z + && !(char >= 0x61 && char <= 0x7A) // a-z + && !(char == 0x20) // Space + && !(char == 0x2D || char == 0x5F) // Hyphen (-), Underscore (_) + && !(char == 0x2E) // Period (.) + && !(char == 0x28 || char == 0x29) // Parentheses ( () ) + && !(char == 0x27) // Apostrophe (') + && !(char == 0x26) // Ampersand (&) + && !(char == 0x2B || char == 0x23) // Plus (+), Hash (#) + ) { + return false; + } + } + return true; + } + + /** + * @dev checks whether string is a valid symbol by checking + * the length as max 16 bytes and the allowed characters: 0-9, A-Z, a-z, + * hyphen, underscore. + */ + function _isValidSymbol(string memory _symbol) public pure returns (bool) { + bytes memory symbolBytes = bytes(_symbol); + if (symbolBytes.length == 0 || symbolBytes.length > 16) { + return false; // Check length is within range + } + + for (uint256 i = 0; i < symbolBytes.length; i++) { + bytes1 char = symbolBytes[i]; + if ( + // allowed ASCII characters 0-9, A-Z, a-z, Hyphen (-), Underscore (_) + !( + (char >= 0x30 && char <= 0x39) || (char >= 0x41 && char <= 0x5A) || (char >= 0x61 && char <= 0x7A) + || (char == 0x2D) || (char == 0x5F) + ) + ) { + return false; + } + } + return true; + } + // Internal functions /** @@ -514,7 +604,7 @@ contract Hub is Circles { mintTime.lastMintTime = uint96(block.timestamp); // trust self indefinitely, cannot be altered later - _trust(_human, _human, INDEFINITELY); + _trust(_human, _human, INDEFINITE_FUTURE); return v1CirclesStatus; } @@ -605,67 +695,6 @@ contract Hub is Circles { // Private functions - /** - * @dev checks whether string is a valid name by checking - * the length as max 32 bytes and the allowed characters: 0-9, A-Z, a-z, space, - * hyphen, underscore, period, parentheses, apostrophe, - * ampersand, plus and hash. - * This restricts the contract name to a subset of ASCII characters, - * and excludes unicode characters for other alphabets and emoticons. - * Instead the default ERC1155 metadata read from the IPFS CID registry, - * should provide the full display name with unicode characters. - * Names are not checked for uniqueness. - */ - function _isValidName(string memory _name) public pure returns (bool) { - bytes memory nameBytes = bytes(_name); - if (nameBytes.length > 32 || nameBytes.length == 0) return false; // Check length - - for (uint256 i = 0; i < nameBytes.length; i++) { - bytes1 char = nameBytes[i]; - if ( - !(char >= 0x30 && char <= 0x39) // 0-9 - && !(char >= 0x41 && char <= 0x5A) // A-Z - && !(char >= 0x61 && char <= 0x7A) // a-z - && !(char == 0x20) // Space - && !(char == 0x2D || char == 0x5F) // Hyphen (-), Underscore (_) - && !(char == 0x2E) // Period (.) - && !(char == 0x28 || char == 0x29) // Parentheses ( () ) - && !(char == 0x27) // Apostrophe (') - && !(char == 0x26) // Ampersand (&) - && !(char == 0x2B || char == 0x23) // Plus (+), Hash (#) - ) { - return false; - } - } - return true; - } - - /** - * @dev checks whether string is a valid symbol by checking - * the length as max 16 bytes and the allowed characters: 0-9, A-Z, a-z, - * hyphen, underscore. - */ - function _isValidSymbol(string memory _symbol) public pure returns (bool) { - bytes memory symbolBytes = bytes(_symbol); - if (symbolBytes.length == 0 || symbolBytes.length > 16) { - return false; // Check length is within range - } - - for (uint256 i = 0; i < symbolBytes.length; i++) { - bytes1 char = symbolBytes[i]; - if ( - // allowed ASCII characters 0-9, A-Z, a-z, Hyphen (-), Underscore (_) - !( - (char >= 0x30 && char <= 0x39) || (char >= 0x41 && char <= 0x5A) || (char >= 0x61 && char <= 0x7A) - || (char == 0x2D) || (char == 0x5F) - ) - ) { - return false; - } - } - return true; - } - /** * @dev Internal function to upsert a trust marker for a truster and a trusted address. * It will initialize the linked list for the truster if it does not exist yet. From c72c75e86dc0de200e761dda45f5323587dc4d65 Mon Sep 17 00:00:00 2001 From: jaensen <4954577+jaensen@users.noreply.github.com> Date: Wed, 7 Feb 2024 23:26:09 +0100 Subject: [PATCH 2/2] fix: inverted v1 token stopped check --- src/hub/Hub.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hub/Hub.sol b/src/hub/Hub.sol index 49822dd..dabb8a6 100644 --- a/src/hub/Hub.sol +++ b/src/hub/Hub.sol @@ -210,7 +210,7 @@ contract Hub is Circles { function registerHuman(bytes32 _cidV0Digest) external duringBootstrap { // only available for v1 users with stopped v1 mint, for initial bootstrap period address v1CirclesStatus = _registerHuman(msg.sender); - require(v1CirclesStatus != CIRCLES_STOPPED_V1, "Avatar must have stopped v1 Circles contract."); + require(v1CirclesStatus == CIRCLES_STOPPED_V1, "Avatar must have stopped v1 Circles contract."); // store the IPFS CIDv0 digest for the avatar metadata tokenIdToCidV0Digest[_toTokenId(msg.sender)] = _cidV0Digest;