diff --git a/CHANGELOG.md b/CHANGELOG.md index 01e9958..dc4faf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## latest + +- [PR141] consented flow with bi-directional trust, with opt-out to v1 protocol of uni-directional trust +- [PR138] storage-based reentrency guard for operateFlowMatrix +- [PR136] total supply, name and symbol for ERC1155 (out of specification) + ## v0.3.3 - [PR132] bug fix in groupMint(); initial test coverage for group mint diff --git a/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/chiado-0.3.3-alpha-7b78c74-240412-151200.log b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/chiado-0.3.3-alpha-7b78c74-240412-151200.log new file mode 100644 index 0000000..f3a29b3 --- /dev/null +++ b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/chiado-0.3.3-alpha-7b78c74-240412-151200.log @@ -0,0 +1,18 @@ +Chiado deployment +================= +Deployment Date: 2024-04-12 15:12:00 +Version: 0.3.3-alpha +Git Commit: 7b78c749b91175265f2cf934fcc4f51a2085358d +Deployer Address: 0x7619F26728Ced663E50E578EB6ff42430931564c, Intitial nonce: 108 +Compiler Version: v0.8.23+commit.f704f362 + +Deployed Contracts: +Hub: 0xFFfbD3E62203B888bb8E09c1fcAcE58242674964 +Migration: 0x9c7E2AfAc052743944d5d8F80Db90E1E14A663aE +NameRegistry: 0x0A1D308a39A6dF8972A972E586E4b4b3Dc73520f +ERC20Lift: 0xaA20efff0511175C091F156D095DD3b62951Be48 +StandardTreasury: 0x97c28CCC374570C2b10f7569197919418D702F99 +BaseGroupMintPolicy: 0x95fbD8D52c18b6AA453e9cB06851543f125DCedE +MastercopyDemurrageERC20: 0x44A47EC79B45A13e884328018320D90DE86Bb932 +MastercopyInflationaryERC20: 0xb4A1012a0564a25dF5a8311d1B6DF47b098BdfdB +MastercopyStandardVault: 0x8E0115358DC2Dc984c0C1d9530BEE19616492177 diff --git a/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/chiado-artefacts-0.3.3-alpha-7b78c74-240412-151200.txt b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/chiado-artefacts-0.3.3-alpha-7b78c74-240412-151200.txt new file mode 100644 index 0000000..9b59f82 --- /dev/null +++ b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/chiado-artefacts-0.3.3-alpha-7b78c74-240412-151200.txt @@ -0,0 +1,9 @@ +{"contractName":"Hub","deployedAddress":"0xFFfbD3E62203B888bb8E09c1fcAcE58242674964","sourcePath":"src/hub/Hub.sol:Hub","constructor-args":"0xdbF22D4e8962Db3b2F1d9Ff55be728A887e47710 0x0A1D308a39A6dF8972A972E586E4b4b3Dc73520f 0x9c7E2AfAc052743944d5d8F80Db90E1E14A663aE 0xaA20efff0511175C091F156D095DD3b62951Be48 0x97c28CCC374570C2b10f7569197919418D702F99 1675209600 31540000 https://fallback.aboutcircles.com/v1/circles/{id}.json","argumentsFile":"constructorArgs_Hub.txt"} +{"contractName":"Migration","deployedAddress":"0x9c7E2AfAc052743944d5d8F80Db90E1E14A663aE","sourcePath":"src/migration/Migration.sol:Migration","constructor-args":"0xdbF22D4e8962Db3b2F1d9Ff55be728A887e47710 0xFFfbD3E62203B888bb8E09c1fcAcE58242674964 1675209600","argumentsFile":"constructorArgs_Migration.txt"} +{"contractName":"NameRegistry","deployedAddress":"0x0A1D308a39A6dF8972A972E586E4b4b3Dc73520f","sourcePath":"src/names/NameRegistry.sol:NameRegistry","constructor-args":"0xFFfbD3E62203B888bb8E09c1fcAcE58242674964","argumentsFile":"constructorArgs_NameRegistry.txt"} +{"contractName":"ERC20Lift","deployedAddress":"0xaA20efff0511175C091F156D095DD3b62951Be48","sourcePath":"src/lift/ERC20Lift.sol:ERC20Lift","constructor-args":"0xFFfbD3E62203B888bb8E09c1fcAcE58242674964 0x0A1D308a39A6dF8972A972E586E4b4b3Dc73520f 0x44A47EC79B45A13e884328018320D90DE86Bb932 0xb4A1012a0564a25dF5a8311d1B6DF47b098BdfdB","argumentsFile":"constructorArgs_ERC20Lift.txt"} +{"contractName":"StandardTreasury","deployedAddress":"0x97c28CCC374570C2b10f7569197919418D702F99","sourcePath":"src/treasury/StandardTreasury.sol:StandardTreasury","constructor-args":"0xFFfbD3E62203B888bb8E09c1fcAcE58242674964 0x8E0115358DC2Dc984c0C1d9530BEE19616492177","argumentsFile":"constructorArgs_StandardTreasury.txt"} +{"contractName":"BaseGroupMintPolicy","deployedAddress":"0x95fbD8D52c18b6AA453e9cB06851543f125DCedE","sourcePath":"src/groups/BaseMintPolicy.sol:MintPolicy","constructor-args":"","argumentsFile":"constructorArgs_BaseGroupMintPolicy.txt"} +{"contractName":"MastercopyDemurrageERC20","deployedAddress":"0x44A47EC79B45A13e884328018320D90DE86Bb932","sourcePath":"src/lift/DemurrageCircles.sol:DemurrageCircles","constructor-args":"","argumentsFile":"constructorArgs_MastercopyDemurrageERC20.txt"} +{"contractName":"MastercopyInflationaryERC20","deployedAddress":"0xb4A1012a0564a25dF5a8311d1B6DF47b098BdfdB","sourcePath":"src/lift/InflationaryCircles.sol:InflationaryCircles","constructor-args":"","argumentsFile":"constructorArgs_MastercopyInflationaryERC20.txt"} +{"contractName":"MastercopyStandardVault","deployedAddress":"0x8E0115358DC2Dc984c0C1d9530BEE19616492177","sourcePath":"src/treasury/StandardVault.sol:StandardVault","constructor-args":"","argumentsFile":"constructorArgs_MastercopyStandardVault.txt"} diff --git a/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_BaseGroupMintPolicy.txt b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_BaseGroupMintPolicy.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_BaseGroupMintPolicy.txt @@ -0,0 +1 @@ + diff --git a/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_ERC20Lift.txt b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_ERC20Lift.txt new file mode 100644 index 0000000..c866ccf --- /dev/null +++ b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_ERC20Lift.txt @@ -0,0 +1 @@ +0xFFfbD3E62203B888bb8E09c1fcAcE58242674964 0x0A1D308a39A6dF8972A972E586E4b4b3Dc73520f 0x44A47EC79B45A13e884328018320D90DE86Bb932 0xb4A1012a0564a25dF5a8311d1B6DF47b098BdfdB diff --git a/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_Hub.txt b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_Hub.txt new file mode 100644 index 0000000..81d94ae --- /dev/null +++ b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_Hub.txt @@ -0,0 +1 @@ +0xdbF22D4e8962Db3b2F1d9Ff55be728A887e47710 0x0A1D308a39A6dF8972A972E586E4b4b3Dc73520f 0x9c7E2AfAc052743944d5d8F80Db90E1E14A663aE 0xaA20efff0511175C091F156D095DD3b62951Be48 0x97c28CCC374570C2b10f7569197919418D702F99 1675209600 31540000 https://fallback.aboutcircles.com/v1/circles/{id}.json diff --git a/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_MastercopyDemurrageERC20.txt b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_MastercopyDemurrageERC20.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_MastercopyDemurrageERC20.txt @@ -0,0 +1 @@ + diff --git a/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_MastercopyInflationaryERC20.txt b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_MastercopyInflationaryERC20.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_MastercopyInflationaryERC20.txt @@ -0,0 +1 @@ + diff --git a/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_MastercopyStandardVault.txt b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_MastercopyStandardVault.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_MastercopyStandardVault.txt @@ -0,0 +1 @@ + diff --git a/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_Migration.txt b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_Migration.txt new file mode 100644 index 0000000..80db1de --- /dev/null +++ b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_Migration.txt @@ -0,0 +1 @@ +0xdbF22D4e8962Db3b2F1d9Ff55be728A887e47710 0xFFfbD3E62203B888bb8E09c1fcAcE58242674964 1675209600 diff --git a/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_NameRegistry.txt b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_NameRegistry.txt new file mode 100644 index 0000000..9b3ccd8 --- /dev/null +++ b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_NameRegistry.txt @@ -0,0 +1 @@ +0xFFfbD3E62203B888bb8E09c1fcAcE58242674964 diff --git a/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_StandardTreasury.txt b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_StandardTreasury.txt new file mode 100644 index 0000000..eef2fdd --- /dev/null +++ b/script/deployments/chiado-0.3.3-alpha-7b78c74-240412-151200/constructorArgs_StandardTreasury.txt @@ -0,0 +1 @@ +0xFFfbD3E62203B888bb8E09c1fcAcE58242674964 0x8E0115358DC2Dc984c0C1d9530BEE19616492177 diff --git a/specifications/TCIP010-erc1155-path-transfer b/specifications/TCIP010-erc1155-path-transfer.md similarity index 100% rename from specifications/TCIP010-erc1155-path-transfer rename to specifications/TCIP010-erc1155-path-transfer.md diff --git a/src/circles/Circles.sol b/src/circles/Circles.sol index 2063a7a..dfb2571 100644 --- a/src/circles/Circles.sol +++ b/src/circles/Circles.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.24; +import "../errors/Errors.sol"; import "../lib/Math64x64.sol"; import "./ERC1155.sol"; -contract Circles is ERC1155 { +contract Circles is ERC1155, ICirclesErrors { // Type declarations /** @@ -56,7 +57,7 @@ contract Circles is ERC1155 { * and the status of the v1 Circles minting. * @dev This is used to store the last mint time for each avatar. */ - mapping(address => MintTime) public mintTimes; + mapping(address => MintTime) internal mintTimes; // Events @@ -74,8 +75,6 @@ contract Circles is ERC1155 { DiscountedBalances(_inflation_day_zero) {} - // External functions - // Public functions /** @@ -142,13 +141,50 @@ contract Circles is ERC1155 { return; } // mint personal Circles to the human - _mint(_human, toTokenId(_human), issuance, ""); + _mintAndUpdateTotalSupply(_human, toTokenId(_human), issuance, ""); // update the last mint time mintTimes[_human].lastMintTime = uint96(block.timestamp); emit PersonalMint(_human, issuance, startPeriod, endPeriod); } + function _mintAndUpdateTotalSupply(address _account, uint256 _id, uint256 _value, bytes memory _data) internal { + _mint(_account, _id, _value, _data); + + uint64 today = day(block.timestamp); + DiscountedBalance storage totalSupplyBalance = discountedTotalSupplies[_id]; + uint256 newTotalSupply = + _calculateDiscountedBalance(totalSupplyBalance.balance, today - totalSupplyBalance.lastUpdatedDay) + _value; + if (newTotalSupply > MAX_VALUE) { + // DiscountedBalances: balance exceeds maximum value + revert CirclesERC1155AmountExceedsMaxUint190(_account, _id, newTotalSupply, 2); + } + totalSupplyBalance.balance = uint192(newTotalSupply); + totalSupplyBalance.lastUpdatedDay = today; + } + + function _burnAndUpdateTotalSupply(address _account, uint256 _id, uint256 _value) internal { + // _update will discount the balance before subtracting the value + _burn(_account, _id, _value); + + uint64 today = day(block.timestamp); + DiscountedBalance storage totalSupplyBalance = discountedTotalSupplies[_id]; + uint256 discountedTotalSupply = + _calculateDiscountedBalance(totalSupplyBalance.balance, today - totalSupplyBalance.lastUpdatedDay); + if (discountedTotalSupply < _value) { + // Logically impossible to burn more than the total supply + // however if the total supply nears dust, the discounting of the balance + // and the total supply might differ on the least significant bits. + // There is no good way to handle this, so user should burn a few attoCRC less, + // or wait a day for the total supply to be discounted to zero automatically. + revert CirclesLogicAssertion(4); + } + unchecked { + totalSupplyBalance.balance = uint192(discountedTotalSupply - _value); + } + totalSupplyBalance.lastUpdatedDay = today; + } + // Private functions /** diff --git a/src/circles/DiscountedBalances.sol b/src/circles/DiscountedBalances.sol index f95c00f..0b6e16a 100644 --- a/src/circles/DiscountedBalances.sol +++ b/src/circles/DiscountedBalances.sol @@ -11,7 +11,12 @@ contract DiscountedBalances is Demurrage { * @dev stores the discounted balances of the accounts privately. * Mapping from Circles identifiers to accounts to the discounted balance. */ - mapping(uint256 => mapping(address => DiscountedBalance)) public discountedBalances; + mapping(uint256 => mapping(address => DiscountedBalance)) internal discountedBalances; + + /** + * @dev stores the total supply for each Circles identifier + */ + mapping(uint256 => DiscountedBalance) internal discountedTotalSupplies; // Constructor @@ -36,17 +41,17 @@ contract DiscountedBalances is Demurrage { return _calculateDiscountedBalance(discountedBalance.balance, _day - discountedBalance.lastUpdatedDay); } - // Internal functions + /** + * @notice Total supply of a Circles identifier. + * @param _id Circles identifier for which to calculate the total supply + */ + function totalSupply(uint256 _id) public view returns (uint256) { + DiscountedBalance memory totalSupplyBalance = discountedTotalSupplies[_id]; + uint64 today = day(block.timestamp); + return _calculateDiscountedBalance(totalSupplyBalance.balance, today - totalSupplyBalance.lastUpdatedDay); + } - // /** - // * @dev Calculate the inflationary balance of a discounted balance - // * @param _account Address of the account to calculate the balance of - // * @param _id Circles identifier for which to calculate the balance - // */ - // function _inflationaryBalanceOf(address _account, uint256 _id) internal view returns (uint256) { - // DiscountedBalance memory discountedBalance = discountedBalances[_id][_account]; - // return _calculateInflationaryBalance(discountedBalance.balance, discountedBalance.lastUpdatedDay); - // } + // Internal functions /** * @dev Update the balance of an account for a given Circles identifier diff --git a/src/circles/ERC1155.sol b/src/circles/ERC1155.sol index fec60c6..0e700be 100644 --- a/src/circles/ERC1155.sol +++ b/src/circles/ERC1155.sol @@ -341,12 +341,12 @@ abstract contract ERC1155 is DiscountedBalances, Context, ERC165, IERC1155, IERC * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the * acceptance magic value. */ - function _mintBatch(address to, uint256[] memory ids, uint256[] memory values, bytes memory data) internal { - if (to == address(0)) { - revert ERC1155InvalidReceiver(address(0)); - } - _updateWithAcceptanceCheck(address(0), to, ids, values, data); - } + // function _mintBatch(address to, uint256[] memory ids, uint256[] memory values, bytes memory data) internal { + // if (to == address(0)) { + // revert ERC1155InvalidReceiver(address(0)); + // } + // _updateWithAcceptanceCheck(address(0), to, ids, values, data); + // } /** * @dev Destroys a `value` amount of tokens of type `id` from `from` @@ -376,13 +376,14 @@ abstract contract ERC1155 is DiscountedBalances, Context, ERC165, IERC1155, IERC * - `from` cannot be the zero address. * - `from` must have at least `value` amount of tokens of type `id`. * - `ids` and `values` must have the same length. + * // */ - function _burnBatch(address from, uint256[] memory ids, uint256[] memory values) internal { - if (from == address(0)) { - revert ERC1155InvalidSender(address(0)); - } - _updateWithAcceptanceCheck(from, address(0), ids, values, ""); - } + // function _burnBatch(address from, uint256[] memory ids, uint256[] memory values) internal { + // if (from == address(0)) { + // revert ERC1155InvalidSender(address(0)); + // } + // _updateWithAcceptanceCheck(from, address(0), ids, values, ""); + // } /** * @dev Approve `operator` to operate on all of `owner` tokens diff --git a/src/hub/Hub.sol b/src/hub/Hub.sol index b7926c2..b264553 100644 --- a/src/hub/Hub.sol +++ b/src/hub/Hub.sol @@ -21,7 +21,7 @@ import "./MetadataDefinitions.sol"; * It further allows to wrap any token into an inflationary or demurraged * ERC20 Circles contract. */ -contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { +contract Hub is Circles, MetadataDefinitions, IHubErrors { // Type declarations /** @@ -67,25 +67,37 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { // State variables + // /** + // * @notice The global name of Circles. + // * todo, change this to "Circles" for the production deployment + // */ + // string public name = "Rings"; + + // /** + // * @notice The global symbol ticker for Circles. + // * todo, change this to "CRC" for the production deployment + // */ + // string public symbol = "RING"; + /** * @notice The Hub v1 contract address. */ - IHubV1 public immutable hubV1; + IHubV1 internal immutable hubV1; /** * @notice The name registry contract address. */ - INameRegistry public nameRegistry; + INameRegistry internal nameRegistry; /** * @notice The address of the migration contract for v1 Circles. */ - address public migration; + address internal migration; /** * @notice The address of the Lift ERC20 contract. */ - IERC20Lift public liftERC20; + IERC20Lift internal liftERC20; /** * @notice The timestamp of the start of the invitation-only period. @@ -94,13 +106,13 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { * new avatars can be invited by registered avatars. After this time * only registered avatars can invite new avatars. */ - uint256 public immutable invitationOnlyTime; + uint256 internal immutable invitationOnlyTime; /** * @notice The standard treasury contract address used when * registering a (non-custom) group. */ - address public standardTreasury; + address internal standardTreasury; /** * @notice The mapping of registered avatar addresses to the next avatar address, @@ -179,8 +191,8 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { * see https://soliditylang.org/blog/2024/01/26/transient-storage/ */ modifier nonReentrant(uint8 _code) { - // todo: this should use transient storage slot - // but didn't compile; investigate + // todo: this should use a transient storage slot + // but doesn't compile through `forge build`, but does compile with solc directly // assembly { // if tload(0) { revert(0, 0) } // tstore(0, 1) @@ -210,8 +222,8 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { * or in unix time 1602786330 (deployment at 6:25:30 pm UTC) - 66330 (offset to midnight) = 1602720000. * @param _standardTreasury address of the standard treasury contract * @param _bootstrapTime duration of the bootstrap period (for v1 registration) in seconds - * @param _fallbackUri fallback URI string for the ERC1155 metadata, - * (todo: eg. "https://fallback.aboutcircles.com/v1/circles/{id}.json") + * @param _gatewayUrl gateway URL string for the ERC1155 metadata mirroring IPFS metadata storage + * (eg. "https://gateway.aboutcircles.com/v2/circles/{id}.json") */ constructor( IHubV1 _hubV1, @@ -221,8 +233,8 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { address _standardTreasury, uint256 _inflationDayZero, uint256 _bootstrapTime, - string memory _fallbackUri - ) Circles(_inflationDayZero, _fallbackUri) { + string memory _gatewayUrl + ) Circles(_inflationDayZero, _gatewayUrl) { if (address(_hubV1) == address(0)) { revert CirclesAddressCannotBeZero(0); } @@ -257,18 +269,20 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { /** * @notice Register human allows to register an avatar for a human, * if they have a stopped v1 Circles contract, during the bootstrap period. - * @param _cidV0Digest (optional) IPFS CIDv0 digest for the avatar metadata + * @param _metatdataDigest (optional) sha256 metadata digest for the avatar metadata * should follow ERC1155 metadata standard. */ - function registerHuman(bytes32 _cidV0Digest) external onlyDuringBootstrap(0) { + function registerHuman(bytes32 _metatdataDigest) external onlyDuringBootstrap(0) { // only available for v1 users with stopped v1 mint, for initial bootstrap period address v1CirclesStatus = _registerHuman(msg.sender); if (v1CirclesStatus != CIRCLES_STOPPED_V1) { revert CirclesHubRegisterAvatarV1MustBeStopped(msg.sender, 0); } - // store the IPFS CIDv0 digest for the avatar metadata - nameRegistry.updateCidV0Digest(msg.sender, _cidV0Digest); + // store the metatdata digest for the avatar metadata + if (_metatdataDigest != bytes32(0)) { + nameRegistry.setMetadataDigest(msg.sender, _metatdataDigest); + } emit RegisterHuman(msg.sender); } @@ -290,11 +304,11 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { if (block.timestamp > invitationOnlyTime) { // after the bootstrap period, the inviter must burn the invitation cost - _burn(msg.sender, toTokenId(msg.sender), INVITATION_COST); + _burnAndUpdateTotalSupply(msg.sender, toTokenId(msg.sender), INVITATION_COST); // todo: re-discuss desired approach to welcome bonus vs migration // invited receives the welcome bonus in their personal Circles - _mint(_human, toTokenId(_human), WELCOME_BONUS, ""); + _mintAndUpdateTotalSupply(_human, toTokenId(_human), WELCOME_BONUS, ""); } // set trust to indefinite future, but avatar can edit this later @@ -308,9 +322,9 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { * @param _mint mint address will be called before minting group circles * @param _name immutable name of the group Circles * @param _symbol immutable symbol of the group Circles - * @param _cidV0Digest IPFS CIDv0 digest for the group metadata + * @param _metatdataDigest sha256 digest for the group metadata */ - function registerGroup(address _mint, string calldata _name, string calldata _symbol, bytes32 _cidV0Digest) + function registerGroup(address _mint, string calldata _name, string calldata _symbol, bytes32 _metatdataDigest) external { _registerGroup(msg.sender, _mint, standardTreasury, _name, _symbol); @@ -320,7 +334,7 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { nameRegistry.registerCustomSymbol(msg.sender, _symbol); // store the IPFS CIDv0 digest for the group metadata - nameRegistry.updateCidV0Digest(msg.sender, _cidV0Digest); + nameRegistry.setMetadataDigest(msg.sender, _metatdataDigest); emit RegisterGroup(msg.sender, _mint, standardTreasury, _name, _symbol); } @@ -331,14 +345,14 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { * @param _treasury treasury address for receiving collateral * @param _name immutable name of the group Circles * @param _symbol immutable symbol of the group Circles - * @param _cidV0Digest IPFS CIDv0 digest for the group metadata + * @param _metatdataDigest metadata digest for the group metadata */ function registerCustomGroup( address _mint, address _treasury, string calldata _name, string calldata _symbol, - bytes32 _cidV0Digest + bytes32 _metatdataDigest ) external { _registerGroup(msg.sender, _mint, _treasury, _name, _symbol); @@ -346,8 +360,8 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { nameRegistry.registerCustomName(msg.sender, _name); nameRegistry.registerCustomSymbol(msg.sender, _symbol); - // store the IPFS CIDv0 digest for the group metadata - nameRegistry.updateCidV0Digest(msg.sender, _cidV0Digest); + // store the metatdata digest for the group metadata + nameRegistry.setMetadataDigest(msg.sender, _metatdataDigest); emit RegisterGroup(msg.sender, _mint, _treasury, _name, _symbol); } @@ -355,16 +369,16 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { /** * @notice Register organization allows to register an organization avatar. * @param _name name of the organization - * @param _cidV0Digest IPFS CIDv0 digest for the organization metadata + * @param _metatdataDigest Metadata digest for the organization metadata */ - function registerOrganization(string calldata _name, bytes32 _cidV0Digest) external { + function registerOrganization(string calldata _name, bytes32 _metatdataDigest) external { _insertAvatar(msg.sender); // for organizations, only register possible custom name nameRegistry.registerCustomName(msg.sender, _name); // store the IPFS CIDv0 digest for the organization metadata - nameRegistry.updateCidV0Digest(msg.sender, _cidV0Digest); + nameRegistry.setMetadataDigest(msg.sender, _metatdataDigest); emit RegisterOrganization(msg.sender, _name); } @@ -510,12 +524,12 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { // Only humans can migrate v1 tokens after the bootstrap period. revert CirclesHubMustBeHuman(_owner, 4); } - _burn(_owner, toTokenId(_owner), cost); + _burnAndUpdateTotalSupply(_owner, toTokenId(_owner), cost); } for (uint256 i = 0; i < _avatars.length; i++) { // mint the migrated balances to _owner - _mint(_owner, toTokenId(_avatars[i]), _amounts[i], ""); + _mintAndUpdateTotalSupply(_owner, toTokenId(_avatars[i]), _amounts[i], ""); } } @@ -540,7 +554,7 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { revert CirclesHubGroupMintPolicyRejectedBurn(msg.sender, group, _amount, _data, 0); } } - _burn(msg.sender, _id, _amount); + _burnAndUpdateTotalSupply(msg.sender, _id, _amount); } function wrap(address _avatar, uint256 _amount, CirclesType _type) external returns (address) { @@ -647,18 +661,6 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { return uint256(trustMarkers[_circlesAvatar][_to].expiry) >= block.timestamp; } - /** - * uri returns the IPFS URI for the ERC1155 token. - * If the - * @param _id tokenId of the ERC1155 token - */ - function uri(uint256 _id) public view override returns (string memory uri_) { - // todo: should fallback move into SDK rather than contract ? - // todo: we don't need to override this function if we keep this pattern - // "https://fallback.aboutcircles.com/v1/profile/{id}.json" - return super.uri(_id); - } - // Internal functions /** @@ -697,7 +699,7 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { // _groupMint is only called from the public groupMint function, // or from operateFlowMatrix, and both ensure the collateral ids are derived // from an address, so we can cast here without checks. - if (!isPermittedFlow(_group, address(uint160(_collateral[i])))) { + if (!isPermittedFlow(_group, _validateAddressFromId(_collateral[i], 2))) { // Group does not trust collateral. revert CirclesHubFlowEdgeIsNotPermitted(_group, _collateral[i], 0); } @@ -725,7 +727,7 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { safeBatchTransferFrom(_sender, treasuries[_group], _collateral, _amounts, dataWithGroup); // mint group Circles to the sender and send the original _data onwards - _mint(_sender, toTokenId(_group), sumAmounts, _data); + _mintAndUpdateTotalSupply(_sender, toTokenId(_group), sumAmounts, _data); } function _verifyFlowMatrix( @@ -1103,12 +1105,11 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors { } function _validateAddressFromId(uint256 _id, uint8 _code) internal pure returns (address) { - address avatar = address(uint160(_id)); - if (uint256(uint160(avatar)) != _id) { + if (_id > type(uint160).max) { // Invalid Circles identifier, not derived from address revert CirclesIdMustBeDerivedFromAddress(_id, _code); } - return avatar; + return address(uint160(_id)); } /** diff --git a/src/lift/ERC20DiscountedBalances.sol b/src/lift/ERC20DiscountedBalances.sol index 32f93ca..4454a92 100644 --- a/src/lift/ERC20DiscountedBalances.sol +++ b/src/lift/ERC20DiscountedBalances.sol @@ -97,7 +97,7 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 { ) + _value; if (newBalance > MAX_VALUE) { // Balance exceeds maximum value. - revert CirclesERC1155AmountExceedsMaxUint190(_account, 0, newBalance, 0); + revert CirclesERC1155AmountExceedsMaxUint190(_account, 0, newBalance, 1); } discountedBalance.balance = uint192(newBalance); discountedBalance.lastUpdatedDay = _day; diff --git a/src/names/Base58Converter.sol b/src/names/Base58Converter.sol index a34383b..31a2628 100644 --- a/src/names/Base58Converter.sol +++ b/src/names/Base58Converter.sol @@ -2,20 +2,47 @@ pragma solidity >=0.8.24; contract Base58Converter { + // Constants + string internal constant ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; - function toBase58(uint256 _data) internal pure returns (string memory) { - bytes memory b58 = new bytes(64); // More than enough length + uint256 internal constant FIXED_SHORT_NAME_LENGTH = 12; + + uint256 internal constant ALPHABET_LENGTH = 58; + + // Internal functions + + function _toBase58(uint256 _data) internal pure returns (string memory) { + // Initialize enough length, only called in view functions + // so no need to optimize gas costs + bytes memory b58 = new bytes(64); uint256 i = 0; - while (_data > 0) { - uint256 mod = _data % 58; + // Ensure the loop runs at least once, + // even if the input is 0 return "1" + while (_data > 0 || i == 0) { + uint256 mod = _data % ALPHABET_LENGTH; b58[i++] = bytes(ALPHABET)[mod]; - _data = _data / 58; + _data = _data / ALPHABET_LENGTH; } // Reverse the string since the encoding works backwards return string(_reverse(b58, i)); } + function _toBase58WithPadding(uint256 _data) internal pure returns (string memory) { + bytes memory b58 = new bytes(FIXED_SHORT_NAME_LENGTH); // Fixed length for short name + uint256 i = 0; + while (_data > 0 || i == 0) { + uint256 mod = _data % ALPHABET_LENGTH; + b58[i++] = bytes(ALPHABET)[mod]; + _data /= ALPHABET_LENGTH; + } + while (i < FIXED_SHORT_NAME_LENGTH) { + // Ensure the output is exactly 12 characters + b58[i++] = bytes(ALPHABET)[0]; // '1' in base58 represents the value 0 + } + return string(_reverse(b58, i)); + } + function _reverse(bytes memory _b, uint256 _len) internal pure returns (bytes memory) { bytes memory reversed = new bytes(_len); for (uint256 i = 0; i < _len; i++) { diff --git a/src/names/INameRegistry.sol b/src/names/INameRegistry.sol index a79a237..496c121 100644 --- a/src/names/INameRegistry.sol +++ b/src/names/INameRegistry.sol @@ -2,12 +2,13 @@ pragma solidity >=0.8.24; interface INameRegistry { - function updateCidV0Digest(address avatar, bytes32 cidVoDigest) external; + function setMetadataDigest(address avatar, bytes32 metadataDigest) external; function registerCustomName(address avatar, string calldata name) external; function registerCustomSymbol(address avatar, string calldata symbol) external; function name(address avatar) external view returns (string memory); function symbol(address avatar) external view returns (string memory); + function getMetadataDigest(address _avatar) external view returns (bytes32); function isValidName(string calldata name) external pure returns (bool); function isValidSymbol(string calldata symbol) external pure returns (bool); diff --git a/src/names/NameRegistry.sol b/src/names/NameRegistry.sol index 9ab8d58..7200332 100644 --- a/src/names/NameRegistry.sol +++ b/src/names/NameRegistry.sol @@ -27,6 +27,11 @@ contract NameRegistry is Base58Converter, INameRegistry, INameRegistryErrors, IC */ string public constant DEFAULT_CIRCLES_SYMBOL = "RING"; + // /** + // * @dev The IPFS protocol prefix for cid v0 resolution + // */ + // string private constant IPFS_PROTOCOL = "ipfs://Qm"; + // State variables /** @@ -50,15 +55,16 @@ contract NameRegistry is Base58Converter, INameRegistry, INameRegistryErrors, IC mapping(address => string) public customSymbols; /** - * @notice avatarToCidV0Digest is a mapping of avatar to the IPFS CIDv0 digest. + * @notice avatarToMetaDataDigest is a mapping of avatar to the sha256 digest + * of their latest ERC1155 metadata. */ - mapping(address => bytes32) public avatarToCidV0Digest; + mapping(address => bytes32) public avatarToMetaDataDigest; // Events event RegisterShortName(address indexed avatar, uint72 shortName, uint256 nonce); - event CidV0(address indexed avatar, bytes32 cidV0Digest); + event UpdateMetadataDigest(address indexed avatar, bytes32 metadataDigest); // Modifiers @@ -92,14 +98,7 @@ contract NameRegistry is Base58Converter, INameRegistry, INameRegistryErrors, IC * @notice Register a short name for the avatar */ function registerShortName() external mustBeRegistered(msg.sender, 0) { - (uint72 shortName, uint256 nonce) = searchShortName(msg.sender); - - // assign the name to the address - shortNames[msg.sender] = shortName; - // assign the address to the name - shortNameToAvatar[shortName] = msg.sender; - - emit RegisterShortName(msg.sender, shortName, nonce); + _registerShortName(); } /** @@ -107,28 +106,15 @@ contract NameRegistry is Base58Converter, INameRegistry, INameRegistryErrors, IC * @param _nonce nonce to be used in the calculation */ function registerShortNameWithNonce(uint256 _nonce) external mustBeRegistered(msg.sender, 1) { - if (shortNames[msg.sender] != uint72(0)) { - revert CirclesNamesShortNameAlreadyAssigned(msg.sender, shortNames[msg.sender], 0); - } - - uint72 shortName = calculateShortNameWithNonce(msg.sender, _nonce); - - if (shortNameToAvatar[shortName] != address(0)) { - revert CirclesNamesShortNameWithNonceTaken(msg.sender, _nonce, shortName, shortNameToAvatar[shortName]); - } - - // assign the name to the address - shortNames[msg.sender] = shortName; - // assign the address to the name - shortNameToAvatar[shortName] = msg.sender; - - emit RegisterShortName(msg.sender, shortName, _nonce); + _registerShortNameWithNonce(_nonce); } - function updateCidV0Digest(address _avatar, bytes32 _cidV0Digest) external onlyHub(0) { - avatarToCidV0Digest[_avatar] = _cidV0Digest; + function setMetadataDigest(address _avatar, bytes32 _metadataDigest) external onlyHub(0) { + _setMetadataDigest(_avatar, _metadataDigest); + } - emit CidV0(_avatar, _cidV0Digest); + function updateMetadataDigest(bytes32 _metadataDigest) external mustBeRegistered(msg.sender, 2) { + _setMetadataDigest(msg.sender, _metadataDigest); } function registerCustomName(address _avatar, string calldata _name) external onlyHub(1) { @@ -153,7 +139,7 @@ contract NameRegistry is Base58Converter, INameRegistry, INameRegistryErrors, IC customSymbols[_avatar] = _symbol; } - function name(address _avatar) external view mustBeRegistered(_avatar, 1) returns (string memory) { + function name(address _avatar) external view mustBeRegistered(_avatar, 3) returns (string memory) { if (!hub.isHuman(_avatar)) { // groups and organizations can have set a custom name string memory customName = customNames[_avatar]; @@ -164,16 +150,10 @@ contract NameRegistry is Base58Converter, INameRegistry, INameRegistryErrors, IC // otherwise, use the default name for groups and organizations } // for personal Circles use default name - uint72 shortName = shortNames[_avatar]; - if (shortName == uint72(0)) { - string memory base58FullAddress = toBase58(uint256(uint160(_avatar))); - return string(abi.encodePacked(DEFAULT_CIRCLES_NAME_PREFIX, base58FullAddress)); - } - string memory base58ShortName = toBase58(uint256(shortName)); - return string(abi.encodePacked(DEFAULT_CIRCLES_NAME_PREFIX, base58ShortName)); + return _getShortOrLongName(_avatar); } - function symbol(address _avatar) external view mustBeRegistered(_avatar, 2) returns (string memory) { + function symbol(address _avatar) external view mustBeRegistered(_avatar, 4) returns (string memory) { if (hub.isOrganization(_avatar)) { revert CirclesNamesOrganizationHasNoSymbol(_avatar, 0); } @@ -190,6 +170,10 @@ contract NameRegistry is Base58Converter, INameRegistry, INameRegistryErrors, IC return DEFAULT_CIRCLES_SYMBOL; } + function getMetadataDigest(address _avatar) external view returns (bytes32) { + return avatarToMetaDataDigest[_avatar]; + } + // Public functions /** @@ -225,7 +209,7 @@ contract NameRegistry is Base58Converter, INameRegistry, INameRegistryErrors, IC function calculateShortNameWithNonce(address _avatar, uint256 _nonce) public pure returns (uint72 shortName_) { // use keccak256 to generate a pseudo-random number bytes32 digest = keccak256(abi.encodePacked(_avatar, _nonce)); - // take the modulo of the digest to get a number between 0 and MAX_NAME + // take the modulo of the digest to get a number between 0 and MAX_SHORT_NAME shortName_ = uint72(uint256(digest) % (MAX_SHORT_NAME + 1)); } @@ -289,4 +273,51 @@ contract NameRegistry is Base58Converter, INameRegistry, INameRegistryErrors, IC } return true; } + + // Internal functions + + function _registerShortName() internal { + (uint72 shortName, uint256 nonce) = searchShortName(msg.sender); + + _storeShortName(msg.sender, shortName, nonce); + } + + function _registerShortNameWithNonce(uint256 _nonce) internal { + if (shortNames[msg.sender] != uint72(0)) { + revert CirclesNamesShortNameAlreadyAssigned(msg.sender, shortNames[msg.sender], 1); + } + + uint72 shortName = calculateShortNameWithNonce(msg.sender, _nonce); + + if (shortNameToAvatar[shortName] != address(0)) { + revert CirclesNamesShortNameWithNonceTaken(msg.sender, _nonce, shortName, shortNameToAvatar[shortName]); + } + + _storeShortName(msg.sender, shortName, _nonce); + } + + function _storeShortName(address _avatar, uint72 _shortName, uint256 _nonce) internal { + // assign the name to the address + shortNames[_avatar] = _shortName; + // assign the address to the name + shortNameToAvatar[_shortName] = _avatar; + + emit RegisterShortName(_avatar, _shortName, _nonce); + } + + function _getShortOrLongName(address _avatar) internal view returns (string memory) { + uint72 shortName = shortNames[_avatar]; + if (shortName == uint72(0)) { + string memory base58FullAddress = _toBase58(uint256(uint160(_avatar))); + return string(abi.encodePacked(DEFAULT_CIRCLES_NAME_PREFIX, base58FullAddress)); + } + string memory base58ShortName = _toBase58WithPadding(uint256(shortName)); + return string(abi.encodePacked(DEFAULT_CIRCLES_NAME_PREFIX, base58ShortName)); + } + + function _setMetadataDigest(address _avatar, bytes32 _metadataDigest) internal { + avatarToMetaDataDigest[_avatar] = _metadataDigest; + + emit UpdateMetadataDigest(_avatar, _metadataDigest); + } } diff --git a/test/groups/mintGroupCircles.t.sol b/test/groups/mintGroupCircles.t.sol index 1c22c53..c72bc25 100644 --- a/test/groups/mintGroupCircles.t.sol +++ b/test/groups/mintGroupCircles.t.sol @@ -10,7 +10,6 @@ import "./setup.sol"; contract MintGroupCirclesTest is Test, GroupSetup, IHubErrors { // State variables address group; - address group2; // Constructor diff --git a/test/names/MockNameRegistry.sol b/test/names/MockNameRegistry.sol new file mode 100644 index 0000000..3b27ac2 --- /dev/null +++ b/test/names/MockNameRegistry.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.24; + +import "../../src/names/NameRegistry.sol"; + +contract MockNameRegistry is NameRegistry { + // Constructor + + constructor() NameRegistry(IHubV2(address(1))) {} + + // External functions + + function registerShortNameNoChecks() external { + _registerShortName(); + } + + function registerShortNameWithNonceNoChecks(uint256 _nonce) external { + _registerShortNameWithNonce(_nonce); + } + + function registerCustomNameNoChecks(address _avatar, string calldata _name) external { + if (bytes(_name).length == 0) { + // if name is left empty, it will default to default name "Circles-" + return; + } + if (!isValidName(_name)) { + revert CirclesNamesInvalidName(_avatar, _name, 0); + } + customNames[_avatar] = _name; + } + + function registerCustomSymbolNoChecks(address _avatar, string calldata _symbol) external { + if (bytes(_symbol).length == 0) { + // if symbol is left empty, it will default to default symbol "CRC" + return; + } + if (!isValidSymbol(_symbol)) { + revert CirclesNamesInvalidName(_avatar, _symbol, 1); + } + customSymbols[_avatar] = _symbol; + } + + function setMetadataDigestNoChecks(address _avatar, bytes32 _metadataDigest) external { + _setMetadataDigest(_avatar, _metadataDigest); + } + + function getShortOrLongName(address _avatar) external view returns (string memory) { + return _getShortOrLongName(_avatar); + } + + function toBase58(uint256 _data) external pure returns (string memory) { + return _toBase58(_data); + } + + function toBase58WithPadding(uint256 _data) external pure returns (string memory) { + return _toBase58WithPadding(_data); + } + + function storeShortName(address _avatar, uint72 _shortName) external { + _storeShortName(_avatar, _shortName, 0); + } +} diff --git a/test/names/NameRegistry.t.sol b/test/names/NameRegistry.t.sol new file mode 100644 index 0000000..5e32234 --- /dev/null +++ b/test/names/NameRegistry.t.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {StdCheats} from "forge-std/StdCheats.sol"; +import "forge-std/console.sol"; +import "../setup/HumanRegistration.sol"; +import "./MockNameRegistry.sol"; + +contract NamesTest is Test, HumanRegistration { + // Constants + + // IPFS hash for Ubuntu 20.04, random CIDv0 + // string constant IPFS_CID_V0 = "ipfs://QmPK1s3pNYLi9ERiq3BDxKa4XosgWwFRQUydHUtz4YgpqB"; + // using https://cid.ipfs.tech/#QmPK1s3pNYLi9ERiq3BDxKa4XosgWwFRQUydHUtz4YgpqB + // this is the 32 bytes hash digest + bytes32 constant SHA256_DIGEST = 0x0E7071C59DF3B9454D1D18A15270AA36D54F89606A576DC621757AFD44AD1D2E; + + // State variables + + MockNameRegistry mockNameRegistry; + + // Constructor + + constructor() HumanRegistration(4) {} + + // Setup + + function setUp() public { + mockNameRegistry = new MockNameRegistry(); + } + + // Tests + + function testShortName() public { + // without a short name registered, first get the long name + string memory longName = mockNameRegistry.getShortOrLongName(addresses[0]); + assertEq(longName, "Rings-3fNX29VBXc9WSxAT2dG3RYSfj6uX"); + + // now register a short name + vm.prank(addresses[0]); + mockNameRegistry.registerShortNameNoChecks(); + + // and get the short name + string memory shortName = mockNameRegistry.getShortOrLongName(addresses[0]); + assertEq(shortName, "Rings-Q6sQpEYS9Dg1"); + + // can't register a second time + vm.expectRevert(); + vm.prank(addresses[0]); + mockNameRegistry.registerShortNameWithNonceNoChecks(839892892); + } + + function testShortNameWithNonce() public { + vm.prank(addresses[0]); + mockNameRegistry.registerShortNameWithNonceNoChecks(839892892); + string memory shortName = mockNameRegistry.getShortOrLongName(addresses[0]); + assertEq(shortName, "Rings-uNJGyf6sN6vY"); + } + + function testShortNameWithPadding() public { + // 42 converts to "j" in base58 + assertEq(mockNameRegistry.toBase58(42), "j"); + + // but as a short name it shold be padded to 12 characters + mockNameRegistry.storeShortName(addresses[0], 42); + string memory shortName = mockNameRegistry.getShortOrLongName(addresses[0]); + assertEq(shortName, "Rings-11111111111j"); + } + + function testBase58Conversion() public { + assertEq(mockNameRegistry.toBase58(0), "1"); + assertEq(mockNameRegistry.toBase58(mockNameRegistry.MAX_SHORT_NAME()), "zzzzzzzzzzzz"); + // longer names are not possible as tha calculation takes modulo MAX_SHORT_NAME + 1 + assertEq(mockNameRegistry.toBase58(mockNameRegistry.MAX_SHORT_NAME() + 1), "2111111111111"); + + assertEq(mockNameRegistry.toBase58(845156846445168), "7bmqZRAo1"); + assertEq(mockNameRegistry.toBase58(912670482714768333), "37sj6xwGEtL"); + + assertEq(mockNameRegistry.toBase58WithPadding(0), "111111111111"); + assertEq(mockNameRegistry.toBase58WithPadding(845156846445168), "1117bmqZRAo1"); + } + + function testCustomName() public { + mockNameRegistry.registerCustomNameNoChecks(addresses[0], "Circles"); + assertEq(mockNameRegistry.customNames(addresses[0]), "Circles"); + } + + function testInvalidCustomNames() public { + vm.expectRevert(); + mockNameRegistry.registerCustomNameNoChecks(addresses[0], "WeirdName=NotAllowed"); + } + + function testMetadataDigest() public { + mockNameRegistry.setMetadataDigestNoChecks(addresses[0], SHA256_DIGEST); + bytes32 digest = mockNameRegistry.getMetadataDigest(addresses[0]); + + assertEq(digest, SHA256_DIGEST); + } +}