diff --git a/.gitignore b/.gitignore index 7ae88d4..eb56cc2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ docs/ tools/playground.sh scripts/Playground.s.sol +scripts/data.json diff --git a/artifacts/abi/marketplace/lsp8/LSP8Marketplace.json b/artifacts/abi/marketplace/lsp8/LSP8Marketplace.json index eacdb1d..65d6bc7 100644 --- a/artifacts/abi/marketplace/lsp8/LSP8Marketplace.json +++ b/artifacts/abi/marketplace/lsp8/LSP8Marketplace.json @@ -90,11 +90,6 @@ "name": "InvalidLSP18RoyaltiesData", "type": "error" }, - { - "inputs": [], - "name": "NoPendingSale", - "type": "error" - }, { "inputs": [ { @@ -674,46 +669,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "pendingSale", - "outputs": [ - { - "components": [ - { - "internalType": "address", - "name": "asset", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "tokenId", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "seller", - "type": "address" - }, - { - "internalType": "address", - "name": "buyer", - "type": "address" - }, - { - "internalType": "uint256", - "name": "totalPaid", - "type": "uint256" - } - ], - "internalType": "struct PendingSale", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "renounceOwnership", diff --git a/artifacts/abi/page/PageName.json b/artifacts/abi/page/PageName.json index bd98872..b64fd58 100644 --- a/artifacts/abi/page/PageName.json +++ b/artifacts/abi/page/PageName.json @@ -295,32 +295,6 @@ "name": "OwnableCannotSetZeroAddressAsOwner", "type": "error" }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "tokenId", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "limit", - "type": "uint256" - } - ], - "name": "TransferExceedLimit", - "type": "error" - }, { "inputs": [ { @@ -721,6 +695,45 @@ "stateMutability": "payable", "type": "fallback" }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "profiles", + "type": "address[]" + } + ], + "name": "_internal_setProfileClaims", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "_unused_storage_slot_0", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_unused_storage_slot_1", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -795,6 +808,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "claimsOf", + "outputs": [ + { + "internalType": "uint24", + "name": "reservations", + "type": "uint24" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "controller", @@ -940,25 +972,10 @@ "name": "controller_", "type": "address" }, - { - "internalType": "uint256", - "name": "price_", - "type": "uint256" - }, { "internalType": "uint8", "name": "minimumLength_", "type": "uint8" - }, - { - "internalType": "uint16", - "name": "profileLimit_", - "type": "uint16" - }, - { - "internalType": "contract IPageNameMarketplace", - "name": "marketplace_", - "type": "address" } ], "name": "initialize", @@ -990,19 +1007,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "marketplace", - "outputs": [ - { - "internalType": "contract IPageNameMarketplace", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "minimumLength", @@ -1049,51 +1053,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "price", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "profileLimit", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "tokenOwner", - "type": "address" - } - ], - "name": "profileLimitOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -1126,6 +1085,11 @@ "name": "name", "type": "string" }, + { + "internalType": "bytes", + "name": "salt", + "type": "bytes" + }, { "internalType": "uint8", "name": "v", @@ -1296,32 +1260,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newPrice", - "type": "uint256" - } - ], - "name": "setPrice", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "newLimit", - "type": "uint16" - } - ], - "name": "setProfileLimit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { diff --git a/scripts/page/PageName.s.sol b/scripts/page/PageName.s.sol index 7c1f877..fc41de6 100644 --- a/scripts/page/PageName.s.sol +++ b/scripts/page/PageName.s.sol @@ -9,13 +9,10 @@ import { ITransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {UniversalProfile} from "@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol"; -import {LSP6KeyManager} from "@lukso/lsp-smart-contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol"; import {OPERATION_0_CALL} from "@erc725/smart-contracts/contracts/constants.sol"; import {PageName} from "../../src/page/PageName.sol"; -uint16 constant PROFILE_LIMIT = 1; uint8 constant MINIMUM_LENGTH = 3; -uint256 constant PRICE = 1 ether; contract Deploy is Script { function run() external { @@ -23,7 +20,6 @@ contract Deploy is Script { address profile = vm.envAddress("PROFILE_ADDRESS"); address treasury = vm.envAddress("TREASURY_ADDRESS"); address controller = vm.envAddress("PAGE_NAME_CONTROLLER_ADDRESS"); - address marketplace = vm.envAddress("CONTRACT_LSP8_MARKETPLACE_ADDRESS"); address proxy = vm.envOr("CONTRACT_PAGE_NAME_ADDRESS", address(0)); @@ -43,10 +39,7 @@ contract Deploy is Script { profile, treasury, controller, - PRICE, - MINIMUM_LENGTH, - PROFILE_LIMIT, - marketplace) + MINIMUM_LENGTH) ) ); console.log(string.concat("PageName: deploy ", Strings.toHexString(address(proxy)))); @@ -68,7 +61,6 @@ contract Configure is Script { address controller = vm.envAddress("PROFILE_CONTROLLER_ADDRESS"); address treasury = vm.envAddress("TREASURY_ADDRESS"); UniversalProfile profile = UniversalProfile(payable(vm.envAddress("PROFILE_ADDRESS"))); - LSP6KeyManager keyManager = LSP6KeyManager(profile.owner()); PageName pageName = PageName(payable(vm.envAddress("CONTRACT_PAGE_NAME_ADDRESS"))); bytes memory currentBaseUri = pageName.getData(_LSP8_TOKEN_METADATA_BASE_URI_KEY); @@ -76,66 +68,31 @@ contract Configure is Script { bytes memory encodedBaseUri = bytes.concat(_baseUriHash, bytes(baseUri)); if (keccak256(encodedBaseUri) != keccak256(currentBaseUri)) { vm.broadcast(controller); - keyManager.execute( - abi.encodeWithSelector( - profile.execute.selector, - OPERATION_0_CALL, - address(pageName), - 0, - abi.encodeWithSelector(pageName.setData.selector, _LSP8_TOKEN_METADATA_BASE_URI_KEY, encodedBaseUri) - ) + profile.execute( + OPERATION_0_CALL, + address(pageName), + 0, + abi.encodeWithSelector(pageName.setData.selector, _LSP8_TOKEN_METADATA_BASE_URI_KEY, encodedBaseUri) ); } if (pageName.beneficiary() != treasury) { vm.broadcast(controller); - keyManager.execute( - abi.encodeWithSelector( - profile.execute.selector, - OPERATION_0_CALL, - address(pageName), - 0, - abi.encodeWithSelector(pageName.setBeneficiary.selector, treasury) - ) - ); - } - - if (pageName.price() != PRICE) { - vm.broadcast(controller); - keyManager.execute( - abi.encodeWithSelector( - profile.execute.selector, - OPERATION_0_CALL, - address(pageName), - 0, - abi.encodeWithSelector(pageName.setPrice.selector, PRICE) - ) - ); - } - - if (pageName.profileLimit() != PROFILE_LIMIT) { - vm.broadcast(controller); - keyManager.execute( - abi.encodeWithSelector( - profile.execute.selector, - OPERATION_0_CALL, - address(pageName), - 0, - abi.encodeWithSelector(pageName.setProfileLimit.selector, PROFILE_LIMIT) - ) + profile.execute( + OPERATION_0_CALL, + address(pageName), + 0, + abi.encodeWithSelector(pageName.setBeneficiary.selector, treasury) ); } if (pageName.minimumLength() != MINIMUM_LENGTH) { vm.broadcast(controller); - keyManager.execute( - abi.encodeWithSelector( - profile.execute.selector, - OPERATION_0_CALL, - address(pageName), - 0, - abi.encodeWithSelector(pageName.setMinimumLength.selector, MINIMUM_LENGTH) - ) + profile.execute( + OPERATION_0_CALL, + address(pageName), + 0, + abi.encodeWithSelector(pageName.setMinimumLength.selector, MINIMUM_LENGTH) ); } } diff --git a/src/marketplace/lsp8/LSP8Marketplace.sol b/src/marketplace/lsp8/LSP8Marketplace.sol index a6234a4..49efb3c 100644 --- a/src/marketplace/lsp8/LSP8Marketplace.sol +++ b/src/marketplace/lsp8/LSP8Marketplace.sol @@ -5,13 +5,12 @@ import {ILSP8IdentifiableDigitalAsset} from "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/ILSP8IdentifiableDigitalAsset.sol"; import {Base} from "../common/Base.sol"; import {Royalties} from "../../common/Royalties.sol"; -import {IPageNameMarketplace, PendingSale} from "../../page/IPageNameMarketplace.sol"; import {IParticipant} from "../IParticipant.sol"; import {ILSP8Listings, LSP8Listing} from "./ILSP8Listings.sol"; import {ILSP8Offers, LSP8Offer} from "./ILSP8Offers.sol"; import {ILSP8Auctions, LSP8Auction, LSP8Bid} from "./ILSP8Auctions.sol"; -contract LSP8Marketplace is IPageNameMarketplace, Base { +contract LSP8Marketplace is Base { event Sale( uint256 indexed listingId, address indexed asset, @@ -29,13 +28,16 @@ contract LSP8Marketplace is IPageNameMarketplace, Base { error FeesExceedTotalPaid(uint256 totalPaid, uint256 feesAmount, uint256 royaltiesTotalAmount); error Unpaid(uint256 listingId, address account, uint256 amount); error UnathorizedSeller(address account); - error NoPendingSale(); error Auctioned(uint256 listingId); ILSP8Listings public listings; ILSP8Offers public offers; ILSP8Auctions public auctions; - PendingSale private _pendingSale; + uint256 private _unused_storage_slot_0; + uint256 private _unused_storage_slot_1; + uint256 private _unused_storage_slot_2; + uint256 private _unused_storage_slot_3; + uint256 private _unused_storage_slot_4; // profile -> asset -> token id -> price mapping(address => mapping(address => mapping(bytes32 => uint256))) private _lastPurchasePrice; @@ -60,13 +62,6 @@ contract LSP8Marketplace is IPageNameMarketplace, Base { auctions = ILSP8Auctions(auctions_); } - function pendingSale() external view returns (PendingSale memory) { - if (_pendingSale.asset == address(0)) { - revert NoPendingSale(); - } - return _pendingSale; - } - function lastPurchasePrice(address buyer, address asset, bytes32 tokenId) public view returns (uint256) { return _lastPurchasePrice[buyer][asset][tokenId]; } @@ -116,7 +111,6 @@ contract LSP8Marketplace is IPageNameMarketplace, Base { address buyer, uint256 totalPaid ) private { - _pendingSale = PendingSale({asset: asset, tokenId: tokenId, seller: seller, buyer: buyer, totalPaid: totalPaid}); (uint256 royaltiesTotalAmount, address[] memory royaltiesRecipients, uint256[] memory royaltiesAmounts) = _calculateRoyalties(asset, totalPaid); uint256 feeAmount = _calculateFeeWithDiscount(seller, totalPaid); @@ -149,7 +143,6 @@ contract LSP8Marketplace is IPageNameMarketplace, Base { emit FeePaid(listingId, asset, tokenId, feeAmount); } ILSP8IdentifiableDigitalAsset(asset).transfer(seller, buyer, tokenId, false, ""); - delete _pendingSale; _lastPurchasePrice[buyer][asset][tokenId] = totalPaid; emit Sale(listingId, asset, tokenId, seller, buyer, totalPaid); } diff --git a/src/page/IPageNameMarketplace.sol b/src/page/IPageNameMarketplace.sol deleted file mode 100644 index b5d3887..0000000 --- a/src/page/IPageNameMarketplace.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.22; - -struct PendingSale { - address asset; - bytes32 tokenId; - address seller; - address buyer; - uint256 totalPaid; -} - -interface IPageNameMarketplace { - /// a pending sale that is currently being transacted. - function pendingSale() external view returns (PendingSale memory); -} diff --git a/src/page/PageName.sol b/src/page/PageName.sol index 1015b1e..a1fc40e 100644 --- a/src/page/PageName.sol +++ b/src/page/PageName.sol @@ -12,7 +12,6 @@ import {LSP8EnumerableInitAbstract} from import {LSP8IdentifiableDigitalAssetInitAbstract} from "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetInitAbstract.sol"; import {Withdrawable} from "../common/Withdrawable.sol"; -import {IPageNameMarketplace, PendingSale} from "./IPageNameMarketplace.sol"; contract PageName is LSP8EnumerableInitAbstract, ReentrancyGuardUpgradeable, PausableUpgradeable, Withdrawable { error InvalidController(); @@ -22,7 +21,6 @@ contract PageName is LSP8EnumerableInitAbstract, ReentrancyGuardUpgradeable, Pau error IncorrectReservationName(address recipient, string name); error UnauthorizedReservation(address recipient, string name, uint256 price); - error TransferExceedLimit(address from, address to, bytes32 tokenId, uint256 limit); error TransferInvalidSale(address from, address to, bytes32 tokenId, uint256 totalPaid); event ControllerChanged(address indexed oldController, address indexed newController); @@ -30,12 +28,14 @@ contract PageName is LSP8EnumerableInitAbstract, ReentrancyGuardUpgradeable, Pau event ReservedName(address indexed account, bytes32 indexed tokenId, uint256 price); event ReleasedName(address indexed account, bytes32 indexed tokenId); - mapping(address => uint256) private _profileLimit; - uint256 public price; + mapping(address => uint256) private _unused_storage_slot_0; + uint256 public _unused_storage_slot_1; uint8 public minimumLength; - uint16 public profileLimit; + uint16 public _unused_storage_slot_2; address public controller; - IPageNameMarketplace public marketplace; + uint160 private _unused_storage_slot_3; + // hash => used + mapping(bytes32 => bool) private _usedReservations; constructor() { _disableInitializers(); @@ -47,38 +47,24 @@ contract PageName is LSP8EnumerableInitAbstract, ReentrancyGuardUpgradeable, Pau address newOwner_, address beneficiary_, address controller_, - uint256 price_, - uint8 minimumLength_, - uint16 profileLimit_, - IPageNameMarketplace marketplace_ + uint8 minimumLength_ ) external initializer { super._initialize(name_, symbol_, newOwner_, _LSP4_TOKEN_TYPE_NFT, _LSP8_TOKENID_FORMAT_STRING); __ReentrancyGuard_init(); __Pausable_init(); _setBeneficiary(beneficiary_); _setController(controller_); - price = price_; - profileLimit = profileLimit_; minimumLength = minimumLength_; - marketplace = marketplace_; } receive() external payable override(LSP8IdentifiableDigitalAssetInitAbstract, Withdrawable) { _doReceive(); } - function setProfileLimit(uint16 newLimit) external onlyOwner { - profileLimit = newLimit; - } - function setMinimumLength(uint8 newLength) external onlyOwner { minimumLength = newLength; } - function setPrice(uint256 newPrice) external onlyOwner { - price = newPrice; - } - function setController(address newController) external onlyOwner { _setController(newController); } @@ -103,23 +89,20 @@ contract PageName is LSP8EnumerableInitAbstract, ReentrancyGuardUpgradeable, Pau _unpause(); } - function profileLimitOf(address tokenOwner) public view returns (uint256) { - return profileLimit + _profileLimit[tokenOwner]; - } - - function reserve(address recipient, string calldata name, uint8 v, bytes32 r, bytes32 s) + function reserve(address recipient, string calldata name, bytes calldata salt, uint8 v, bytes32 r, bytes32 s) external payable nonReentrant whenNotPaused { - bytes32 hash = keccak256(abi.encodePacked(address(this), block.chainid, recipient, name, msg.value)); - if (ECDSA.recover(hash, v, r, s) != controller) { - revert UnauthorizedReservation(recipient, name, msg.value); - } if (!_isValidName(name)) { revert IncorrectReservationName(recipient, name); } + bytes32 hash = keccak256(abi.encodePacked(address(this), block.chainid, recipient, name, salt, msg.value)); + if (_usedReservations[hash] || (ECDSA.recover(hash, v, r, s) != controller)) { + revert UnauthorizedReservation(recipient, name, msg.value); + } + _usedReservations[hash] = true; bytes32 tokenId = bytes32(bytes(name)); _mint(recipient, tokenId, false, ""); emit ReservedName(recipient, tokenId, msg.value); @@ -148,27 +131,4 @@ contract PageName is LSP8EnumerableInitAbstract, ReentrancyGuardUpgradeable, Pau } return true; } - - function _beforeTokenTransfer(address from, address to, bytes32 tokenId, bytes memory data) - internal - virtual - override(LSP8EnumerableInitAbstract) - whenNotPaused - { - super._beforeTokenTransfer(from, to, tokenId, data); - if (from != address(0) && to != address(0) && balanceOf(to) >= profileLimitOf(to)) { - if (msg.sender == address(marketplace)) { - PendingSale memory sale = marketplace.pendingSale(); - if (sale.asset == address(this) && sale.tokenId == tokenId && sale.seller == from && sale.buyer == to) { - if (sale.totalPaid < price) { - revert TransferInvalidSale(from, to, tokenId, sale.totalPaid); - } - } else { - revert TransferInvalidSale(from, to, tokenId, 0); - } - } else { - revert TransferExceedLimit(from, to, tokenId, profileLimitOf(to)); - } - } - } } diff --git a/test/page/PageName.t.sol b/test/page/PageName.t.sol index 01c0379..d3e1767 100644 --- a/test/page/PageName.t.sol +++ b/test/page/PageName.t.sol @@ -15,10 +15,8 @@ import { } from "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8Constants.sol"; import {UniversalProfile} from "@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol"; import {OwnableCallerNotTheOwner} from "@erc725/smart-contracts/contracts/errors.sol"; -import {IPageNameMarketplace, PendingSale} from "../../src/page/IPageNameMarketplace.sol"; import {PageName} from "../../src/page/PageName.sol"; import {deployProfile} from "../utils/profile.sol"; -import {PageNameMarketplaceMock} from "./PageNameMarketplaceMock.sol"; contract PageNameTest is Test { event ValueReceived(address indexed sender, uint256 indexed value); @@ -33,7 +31,6 @@ contract PageNameTest is Test { address beneficiary; address controller; uint256 controllerKey; - PageNameMarketplaceMock marketplace; function setUp() public { admin = vm.addr(1); @@ -43,7 +40,6 @@ contract PageNameTest is Test { controllerKey = 4; controller = vm.addr(controllerKey); - marketplace = new PageNameMarketplaceMock(); name = PageName( payable( address( @@ -57,10 +53,7 @@ contract PageNameTest is Test { owner, beneficiary, controller, - 1 ether, - 3, - 2, - marketplace + 3 ) ) ) @@ -77,17 +70,12 @@ contract PageNameTest is Test { assertEq(owner, name.owner()); assertEq(beneficiary, name.beneficiary()); assertEq(controller, name.controller()); - assertEq(1 ether, name.price()); assertEq(3, name.minimumLength()); - assertEq(2, name.profileLimit()); - assertEq(address(marketplace), address(name.marketplace())); } function test_ConfigureIfOwner() public { vm.startPrank(owner); - name.setProfileLimit(10); name.setMinimumLength(4); - name.setPrice(0 ether); name.setController(address(10)); name.pause(); name.unpause(); @@ -95,18 +83,10 @@ contract PageNameTest is Test { } function test_Revert_IfConfigureNotOwner() public { - vm.prank(address(1)); - vm.expectRevert(abi.encodeWithSelector(OwnableCallerNotTheOwner.selector, address(1))); - name.setProfileLimit(10); - vm.prank(address(1)); vm.expectRevert(abi.encodeWithSelector(OwnableCallerNotTheOwner.selector, address(1))); name.setMinimumLength(4); - vm.prank(address(1)); - vm.expectRevert(abi.encodeWithSelector(OwnableCallerNotTheOwner.selector, address(1))); - name.setPrice(0 ether); - vm.prank(address(1)); vm.expectRevert(abi.encodeWithSelector(OwnableCallerNotTheOwner.selector, address(1))); name.setController(address(100)); @@ -128,7 +108,7 @@ contract PageNameTest is Test { vm.prank(owner); name.pause(); vm.expectRevert("Pausable: paused"); - name.reserve(address(100), "test", 0, 0, 0); + name.reserve(address(100), "test", abi.encode(uint256(0)), 0, 0, 0); vm.expectRevert("Pausable: paused"); name.release(bytes32(uint256(1))); } @@ -182,26 +162,32 @@ contract PageNameTest is Test { string memory reservationName = string(buffer); (UniversalProfile profile,) = deployProfile(); + bytes memory salt = abi.encode(uint256(0)); + bytes32 hash = keccak256( - abi.encodePacked(address(name), block.chainid, address(profile), reservationName, uint256(0 ether)) + abi.encodePacked(address(name), block.chainid, address(profile), reservationName, salt, uint256(0 ether)) ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); vm.prank(address(profile)); vm.expectEmit(address(name)); emit ReservedName(address(profile), bytes32(bytes(reservationName)), 0 ether); - name.reserve(address(profile), reservationName, v, r, s); + name.reserve(address(profile), reservationName, salt, v, r, s); assertEq(1, name.balanceOf(address(profile))); } function test_Revert_ReserveIfUnathorized() public { (UniversalProfile profile,) = deployProfile(); - bytes32 hash = keccak256(abi.encodePacked(address(name), address(profile), "test", uint256(0 ether))); + bytes32 hash = keccak256( + abi.encodePacked( + address(name), block.chainid, address(profile), "test", abi.encode(uint256(0)), uint256(0 ether) + ) + ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(100, hash); vm.prank(address(profile)); vm.expectRevert( abi.encodeWithSelector(PageName.UnauthorizedReservation.selector, address(profile), "test", 0 ether) ); - name.reserve(address(profile), "test", v, r, s); + name.reserve(address(profile), "test", abi.encode(uint256(0)), v, r, s); } function testFuzz_Revert_ReserveIfShortName(uint8 minimumLength, string calldata reservationName) public { @@ -212,14 +198,21 @@ contract PageNameTest is Test { (UniversalProfile profile,) = deployProfile(); bytes32 hash = keccak256( - abi.encodePacked(address(name), block.chainid, address(profile), reservationName, uint256(0 ether)) + abi.encodePacked( + address(name), + block.chainid, + address(profile), + reservationName, + abi.encode(uint256(0)), + uint256(0 ether) + ) ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); vm.prank(address(profile)); vm.expectRevert( abi.encodeWithSelector(PageName.IncorrectReservationName.selector, address(profile), reservationName) ); - name.reserve(address(profile), reservationName, v, r, s); + name.reserve(address(profile), reservationName, abi.encode(uint256(0)), v, r, s); } function testFuzz_Revert_ReserveIfLongName(uint8 minimumLength, string calldata reservationName) public { @@ -230,14 +223,21 @@ contract PageNameTest is Test { (UniversalProfile profile,) = deployProfile(); bytes32 hash = keccak256( - abi.encodePacked(address(name), block.chainid, address(profile), reservationName, uint256(0 ether)) + abi.encodePacked( + address(name), + block.chainid, + address(profile), + reservationName, + abi.encode(uint256(0)), + uint256(0 ether) + ) ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); vm.prank(address(profile)); vm.expectRevert( abi.encodeWithSelector(PageName.IncorrectReservationName.selector, address(profile), reservationName) ); - name.reserve(address(profile), reservationName, v, r, s); + name.reserve(address(profile), reservationName, abi.encode(uint256(0)), v, r, s); } function testFuzz_Revert_ReserveIfContainsInvalidCharacters(bytes1 char) public { @@ -249,74 +249,63 @@ contract PageNameTest is Test { (UniversalProfile profile,) = deployProfile(); bytes32 hash = keccak256( - abi.encodePacked(address(name), block.chainid, address(profile), reservationName, uint256(0 ether)) + abi.encodePacked( + address(name), + block.chainid, + address(profile), + reservationName, + abi.encode(uint256(0)), + uint256(0 ether) + ) ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); vm.prank(address(profile)); vm.expectRevert( abi.encodeWithSelector(PageName.IncorrectReservationName.selector, address(profile), reservationName) ); - name.reserve(address(profile), reservationName, v, r, s); - } - - function test_Revert_ReserveWhenExceedProfileLimit() public { - (UniversalProfile profile,) = deployProfile(); - vm.prank(owner); - name.setProfileLimit(2); - { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(profile), "test1", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.prank(address(profile)); - name.reserve(address(profile), "test1", v, r, s); - assertEq(1, name.balanceOf(address(profile))); - } - { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(profile), "test2", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.prank(address(profile)); - name.reserve(address(profile), "test2", v, r, s); - assertEq(2, name.balanceOf(address(profile))); - } + name.reserve(address(profile), reservationName, abi.encode(uint256(0)), v, r, s); } - function test_ReserveWhenExceedProfileLimit() public { + function test_ReserveMultiple() public { (UniversalProfile profile,) = deployProfile(); - vm.prank(owner); - name.setProfileLimit(1); - vm.prank(owner); - name.setPrice(1.5 ether); { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(profile), "test1", uint256(0 ether))); + bytes32 hash = keccak256( + abi.encodePacked( + address(name), block.chainid, address(profile), "test1", abi.encode(uint256(0)), uint256(0 ether) + ) + ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); vm.prank(address(profile)); - name.reserve(address(profile), "test1", v, r, s); + vm.expectEmit(address(name)); + emit ReservedName(address(profile), bytes32(bytes("test1")), 0 ether); + name.reserve(address(profile), "test1", abi.encode(uint256(0)), v, r, s); assertEq(1, name.balanceOf(address(profile))); } { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(profile), "test2", uint256(1.5 ether))); + bytes32 hash = keccak256( + abi.encodePacked( + address(name), block.chainid, address(profile), "test2", abi.encode(uint256(1)), uint256(0 ether) + ) + ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.deal(address(profile), 2 ether); vm.prank(address(profile)); vm.expectEmit(address(name)); - emit ReservedName(address(profile), bytes32("test2"), 1.5 ether); - name.reserve{value: 1.5 ether}(address(profile), "test2", v, r, s); + emit ReservedName(address(profile), bytes32(bytes("test2")), 0 ether); + name.reserve(address(profile), "test2", abi.encode(uint256(1)), v, r, s); assertEq(2, name.balanceOf(address(profile))); - assertEq(1.5 ether, address(name).balance); - assertEq(0.5 ether, address(profile).balance); } } function test_Release() public { (UniversalProfile profile,) = deployProfile(); - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(profile), "test", uint256(0 ether))); + bytes32 hash = keccak256( + abi.encodePacked( + address(name), block.chainid, address(profile), "test", abi.encode(uint256(0)), uint256(0 ether) + ) + ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); vm.prank(address(profile)); - name.reserve(address(profile), "test", v, r, s); + name.reserve(address(profile), "test", abi.encode(uint256(0)), v, r, s); assertEq(1, name.totalSupply()); assertEq(1, name.balanceOf(address(profile))); @@ -330,11 +319,14 @@ contract PageNameTest is Test { function test_Revert_ReleaseIfUnathorized() public { (UniversalProfile alice,) = deployProfile(); - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(alice), "test", uint256(0 ether))); + bytes32 hash = keccak256( + abi.encodePacked( + address(name), block.chainid, address(alice), "test", abi.encode(uint256(0)), uint256(0 ether) + ) + ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); vm.prank(address(alice)); - name.reserve(address(alice), "test", v, r, s); + name.reserve(address(alice), "test", abi.encode(uint256(0)), v, r, s); (UniversalProfile bob,) = deployProfile(); vm.prank(address(bob)); @@ -342,249 +334,28 @@ contract PageNameTest is Test { name.release(bytes32("test")); } - function test_ReserveFreeIfPaidRelease() public { + function test_Revert_ReserveWhenReleasedAndUseSameSignature() public { (UniversalProfile profile,) = deployProfile(); - vm.prank(owner); - name.setProfileLimit(1); - vm.prank(owner); - name.setPrice(1 ether); - { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(profile), "test1", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.prank(address(profile)); - name.reserve(address(profile), "test1", v, r, s); - } + bytes32 hash = keccak256( + abi.encodePacked( + address(name), block.chainid, address(profile), "test", abi.encode(uint256(0)), uint256(0 ether) + ) + ); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(profile), "test2", uint256(1 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); vm.prank(address(profile)); - vm.deal(address(profile), 1 ether); - name.reserve{value: 1 ether}(address(profile), "test2", v, r, s); + name.reserve(address(profile), "test", abi.encode(uint256(0)), v, r, s); } { vm.prank(address(profile)); - name.release("test2"); + name.release(bytes32("test")); } { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(profile), "test3", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); vm.prank(address(profile)); - name.reserve(address(profile), "test3", v, r, s); - } - } - - function test_ReserveFreeWhenTransferred() public { - (UniversalProfile alice,) = deployProfile(); - (UniversalProfile bob,) = deployProfile(); - vm.prank(owner); - name.setProfileLimit(1); - vm.prank(owner); - name.setPrice(1 ether); - { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(alice), "test1", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.prank(address(alice)); - name.reserve(address(alice), "test1", v, r, s); - assertEq(1, name.balanceOf(address(alice))); - assertEq(0, name.balanceOf(address(bob))); - } - { - vm.prank(address(alice)); - name.transfer(address(alice), address(bob), bytes32("test1"), false, ""); - assertEq(0, name.balanceOf(address(alice))); - assertEq(1, name.balanceOf(address(bob))); - } - { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(alice), "test2", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.prank(address(alice)); - name.reserve(address(alice), "test2", v, r, s); - assertEq(1, name.balanceOf(address(alice))); - assertEq(1, name.balanceOf(address(bob))); - } - } - - function test_TransferWhenSold() public { - (UniversalProfile alice,) = deployProfile(); - (UniversalProfile bob,) = deployProfile(); - vm.prank(owner); - name.setProfileLimit(1); - vm.prank(owner); - name.setPrice(1.5 ether); - { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(alice), "test1", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.prank(address(alice)); - name.reserve(address(alice), "test1", v, r, s); - } - { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(bob), "test2", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.prank(address(bob)); - name.reserve(address(bob), "test2", v, r, s); - } - { - vm.prank(address(alice)); - name.authorizeOperator(address(marketplace), bytes32("test1"), ""); - } - { - marketplace.setPendingSale( - PendingSale({ - asset: address(name), - tokenId: bytes32("test1"), - seller: address(alice), - buyer: address(bob), - totalPaid: 1.5 ether - }) - ); - vm.prank(address(marketplace)); - name.transfer(address(alice), address(bob), bytes32("test1"), false, ""); - assertEq(0, name.balanceOf(address(alice))); - assertEq(2, name.balanceOf(address(bob))); - } - } - - function test_Revert_TransferIfSoldLow(uint256 price, uint256 totalPaid) public { - vm.assume(price > 0.1 ether); - vm.assume(totalPaid < price); - - (UniversalProfile alice,) = deployProfile(); - (UniversalProfile bob,) = deployProfile(); - vm.prank(owner); - name.setProfileLimit(1); - vm.prank(owner); - name.setPrice(price); - { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(alice), "test1", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.prank(address(alice)); - name.reserve(address(alice), "test1", v, r, s); - } - { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(bob), "test2", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.prank(address(bob)); - name.reserve(address(bob), "test2", v, r, s); - } - { - vm.prank(address(alice)); - name.authorizeOperator(address(marketplace), bytes32("test1"), ""); - } - { - marketplace.setPendingSale( - PendingSale({ - asset: address(name), - tokenId: bytes32("test1"), - seller: address(alice), - buyer: address(bob), - totalPaid: totalPaid - }) - ); - vm.prank(address(marketplace)); vm.expectRevert( - abi.encodeWithSelector( - PageName.TransferInvalidSale.selector, address(alice), address(bob), bytes32("test1"), totalPaid - ) + abi.encodeWithSelector(PageName.UnauthorizedReservation.selector, address(profile), "test", 0 ether) ); - name.transfer(address(alice), address(bob), bytes32("test1"), false, ""); - } - } - - function test_Revert_TransferWhenExceedProfileLimit() public { - (UniversalProfile alice,) = deployProfile(); - (UniversalProfile bob,) = deployProfile(); - vm.prank(owner); - name.setProfileLimit(1); - vm.prank(owner); - name.setPrice(1.5 ether); - { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(alice), "test1", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.prank(address(alice)); - name.reserve(address(alice), "test1", v, r, s); - } - { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(bob), "test2", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.prank(address(bob)); - name.reserve(address(bob), "test2", v, r, s); - } - { - vm.prank(address(alice)); - vm.expectRevert( - abi.encodeWithSelector( - PageName.TransferExceedLimit.selector, address(alice), address(bob), bytes32("test1"), 1 - ) - ); - name.transfer(address(alice), address(bob), bytes32("test1"), false, ""); - } - } - - function test_ReserveFreeAfterReleasedSold() public { - (UniversalProfile alice,) = deployProfile(); - (UniversalProfile bob,) = deployProfile(); - vm.prank(owner); - name.setProfileLimit(1); - vm.prank(owner); - name.setPrice(1.5 ether); - { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(alice), "test1", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.prank(address(alice)); - name.reserve(address(alice), "test1", v, r, s); - } - { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(bob), "test2", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.prank(address(bob)); - name.reserve(address(bob), "test2", v, r, s); - } - { - vm.prank(address(alice)); - name.authorizeOperator(address(marketplace), bytes32("test1"), ""); - } - { - marketplace.setPendingSale( - PendingSale({ - asset: address(name), - tokenId: bytes32("test1"), - seller: address(alice), - buyer: address(bob), - totalPaid: 1.5 ether - }) - ); - vm.prank(address(marketplace)); - name.transfer(address(alice), address(bob), bytes32("test1"), false, ""); - assertEq(0, name.balanceOf(address(alice))); - assertEq(2, name.balanceOf(address(bob))); - } - { - vm.prank(address(bob)); - vm.expectEmit(address(name)); - emit ReleasedName(address(bob), bytes32("test1")); - name.release(bytes32("test1")); - assertEq(1, name.balanceOf(address(bob))); - } - { - bytes32 hash = - keccak256(abi.encodePacked(address(name), block.chainid, address(bob), "test3", uint256(0 ether))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); - vm.prank(address(bob)); - name.reserve(address(bob), "test3", v, r, s); - assertEq(2, name.balanceOf(address(bob))); + name.reserve(address(profile), "test", abi.encode(uint256(0)), v, r, s); } } } diff --git a/test/page/PageNameMarketplaceMock.sol b/test/page/PageNameMarketplaceMock.sol deleted file mode 100644 index 589abc3..0000000 --- a/test/page/PageNameMarketplaceMock.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.22; - -import {IPageNameMarketplace, PendingSale} from "../../src/page/IPageNameMarketplace.sol"; - -contract PageNameMarketplaceMock is IPageNameMarketplace { - PendingSale private _pendingSale; - - function reset() public { - delete _pendingSale; - } - - function setPendingSale(PendingSale calldata pendingSale_) public { - _pendingSale = pendingSale_; - } - - function pendingSale() public view returns (PendingSale memory) { - return _pendingSale; - } -}