-
Notifications
You must be signed in to change notification settings - Fork 1
Update comments and final pass on style #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,9 +13,11 @@ import {LibBit} from "solady/utils/LibBit.sol"; | |
| import {LibString} from "solady/utils/LibString.sol"; | ||
| import {SignatureCheckerLib} from "solady/utils/SignatureCheckerLib.sol"; | ||
|
|
||
| /// @notice Registry for builder codes | ||
| /// @title BuilderCodes | ||
| /// | ||
| /// @author Coinbase | ||
| /// @notice ERC-721 NFT for builders to register codes to identify their apps | ||
| /// | ||
| /// @author Coinbase (https://github.com/base/builder-codes) | ||
| contract BuilderCodes is | ||
| Initializable, | ||
| ERC721Upgradeable, | ||
|
|
@@ -26,14 +28,18 @@ contract BuilderCodes is | |
| IERC4906 | ||
| { | ||
| /// @notice EIP-712 storage structure for registry data | ||
| /// @custom:storage-location erc7201:base.flywheel.BuilderCodes | ||
| /// @custom:storage-location erc7201:base.BuilderCodes | ||
| struct RegistryStorage { | ||
| /// @notice Base URI for referral code metadata | ||
| /// @dev Base URI for builder code metadata | ||
| string uriPrefix; | ||
| /// @dev Mapping of referral code token IDs to payout recipients | ||
| /// @dev Mapping of builder code token IDs to payout recipients | ||
| mapping(uint256 tokenId => address payoutAddress) payoutAddresses; | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////// | ||
| /// Constants /// | ||
| //////////////////////////////////////////////////////////////// | ||
|
|
||
| /// @notice Role identifier for addresses authorized to call register or sign registrations | ||
| bytes32 public constant REGISTER_ROLE = keccak256("REGISTER_ROLE"); | ||
|
|
||
|
|
@@ -47,31 +53,39 @@ contract BuilderCodes is | |
| bytes32 public constant REGISTRATION_TYPEHASH = | ||
| keccak256("BuilderCodeRegistration(string code,address initialOwner,address payoutAddress,uint48 deadline)"); | ||
|
|
||
| /// @notice Allowed characters for referral codes | ||
| /// @notice Allowed characters for builder codes | ||
| string public constant ALLOWED_CHARACTERS = "0123456789abcdefghijklmnopqrstuvwxyz_"; | ||
|
|
||
| /// @notice Allowed characters for referral codes lookup | ||
| /// @notice Allowed characters for builder codes lookup | ||
| /// @dev LibString.to7BitASCIIAllowedLookup(ALLOWED_CHARACTERS) | ||
| uint128 public constant ALLOWED_CHARACTERS_LOOKUP = 10633823847437083212121898993101832192; | ||
|
|
||
| /// @notice EIP-1967 storage slot base for registry mapping using ERC-7201 | ||
| /// @dev keccak256(abi.encode(uint256(keccak256("base.flywheel.BuilderCodes")) - 1)) & ~bytes32(uint256(0xff)) | ||
| /// @dev keccak256(abi.encode(uint256(keccak256("base.BuilderCodes")) - 1)) & ~bytes32(uint256(0xff)) | ||
| bytes32 private constant REGISTRY_STORAGE_LOCATION = | ||
| 0xe3aaf266708e5133bd922e269bb5e8f72a7444c3b231cbf562ddc67a383e5700; | ||
| 0x015aa89e92b56dd64cffc1c9b26553e653b294bc48004bbcc753732d19b11100; | ||
|
|
||
| /// @notice Emitted when a referral code is registered | ||
| //////////////////////////////////////////////////////////////// | ||
| /// Events /// | ||
| //////////////////////////////////////////////////////////////// | ||
|
|
||
| /// @notice Emitted when a builder code is registered | ||
| /// | ||
| /// @param tokenId Token ID of the referral code | ||
| /// @param code Referral code | ||
| /// @param tokenId Token ID of the builder code | ||
| /// @param code Builder code | ||
| event CodeRegistered(uint256 indexed tokenId, string code); | ||
|
|
||
| /// @notice Emitted when a publisher's default payout address is updated | ||
| /// @notice Emitted when a payout address is updated | ||
| /// | ||
| /// @param tokenId Token ID of the referral code | ||
| /// @param payoutAddress New default payout address | ||
| /// @param tokenId Token ID of the builder code | ||
| /// @param payoutAddress New payout address | ||
| event PayoutAddressUpdated(uint256 indexed tokenId, address payoutAddress); | ||
|
|
||
| /// @notice Emits when the contract URI is updated (ERC-7572) | ||
| //////////////////////////////////////////////////////////////// | ||
| /// Errors /// | ||
| //////////////////////////////////////////////////////////////// | ||
|
|
||
| /// @notice Emitted when the contract URI is updated (ERC-7572) | ||
| event ContractURIUpdated(); | ||
|
|
||
| /// @notice Thrown when call doesn't have required permissions | ||
|
|
@@ -95,6 +109,10 @@ contract BuilderCodes is | |
| /// @notice Thrown when trying to renounce ownership (disabled for security) | ||
| error OwnershipRenunciationDisabled(); | ||
|
|
||
| //////////////////////////////////////////////////////////////// | ||
| /// External Functions /// | ||
| //////////////////////////////////////////////////////////////// | ||
|
|
||
| /// @custom:oz-upgrades-unsafe-allow constructor | ||
| constructor() { | ||
| _disableInitializers(); | ||
|
|
@@ -104,6 +122,7 @@ contract BuilderCodes is | |
| /// | ||
| /// @param initialOwner Address that will own the contract | ||
| /// @param initialRegistrar Address to grant REGISTER_ROLE (can be address(0) to skip) | ||
| /// @param uriPrefix Base URI for builder code metadata | ||
| function initialize(address initialOwner, address initialRegistrar, string memory uriPrefix) external initializer { | ||
| if (initialOwner == address(0)) revert ZeroAddress(); | ||
|
|
||
|
|
@@ -117,7 +136,9 @@ contract BuilderCodes is | |
| if (initialRegistrar != address(0)) _grantRole(REGISTER_ROLE, initialRegistrar); | ||
| } | ||
|
|
||
| /// @notice Registers a new referral code in the system with a custom value | ||
| /// @notice Registers a new builder code in the system with a custom value | ||
| /// | ||
| /// @dev Requires sender has REGISTER_ROLE | ||
| /// | ||
| /// @param code Custom builder code for the builder code | ||
| /// @param initialOwner Owner of the builder code | ||
|
|
@@ -129,33 +150,36 @@ contract BuilderCodes is | |
| _register(code, initialOwner, initialPayoutAddress); | ||
| } | ||
|
|
||
| /// @notice Registers a new referral code in the system with a signature | ||
| /// @notice Registers a new builder code in the system with a signature | ||
| /// | ||
| /// @dev Requires registration deadline has not passed | ||
| /// @dev Requires valid signature from an address with REGISTER_ROLE | ||
| /// | ||
| /// @param code Custom builder code for the builder code | ||
| /// @param initialOwner Owner of the builder code | ||
| /// @param initialPayoutAddress Default payout address | ||
| /// @param deadline Deadline to submit the registration | ||
| /// @param registrar Address of the registrar | ||
| /// @param signature Signature of the registrar | ||
| /// @param signer Address of the signer | ||
| /// @param signature Registration signature | ||
| function registerWithSignature( | ||
| string memory code, | ||
| address initialOwner, | ||
| address initialPayoutAddress, | ||
| uint48 deadline, | ||
| address registrar, | ||
| address signer, | ||
| bytes memory signature | ||
| ) external { | ||
| // Check deadline has not passed | ||
| if (block.timestamp > deadline) revert AfterRegistrationDeadline(deadline); | ||
|
|
||
| // Check registrar has role | ||
| _checkRole(REGISTER_ROLE, registrar); | ||
| // Check signer has role | ||
| _checkRole(REGISTER_ROLE, signer); | ||
|
|
||
| // Check signature is valid | ||
| bytes32 structHash = keccak256( | ||
| abi.encode(REGISTRATION_TYPEHASH, keccak256(bytes(code)), initialOwner, initialPayoutAddress, deadline) | ||
| ); | ||
| if (!SignatureCheckerLib.isValidSignatureNow(registrar, _hashTypedData(structHash), signature)) { | ||
| if (!SignatureCheckerLib.isValidSignatureNow(signer, _hashTypedData(structHash), signature)) { | ||
| revert Unauthorized(); | ||
| } | ||
|
|
||
|
|
@@ -168,12 +192,14 @@ contract BuilderCodes is | |
| /// @dev ERC721Upgradeable.safeTransferFrom inherits this function (and no other functions can initiate transfers) | ||
| function transferFrom(address from, address to, uint256 tokenId) public override(ERC721Upgradeable, IERC721) { | ||
| _checkRole(TRANSFER_ROLE, msg.sender); | ||
| // test | ||
| super.transferFrom(from, to, tokenId); | ||
| } | ||
|
|
||
| /// @notice Updates the metadata for a builder code | ||
| /// | ||
| /// @dev Requires sender has METADATA_ROLE | ||
| /// @dev Requires token exists | ||
| /// | ||
| /// @param tokenId Token ID of the builder code | ||
| function updateMetadata(uint256 tokenId) external onlyRole(METADATA_ROLE) { | ||
| _requireOwned(tokenId); // verifies token exists | ||
|
|
@@ -182,90 +208,99 @@ contract BuilderCodes is | |
|
|
||
| /// @notice Updates the base URI for the builder codes | ||
| /// | ||
| /// @dev Requires sender has METADATA_ROLE | ||
| /// | ||
| /// @param uriPrefix New base URI for the builder codes | ||
| function updateBaseURI(string memory uriPrefix) external onlyRole(METADATA_ROLE) { | ||
| _getRegistryStorage().uriPrefix = uriPrefix; | ||
| emit BatchMetadataUpdate(0, type(uint256).max); | ||
| emit ContractURIUpdated(); | ||
| } | ||
|
|
||
| /// @notice Updates the default payout address for a referral code | ||
| /// @notice Updates the payout address for a builder code | ||
| /// | ||
| /// @dev Requires sender is owner of builder code | ||
| /// | ||
| /// @param code Builder code | ||
| /// @param newPayoutAddress New default payout address | ||
| /// @dev Only callable by referral code owner | ||
| /// @param newPayoutAddress New payout address | ||
| function updatePayoutAddress(string memory code, address newPayoutAddress) external { | ||
| uint256 tokenId = toTokenId(code); | ||
| if (_requireOwned(tokenId) != msg.sender) revert Unauthorized(); | ||
| _updatePayoutAddress(tokenId, newPayoutAddress); | ||
| } | ||
|
|
||
| /// @notice Gets the default payout address for a referral code | ||
| /// @notice Gets the payout address for a builder code | ||
| /// | ||
| /// @dev Requires builder code exists | ||
| /// | ||
| /// @param code Builder code | ||
| /// | ||
| /// @return The default payout address | ||
| /// @return payoutAddress The payout address | ||
| function payoutAddress(string memory code) external view returns (address) { | ||
| uint256 tokenId = toTokenId(code); | ||
| if (_ownerOf(tokenId) == address(0)) revert Unregistered(code); | ||
| return _getRegistryStorage().payoutAddresses[tokenId]; | ||
| } | ||
|
|
||
| /// @notice Gets the default payout address for a referral code | ||
| /// @notice Gets the payout address for a builder code | ||
| /// | ||
| /// @param tokenId Token ID of the referral code | ||
| /// @dev Requires builder code exists | ||
| /// | ||
| /// @param tokenId Token ID of the builder code | ||
| /// | ||
| /// @return The default payout address | ||
| /// @return payoutAddress The payout address | ||
| function payoutAddress(uint256 tokenId) external view returns (address) { | ||
| if (_ownerOf(tokenId) == address(0)) revert Unregistered(toCode(tokenId)); | ||
| return _getRegistryStorage().payoutAddresses[tokenId]; | ||
| } | ||
|
|
||
| /// @notice Returns the URI for a referral code | ||
| /// @notice Returns the URI for a builder code | ||
| /// | ||
| /// @param code Builder code | ||
| /// | ||
| /// @return The URI for the referral code | ||
| /// @return uri The URI for the builder code | ||
| function codeURI(string memory code) external view returns (string memory) { | ||
| return tokenURI(toTokenId(code)); | ||
| } | ||
|
|
||
| /// @notice Returns the URI for the contract | ||
| /// | ||
| /// @return The URI for the contract | ||
| /// @return uri The URI for the contract | ||
| function contractURI() external view returns (string memory) { | ||
| string memory uriPrefix = _getRegistryStorage().uriPrefix; | ||
| return bytes(uriPrefix).length > 0 ? string.concat(uriPrefix, "contractURI.json") : ""; | ||
| } | ||
|
|
||
| /// @notice Returns the URI for a referral code | ||
| /// @notice Returns the URI for a builder code | ||
| /// | ||
| /// @param tokenId Token ID of the referral code | ||
| /// @param tokenId Token ID of the builder code | ||
| /// | ||
| /// @return uri The URI for the referral code | ||
| /// @return uri The URI for the builder code | ||
| function tokenURI(uint256 tokenId) public view override returns (string memory) { | ||
| _requireOwned(tokenId); // verifies token exists | ||
| string memory uriPrefix = _getRegistryStorage().uriPrefix; | ||
| return bytes(uriPrefix).length > 0 ? string.concat(uriPrefix, toCode(tokenId)) : ""; | ||
| } | ||
|
|
||
| /// @notice Checks if a referral code exists | ||
| /// @notice Checks if a builder code exists | ||
| /// | ||
| /// @param code Builder code to check | ||
| /// | ||
| /// @return True if the referral code exists | ||
| /// @return registered True if the builder code exists | ||
| function isRegistered(string memory code) public view returns (bool) { | ||
| return _ownerOf(toTokenId(code)) != address(0); | ||
| } | ||
|
|
||
| /// @notice Checks if an address has a role | ||
| /// | ||
| /// @dev Override grants all roles to owner | ||
| /// | ||
| /// @param role The role to check | ||
| /// @param account The address to check | ||
| /// | ||
| /// @return True if the address has the role | ||
| /// @return hasRole True if the address has the role | ||
| function hasRole(bytes32 role, address account) public view override returns (bool) { | ||
| return account == owner() || super.hasRole(role, account); | ||
| return super.hasRole(role, account) || account == owner(); | ||
|
Comment on lines
-268
to
+303
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. non-zero source change |
||
| } | ||
|
|
||
| /// @inheritdoc ERC721Upgradeable | ||
|
|
@@ -279,11 +314,11 @@ contract BuilderCodes is | |
| || AccessControlUpgradeable.supportsInterface(interfaceId) || interfaceId == bytes4(0x49064906); | ||
| } | ||
|
|
||
| /// @notice Checks if a referral code is valid | ||
| /// @notice Checks if a builder code is valid | ||
| /// | ||
| /// @param code Builder code to check | ||
| /// | ||
| /// @return True if the referral code is valid | ||
| /// @return valid True if the builder code is valid | ||
| function isValidCode(string memory code) public pure returns (bool) { | ||
| // Early return invalid if code is zero or over 32 bytes/characters | ||
| uint256 length = bytes(code).length; | ||
|
|
@@ -293,11 +328,13 @@ contract BuilderCodes is | |
| return LibString.is7BitASCII(code, ALLOWED_CHARACTERS_LOOKUP); | ||
| } | ||
|
|
||
| /// @notice Converts a referral code to a token ID | ||
| /// @notice Converts a builder code to a token ID | ||
| /// | ||
| /// @dev Requires builder code is valid | ||
| /// | ||
| /// @param code Builder code to convert | ||
| /// | ||
| /// @return tokenId The token ID for the referral code | ||
| /// @return tokenId The token ID for the builder code | ||
| function toTokenId(string memory code) public pure returns (uint256 tokenId) { | ||
| if (!isValidCode(code)) revert InvalidCode(code); | ||
|
|
||
|
|
@@ -306,11 +343,13 @@ contract BuilderCodes is | |
| tokenId = uint256(bytes32(bytes(code))) >> trailingZeroBytes * 8; | ||
| } | ||
|
|
||
| /// @notice Converts a token ID to a referral code | ||
| /// @notice Converts a token ID to a builder code | ||
| /// | ||
| /// @dev Requires token ID and builder code are valid | ||
| /// | ||
| /// @param tokenId Token ID to convert | ||
| /// | ||
| /// @return code The referral code for the token ID | ||
| /// @return code The builder code for the token ID | ||
| function toCode(uint256 tokenId) public pure returns (string memory code) { | ||
| // Shift nonzero bytes left so low-endian bits are zero, matching LibString's expectation to trim `\0` bytes | ||
| uint256 leadingZeroBytes = LibBit.clz(tokenId) / 8; // "clz" = count leading zeros | ||
|
|
@@ -327,9 +366,13 @@ contract BuilderCodes is | |
| revert OwnershipRenunciationDisabled(); | ||
| } | ||
|
|
||
| /// @notice Registers a new referral code | ||
| //////////////////////////////////////////////////////////////// | ||
| /// Internal Functions /// | ||
| //////////////////////////////////////////////////////////////// | ||
|
|
||
| /// @notice Registers a new builder code | ||
| /// | ||
| /// @param code Referral code | ||
| /// @param code Builder code | ||
| /// @param initialOwner Owner of the ref code | ||
| /// @param initialPayoutAddress Default payout address | ||
| function _register(string memory code, address initialOwner, address initialPayoutAddress) internal { | ||
|
|
@@ -339,9 +382,11 @@ contract BuilderCodes is | |
| _updatePayoutAddress(tokenId, initialPayoutAddress); | ||
| } | ||
|
|
||
| /// @notice Registers a new referral code | ||
| /// @notice Registers a new builder code | ||
| /// | ||
| /// @param tokenId Token ID of the referral code | ||
| /// @dev Requires non-zero payout address | ||
| /// | ||
| /// @param tokenId Token ID of the builder code | ||
| /// @param newPayoutAddress New payout address | ||
| function _updatePayoutAddress(uint256 tokenId, address newPayoutAddress) internal { | ||
| if (newPayoutAddress == address(0)) revert ZeroAddress(); | ||
|
|
@@ -351,13 +396,15 @@ contract BuilderCodes is | |
|
|
||
| /// @notice Authorization for upgrades | ||
| /// | ||
| /// @dev Requires sender is owner | ||
| /// | ||
| /// @param newImplementation Address of new implementation | ||
| function _authorizeUpgrade(address newImplementation) internal view override onlyOwner {} | ||
|
|
||
| /// @notice Returns the domain name and version for the referral codes | ||
| /// @notice Returns the domain name and version for the builder codes | ||
| /// | ||
| /// @return name The domain name for the referral codes | ||
| /// @return version The version of the referral codes | ||
| /// @return name The domain name for the builder codes | ||
| /// @return version The version of the builder codes | ||
| function _domainNameAndVersion() | ||
| internal | ||
| pure | ||
|
|
@@ -371,7 +418,7 @@ contract BuilderCodes is | |
|
|
||
| /// @notice Returns if the domain name and version may change | ||
| /// | ||
| /// @return True if the domain name and version may change | ||
| /// @return res True if the domain name and version may change | ||
| function _domainNameAndVersionMayChange() internal pure override returns (bool) { | ||
| return true; | ||
| } | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
non-zero source change