From 54ef43e8128444ae1a43bd9bf180cdde23e8b7e7 Mon Sep 17 00:00:00 2001 From: Volodymyr Lykhonis Date: Thu, 3 Nov 2022 07:14:45 -0400 Subject: [PATCH] Migrate slug to data storage https://github.com/lukso-network/LIPs/issues/136 --- contracts/page/IUniversalPageName.sol | 2 -- contracts/page/UniversalPageName.sol | 26 ++++++++++++++++++++------ tests/page/UniversalPageNameTest.js | 25 ++++++++++++++++++++++--- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/contracts/page/IUniversalPageName.sol b/contracts/page/IUniversalPageName.sol index ae61542..302b057 100644 --- a/contracts/page/IUniversalPageName.sol +++ b/contracts/page/IUniversalPageName.sol @@ -7,8 +7,6 @@ interface IUniversalPageName is ILSP8IdentifiableDigitalAsset { event ReservedName(address indexed recipient, bytes32 indexed tokenId); event ReleasedName(address indexed owner, bytes32 indexed tokenId); - function tokenSlugOf(bytes32 tokenId) external view returns (string memory); - function reserve(address recipient, string memory slug) external payable; function release(bytes32 tokenId) external; diff --git a/contracts/page/UniversalPageName.sol b/contracts/page/UniversalPageName.sol index 7fb8676..84462a0 100644 --- a/contracts/page/UniversalPageName.sol +++ b/contracts/page/UniversalPageName.sol @@ -15,9 +15,15 @@ contract UniversalPageName is { bytes32 private constant _LSP8_TOKEN_ID_TYPE_KEY = 0x715f248956de7ce65e94d9d836bfead479f7e70d69b718d47bfe7b00e05b4fe4; + bytes32 private constant _LSP8_TOKEN_ID_HASH_FUNCTION_KEY = + 0x200d7fc10771371795cceb6ad6d33d5959c47250486fb93f77f0c20088b90a97; + bytes12 private constant _LSP8_TOKEN_ID_VALUE_KEY_PREFIX = 0x60fcfd25909da10abc320000; + bytes private constant _LSP8_TOKEN_ID_TYPE_HASHED = hex"00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000003"; + bytes private constant _LSP8_TOKEN_ID_HASH_FUNCTION = "keccak256(bytes)"; + // TODO: remove this! mapping(bytes32 => string) private _tokenSlug; modifier onlyOperator(bytes32 tokenId) { @@ -34,20 +40,17 @@ contract UniversalPageName is ) external initializer { LSP8IdentifiableDigitalAssetInitAbstract._initialize(name, symbol, owner); _setData(_LSP8_TOKEN_ID_TYPE_KEY, _LSP8_TOKEN_ID_TYPE_HASHED); + _setData(_LSP8_TOKEN_ID_HASH_FUNCTION_KEY, _LSP8_TOKEN_ID_HASH_FUNCTION); _setAdmin(admin); _setBeneficiary(beneficiary); } - function tokenSlugOf(bytes32 tokenId) external view override returns (string memory) { - return _tokenSlug[tokenId]; - } - function reserve(address recipient, string memory slug) external payable override { require(_isAllowedSlug(slug), "Invalid slug"); bytes32 tokenId = keccak256(bytes(slug)); IUniversalPageNameDelegate(admin()).permitReserve(msg.sender, msg.value, recipient, slug); _mint(recipient, tokenId, false, "0x"); - _tokenSlug[tokenId] = slug; + _setData(_tokenIdKey(_LSP8_TOKEN_ID_VALUE_KEY_PREFIX, tokenId), bytes(slug)); emit ReservedName(recipient, tokenId); } @@ -66,8 +69,19 @@ contract UniversalPageName is super._beforeTokenTransfer(from, to, tokenId); IUniversalPageNameDelegate(admin()).permitTransfer(msg.sender, from, to, tokenId); if (to == address(0)) { - delete _tokenSlug[tokenId]; + bytes32 tokenIdKey = _tokenIdKey(_LSP8_TOKEN_ID_VALUE_KEY_PREFIX, tokenId); + delete store[tokenIdKey]; + } + } + + function _tokenIdKey(bytes12 prefix, bytes32 tokenId) private pure returns (bytes32) { + bytes20 tokenIdPart = bytes20(tokenId); + bytes memory result = new bytes(32); + assembly { + mstore(add(result, 32), prefix) + mstore(add(result, 44), tokenIdPart) } + return bytes32(result); } function _isAllowedSlug(string memory value) private pure returns (bool) { diff --git a/tests/page/UniversalPageNameTest.js b/tests/page/UniversalPageNameTest.js index ca2cabf..43a7e12 100644 --- a/tests/page/UniversalPageNameTest.js +++ b/tests/page/UniversalPageNameTest.js @@ -32,6 +32,16 @@ describe('UniversalPageName', () => { ), ) + const tokenSlugOf = async (tokenId) => { + const data = await pageContract['getData(bytes32)']( + ethers.utils.hexConcat([ + ethers.utils.hexDataSlice(ethers.utils.keccak256(ethers.utils.toUtf8Bytes('LSP8TokenIdValue')), 0, 10), + '0x0000', + ethers.utils.hexDataSlice(tokenId, 0, 20), + ])) + return ethers.utils.toUtf8String(data) + } + beforeEach(async () => { [deployer, owner, beneficiary, alice, bob] = await ethers.getSigners(); [aliceProfile, aliceOwner] = await deployProfile(deployer, alice) @@ -105,6 +115,15 @@ describe('UniversalPageName', () => { }) }) + it('should setup token', async () => { + const [type, hashFunction] = await pageContract['getData(bytes32[])']([ + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('LSP8TokenIdType')), + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('LSP8TokenIdHashFunction')), + ]) + expect(ethers.BigNumber.from(type).toNumber()).to.be.eq(3) + expect(ethers.utils.toUtf8String(hashFunction)).to.be.eq('keccak256(bytes)') + }) + it('should not set owner', async () => { await expect(contract.connect(bob).transferOwnership(bob.address)).to.be.revertedWith('not the owner') }) @@ -190,7 +209,7 @@ describe('UniversalPageName', () => { tokenId, ) expect(await pageContract.totalSupply()).to.be.eq(ethers.BigNumber.from(1)) - expect(await pageContract.tokenSlugOf(tokenId)).to.be.eq('test') + expect(await tokenSlugOf(tokenId)).to.be.eq('test') expect(await pageContract.tokenOwnerOf(tokenId)).to.be.eq(aliceProfile.address) }) @@ -300,7 +319,7 @@ describe('UniversalPageName', () => { it('should release', async () => { expect(await pageContract.totalSupply()).to.be.eq(1) expect(await pageContract.tokenOwnerOf(tokenOne)).to.be.eq(aliceProfile.address) - expect(await pageContract.tokenSlugOf(tokenOne)).to.be.eq('token1') + expect(await tokenSlugOf(tokenOne)).to.be.eq('token1') await expect(aliceOwner.connect(alice).execute( aliceProfile.interface.encodeFunctionData('execute(uint256,address,uint256,bytes)', [ @@ -318,7 +337,7 @@ describe('UniversalPageName', () => { )).to.emit(pageContract, 'ReleasedName').withArgs(aliceProfile.address, tokenOne) expect(await pageContract.totalSupply()).to.be.eq(ethers.BigNumber.from(0)) await expect(pageContract.tokenOwnerOf(tokenOne)).to.be.revertedWith('NonExistentTokenId') - expect(await pageContract.tokenSlugOf(tokenOne)).to.be.empty + expect(await tokenSlugOf(tokenOne)).to.be.empty }) it('should reserve free after release', async () => {