Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,14 @@ optimizer_runs = 100000

# Generate skimmable test docs with command: make docs-test
[profile.testdocs]
src = "test"
src = "test"

[lint]
lint_on_build = false
exclude_lints = [
"mixed-case-variable", # *URI variable names
"mixed-case-function", # *URI function names
"asm-keccak256", # EIP-712 hashing
"erc20-unchecked-transfer", # Warning on ERC-721 transferFrom
"unused-import" # BuilderCodesTest imports for purpose of dependent tests
]
42 changes: 22 additions & 20 deletions src/BuilderCodes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ contract BuilderCodes is
/// @notice Emitted when a publisher's default payout address is updated
///
/// @param tokenId Token ID of the referral code
/// @param payoutAddress New default payout address for all chains
/// @param payoutAddress New default payout address
event PayoutAddressUpdated(uint256 indexed tokenId, address payoutAddress);

/// @notice Emits when the contract URI is updated (ERC-7572)
Expand Down Expand Up @@ -121,26 +121,26 @@ contract BuilderCodes is
///
/// @param code Custom builder code for the builder code
/// @param initialOwner Owner of the builder code
/// @param payoutAddress Default payout address for all chains
function register(string memory code, address initialOwner, address payoutAddress)
/// @param initialPayoutAddress Default payout address
function register(string memory code, address initialOwner, address initialPayoutAddress)
external
onlyRole(REGISTER_ROLE)
{
_register(code, initialOwner, payoutAddress);
_register(code, initialOwner, initialPayoutAddress);
}

/// @notice Registers a new referral code in the system with a signature
///
/// @param code Custom builder code for the builder code
/// @param initialOwner Owner of the builder code
/// @param payoutAddress Default payout address for all chains
/// @param initialPayoutAddress Default payout address
/// @param deadline Deadline to submit the registration
/// @param registrar Address of the registrar
/// @param signature Signature of the registrar
function registerWithSignature(
string memory code,
address initialOwner,
address payoutAddress,
address initialPayoutAddress,
uint48 deadline,
address registrar,
bytes memory signature
Expand All @@ -152,13 +152,14 @@ contract BuilderCodes is
_checkRole(REGISTER_ROLE, registrar);

// Check signature is valid
bytes32 structHash =
keccak256(abi.encode(REGISTRATION_TYPEHASH, keccak256(bytes(code)), initialOwner, payoutAddress, deadline));
bytes32 structHash = keccak256(
abi.encode(REGISTRATION_TYPEHASH, keccak256(bytes(code)), initialOwner, initialPayoutAddress, deadline)
);
if (!SignatureCheckerLib.isValidSignatureNow(registrar, _hashTypedData(structHash), signature)) {
revert Unauthorized();
}

_register(code, initialOwner, payoutAddress);
_register(code, initialOwner, initialPayoutAddress);
}

/// @inheritdoc ERC721Upgradeable
Expand All @@ -167,6 +168,7 @@ 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);
}

Expand All @@ -190,12 +192,12 @@ contract BuilderCodes is
/// @notice Updates the default payout address for a referral code
///
/// @param code Builder code
/// @param payoutAddress New default payout address
/// @param newPayoutAddress New default payout address
/// @dev Only callable by referral code owner
function updatePayoutAddress(string memory code, address payoutAddress) external {
function updatePayoutAddress(string memory code, address newPayoutAddress) external {
uint256 tokenId = toTokenId(code);
if (_requireOwned(tokenId) != msg.sender) revert Unauthorized();
_updatePayoutAddress(tokenId, payoutAddress);
_updatePayoutAddress(tokenId, newPayoutAddress);
}

/// @notice Gets the default payout address for a referral code
Expand Down Expand Up @@ -329,22 +331,22 @@ contract BuilderCodes is
///
/// @param code Referral code
/// @param initialOwner Owner of the ref code
/// @param payoutAddress Default payout address for all chains
function _register(string memory code, address initialOwner, address payoutAddress) internal {
/// @param initialPayoutAddress Default payout address
function _register(string memory code, address initialOwner, address initialPayoutAddress) internal {
uint256 tokenId = toTokenId(code);
_mint(initialOwner, tokenId);
emit CodeRegistered(tokenId, code);
_updatePayoutAddress(tokenId, payoutAddress);
_updatePayoutAddress(tokenId, initialPayoutAddress);
}

/// @notice Registers a new referral code
///
/// @param tokenId Token ID of the referral code
/// @param payoutAddress Default payout address for all chains
function _updatePayoutAddress(uint256 tokenId, address payoutAddress) internal {
if (payoutAddress == address(0)) revert ZeroAddress();
_getRegistryStorage().payoutAddresses[tokenId] = payoutAddress;
emit PayoutAddressUpdated(tokenId, payoutAddress);
/// @param newPayoutAddress New payout address
function _updatePayoutAddress(uint256 tokenId, address newPayoutAddress) internal {
if (newPayoutAddress == address(0)) revert ZeroAddress();
_getRegistryStorage().payoutAddresses[tokenId] = newPayoutAddress;
emit PayoutAddressUpdated(tokenId, newPayoutAddress);
}

/// @notice Authorization for upgrades
Expand Down
1 change: 0 additions & 1 deletion test/integration/BuilderCodeTransfers.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pragma solidity ^0.8.29;
import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";

import {BuilderCodesTest} from "../lib/BuilderCodesTest.sol";
import {BuilderCodes} from "../../src/BuilderCodes.sol";
import {MockTransferRules} from "../lib/mocks/MockTransferRules.sol";

/// @notice Integration tests for BuilderCodes transfers
Expand Down
1 change: 0 additions & 1 deletion test/integration/BuilderCodesAdminOperations.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity ^0.8.29;

import {BuilderCodesTest} from "../lib/BuilderCodesTest.sol";
import {BuilderCodes} from "../../src/BuilderCodes.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";

Expand Down
3 changes: 1 addition & 2 deletions test/lib/BuilderCodesTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity ^0.8.29;

import {Test} from "forge-std/Test.sol";
import {console} from "forge-std/console.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";

Expand Down Expand Up @@ -51,7 +50,7 @@ abstract contract BuilderCodesTest is Test {
/// @param seed Random number to seed the invalid code generation
///
/// @return code Invalid code containing disallowed characters
function _generateInvalidCode(uint256 seed) internal view returns (string memory code) {
function _generateInvalidCode(uint256 seed) internal pure returns (string memory code) {
uint256 length = seed % 32 + 1; // 1-32 characters
string memory invalidCharacters = "!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ";
return _generateCode(seed, length, invalidCharacters);
Expand Down
1 change: 1 addition & 0 deletions test/unit/BuilderCodes/codeURI.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.29;

import {BuilderCodesTest, IERC721Errors} from "../../lib/BuilderCodesTest.sol";

import {BuilderCodes} from "../../../src/BuilderCodes.sol";

/// @notice Unit tests for BuilderCodes.codeURI
Expand Down
30 changes: 4 additions & 26 deletions test/unit/BuilderCodes/contractURI.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@ import {BuilderCodes} from "../../../src/BuilderCodes.sol";
/// @notice Unit tests for BuilderCodes.contractURI
contract ContractURITest is BuilderCodesTest {
/// @notice Test that contractURI returns correct URI when base URI is set
///
/// @param initialOwner The initial owner address
/// @param initialPayoutAddress The initial payout address
function test_contractURI_success_returnsCorrectURIWithBaseURI(address initialOwner, address initialPayoutAddress)
public
{
function test_contractURI_success_returnsCorrectURIWithBaseURI() public view {
// The builderCodes contract is already initialized with URI_PREFIX
string memory contractURI = builderCodes.contractURI();
string memory expected = string.concat(URI_PREFIX, "contractURI.json");
Expand All @@ -21,12 +16,7 @@ contract ContractURITest is BuilderCodesTest {

/// @notice Test that contractURI returns empty string when base URI is not set
///
/// @param initialOwner The initial owner address
/// @param initialPayoutAddress The initial payout address
function test_contractURI_success_returnsEmptyStringWithoutBaseURI(
address initialOwner,
address initialPayoutAddress
) public {
function test_contractURI_success_returnsEmptyStringWithoutBaseURI(address initialOwner) public {
initialOwner = _boundNonZeroAddress(initialOwner);
BuilderCodes freshContract = _deployFreshBuilderCodes();

Expand All @@ -38,14 +28,8 @@ contract ContractURITest is BuilderCodesTest {

/// @notice Test that contractURI reflects updated base URI
///
/// @param initialOwner The initial owner address
/// @param initialPayoutAddress The initial payout address
/// @param newBaseURI The new base URI
function test_contractURI_success_reflectsUpdatedBaseURI(
address initialOwner,
address initialPayoutAddress,
string memory newBaseURI
) public {
function test_contractURI_success_reflectsUpdatedBaseURI(string memory newBaseURI) public {
// Update base URI using owner permissions
vm.prank(owner);
builderCodes.updateBaseURI(newBaseURI);
Expand All @@ -61,14 +45,8 @@ contract ContractURITest is BuilderCodesTest {

/// @notice Test that contractURI returns contractURI.json suffix
///
/// @param initialOwner The initial owner address
/// @param initialPayoutAddress The initial payout address
/// @param baseURI The base URI
function test_contractURI_success_returnsWithCorrectSuffix(
address initialOwner,
address initialPayoutAddress,
string memory baseURI
) public {
function test_contractURI_success_returnsWithCorrectSuffix(string memory baseURI) public {
vm.assume(bytes(baseURI).length > 0);

vm.prank(owner);
Expand Down
2 changes: 1 addition & 1 deletion test/unit/BuilderCodes/hasRole.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ contract HasRoleTest is BuilderCodesTest {
/// @notice Test that the owner has any role
///
/// @param role The role to check
function test_hasRole_true_isOwner(bytes32 role) public {
function test_hasRole_true_isOwner(bytes32 role) public view {
assertTrue(builderCodes.hasRole(role, owner));
}

Expand Down
29 changes: 4 additions & 25 deletions test/unit/BuilderCodes/isRegistered.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,15 @@ import {BuilderCodes} from "../../../src/BuilderCodes.sol";
/// @notice Unit tests for BuilderCodes.isRegistered
contract IsRegisteredTest is BuilderCodesTest {
/// @notice Test that isRegistered reverts when code is empty
///
/// @param initialOwner The initial owner address
/// @param initialPayoutAddress The initial payout address
function test_isRegistered_revert_emptyCode(address initialOwner, address initialPayoutAddress) public {
function test_isRegistered_revert_emptyCode() public {
vm.expectRevert(abi.encodeWithSelector(BuilderCodes.InvalidCode.selector, ""));
builderCodes.isRegistered("");
}

/// @notice Test that isRegistered reverts when code is over 32 characters
///
/// @param codeSeed The seed for generating the code
/// @param initialOwner The initial owner address
/// @param initialPayoutAddress The initial payout address
function test_isRegistered_revert_codeOver32Characters(
uint256 codeSeed,
address initialOwner,
address initialPayoutAddress
) public {
function test_isRegistered_revert_codeOver32Characters(uint256 codeSeed) public {
string memory longCode = _generateLongCode(codeSeed);
vm.expectRevert(abi.encodeWithSelector(BuilderCodes.InvalidCode.selector, longCode));
builderCodes.isRegistered(longCode);
Expand All @@ -33,13 +24,7 @@ contract IsRegisteredTest is BuilderCodesTest {
/// @notice Test that isRegistered reverts when code contains invalid characters
///
/// @param codeSeed The seed for generating the code
/// @param initialOwner The initial owner address
/// @param initialPayoutAddress The initial payout address
function test_isRegistered_revert_codeContainsInvalidCharacters(
uint256 codeSeed,
address initialOwner,
address initialPayoutAddress
) public {
function test_isRegistered_revert_codeContainsInvalidCharacters(uint256 codeSeed) public {
string memory invalidCode = _generateInvalidCode(codeSeed);
vm.expectRevert(abi.encodeWithSelector(BuilderCodes.InvalidCode.selector, invalidCode));
builderCodes.isRegistered(invalidCode);
Expand All @@ -48,13 +33,7 @@ contract IsRegisteredTest is BuilderCodesTest {
/// @notice Test that isRegistered returns false for unregistered valid code
///
/// @param codeSeed The seed for generating the code
/// @param initialOwner The initial owner address
/// @param initialPayoutAddress The initial payout address
function test_isRegistered_success_returnsFalseForUnregistered(
uint256 codeSeed,
address initialOwner,
address initialPayoutAddress
) public {
function test_isRegistered_success_returnsFalseForUnregistered(uint256 codeSeed) public view {
string memory validCode = _generateValidCode(codeSeed);
assertFalse(builderCodes.isRegistered(validCode));
}
Expand Down
Loading