misc: TokenURI contracts separated

This commit is contained in:
vicnaum
2023-11-14 09:26:06 +01:00
committed by donosonaumczuk
parent 1e925dde1f
commit 2521ef4a29
46 changed files with 240 additions and 18 deletions

View File

@@ -13,8 +13,8 @@ import {ILensHub} from 'contracts/interfaces/ILensHub.sol';
import {LensBaseERC721} from 'contracts/base/LensBaseERC721.sol'; import {LensBaseERC721} from 'contracts/base/LensBaseERC721.sol';
import {Strings} from '@openzeppelin/contracts/utils/Strings.sol'; import {Strings} from '@openzeppelin/contracts/utils/Strings.sol';
import {StorageLib} from 'contracts/libraries/StorageLib.sol'; import {StorageLib} from 'contracts/libraries/StorageLib.sol';
import {FollowTokenURILib} from 'contracts/libraries/token-uris/FollowTokenURILib.sol';
import {Types} from 'contracts/libraries/constants/Types.sol'; import {Types} from 'contracts/libraries/constants/Types.sol';
import {IFollowTokenURI} from 'contracts/interfaces/IFollowTokenURI.sol';
/** /**
* @custom:upgradeable Beacon proxy. The beacon, responsible for returning the implementation address, is the LensHub. * @custom:upgradeable Beacon proxy. The beacon, responsible for returning the implementation address, is the LensHub.
@@ -282,7 +282,7 @@ contract FollowNFT is HubRestricted, LensBaseERC721, ERC2981CollectionRoyalties,
revert Errors.TokenDoesNotExist(); revert Errors.TokenDoesNotExist();
} }
return return
FollowTokenURILib.getTokenURI( IFollowTokenURI(ILensHub(HUB).getFollowTokenURIContract()).getTokenURI(
followTokenId, followTokenId,
_followedProfileId, _followedProfileId,
_followDataByFollowTokenId[followTokenId].originalFollowTimestamp _followDataByFollowTokenId[followTokenId].originalFollowTimestamp

View File

@@ -52,6 +52,14 @@ abstract contract LensGovernable is ILensGovernable {
GovernanceLib.whitelistProfileCreator(profileCreator, whitelist); GovernanceLib.whitelistProfileCreator(profileCreator, whitelist);
} }
function setProfileTokenURIContract(address profileTokenURIContract) external override onlyGov {
GovernanceLib.setProfileTokenURIContract(profileTokenURIContract);
}
function setFollowTokenURIContract(address followTokenURIContract) external override onlyGov {
GovernanceLib.setFollowTokenURIContract(followTokenURIContract);
}
/////////////////////////////////////////// ///////////////////////////////////////////
/// EXTERNAL VIEW FUNCTIONS /// /// EXTERNAL VIEW FUNCTIONS ///
/////////////////////////////////////////// ///////////////////////////////////////////
@@ -61,6 +69,14 @@ abstract contract LensGovernable is ILensGovernable {
return StorageLib.getGovernance(); return StorageLib.getGovernance();
} }
function getProfileTokenURIContract() external view override returns (address) {
return StorageLib.getProfileTokenURIContract();
}
function getFollowTokenURIContract() external view override returns (address) {
return StorageLib.getFollowTokenURIContract();
}
/** /**
* @notice Returns the current protocol state. * @notice Returns the current protocol state.
* *

View File

@@ -60,4 +60,8 @@ abstract contract LensHubStorage {
mapping(address migrationAdmin => bool allowed) internal _migrationAdminWhitelisted; // Slot 29 mapping(address migrationAdmin => bool allowed) internal _migrationAdminWhitelisted; // Slot 29
Types.TreasuryData internal _treasuryData; // Slot 30 Types.TreasuryData internal _treasuryData; // Slot 30
address internal _profileTokenURIContract; // Slot 31
address internal _followTokenURIContract; // Slot 32
} }

View File

@@ -11,8 +11,8 @@ import {IERC721Burnable} from 'contracts/interfaces/IERC721Burnable.sol';
import {LensBaseERC721} from 'contracts/base/LensBaseERC721.sol'; import {LensBaseERC721} from 'contracts/base/LensBaseERC721.sol';
import {ProfileLib} from 'contracts/libraries/ProfileLib.sol'; import {ProfileLib} from 'contracts/libraries/ProfileLib.sol';
import {StorageLib} from 'contracts/libraries/StorageLib.sol'; import {StorageLib} from 'contracts/libraries/StorageLib.sol';
import {ProfileTokenURILib} from 'contracts/libraries/token-uris/ProfileTokenURILib.sol';
import {ValidationLib} from 'contracts/libraries/ValidationLib.sol'; import {ValidationLib} from 'contracts/libraries/ValidationLib.sol';
import {IProfileTokenURI} from 'contracts/interfaces/IProfileTokenURI.sol';
import {ERC2981CollectionRoyalties} from 'contracts/base/ERC2981CollectionRoyalties.sol'; import {ERC2981CollectionRoyalties} from 'contracts/base/ERC2981CollectionRoyalties.sol';
@@ -99,7 +99,8 @@ abstract contract LensProfiles is LensBaseERC721, ERC2981CollectionRoyalties, IL
if (!_exists(tokenId)) { if (!_exists(tokenId)) {
revert Errors.TokenDoesNotExist(); revert Errors.TokenDoesNotExist();
} }
return ProfileTokenURILib.getTokenURI(tokenId); uint256 mintTimestamp = StorageLib.getTokenData(tokenId).mintTimestamp;
return IProfileTokenURI(StorageLib.getProfileTokenURIContract()).getTokenURI(tokenId, mintTimestamp);
} }
function approve(address to, uint256 tokenId) public override(LensBaseERC721, IERC721) { function approve(address to, uint256 tokenId) public override(LensBaseERC721, IERC721) {

View File

@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
interface IFollowTokenURI {
function getTokenURI(
uint256 followTokenId,
uint256 followedProfileId,
uint256 originalFollowTimestamp
) external pure returns (string memory);
}

View File

@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
interface IHandleTokenURI {
function getTokenURI(
uint256 tokenId,
string memory localName,
string memory namespace
) external view returns (string memory);
}

View File

@@ -47,6 +47,22 @@ interface ILensGovernable {
*/ */
function whitelistProfileCreator(address profileCreator, bool whitelist) external; function whitelistProfileCreator(address profileCreator, bool whitelist) external;
/**
* @notice Sets the profile token URI contract.
* @custom:permissions Governance.
*
* @param profileTokenURIContract The profile token URI contract to set.
*/
function setProfileTokenURIContract(address profileTokenURIContract) external;
/**
* @notice Sets the follow token URI contract.
* @custom:permissions Governance.
*
* @param followTokenURIContract The follow token URI contract to set.
*/
function setFollowTokenURIContract(address followTokenURIContract) external;
/** /**
* @notice Sets the treasury address. * @notice Sets the treasury address.
* @custom:permissions Governance * @custom:permissions Governance
@@ -108,4 +124,18 @@ interface ILensGovernable {
* @return tuple First, the treasury address, second, the treasury fee. * @return tuple First, the treasury address, second, the treasury fee.
*/ */
function getTreasuryData() external view returns (address, uint16); function getTreasuryData() external view returns (address, uint16);
/**
* @notice Gets the profile token URI contract.
*
* @return address The profile token URI contract.
*/
function getProfileTokenURIContract() external view returns (address);
/**
* @notice Gets the follow token URI contract.
*
* @return address The follow token URI contract.
*/
function getFollowTokenURIContract() external view returns (address);
} }

View File

@@ -63,6 +63,21 @@ interface ILensHandles is IERC721 {
*/ */
function totalSupply() external view returns (uint256); function totalSupply() external view returns (uint256);
/**
* @notice Returns the HandleTokenURI contract address.
*
* @return address The HandleTokenURI contract address.
*/
function getHandleTokenURIContract() external view returns (address);
/**
* @notice Sets the HandleTokenURI contract address.
* @custom:permissions Only LensHandles contract's owner
*
* @param handleTokenURIContract The HandleTokenURI contract address to set.
*/
function setHandleTokenURIContract(address handleTokenURIContract) external;
/** /**
* @notice DANGER: Triggers disabling the profile protection mechanism for the msg.sender, which will allow * @notice DANGER: Triggers disabling the profile protection mechanism for the msg.sender, which will allow
* transfers or approvals over profiles held by it. * transfers or approvals over profiles held by it.

View File

@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
interface IProfileTokenURI {
function getTokenURI(uint256 profileId, uint256 mintTimestamp) external pure returns (string memory);
}

View File

@@ -100,4 +100,12 @@ library GovernanceLib {
emit Events.TreasuryFeeSet(prevTreasuryFee, newTreasuryFee, block.timestamp); emit Events.TreasuryFeeSet(prevTreasuryFee, newTreasuryFee, block.timestamp);
} }
function setProfileTokenURIContract(address profileTokenURIContract) external {
StorageLib.setProfileTokenURIContract(profileTokenURIContract);
}
function setFollowTokenURIContract(address followTokenURIContract) external {
StorageLib.setFollowTokenURIContract(followTokenURIContract);
}
} }

View File

@@ -43,6 +43,8 @@ library StorageLib {
uint256 constant PROFILE_ROYALTIES_BPS_SLOT = 28; uint256 constant PROFILE_ROYALTIES_BPS_SLOT = 28;
uint256 constant MIGRATION_ADMINS_WHITELISTED_MAPPING_SLOT = 29; uint256 constant MIGRATION_ADMINS_WHITELISTED_MAPPING_SLOT = 29;
uint256 constant TREASURY_DATA_SLOT = 30; uint256 constant TREASURY_DATA_SLOT = 30;
uint256 constant PROFILE_TOKEN_URI_CONTRACT_SLOT = 31;
uint256 constant FOLLOW_TOKEN_URI_CONTRACT_SLOT = 32;
function getPublication( function getPublication(
uint256 profileId, uint256 profileId,
@@ -211,4 +213,28 @@ library StorageLib {
_treasuryData.slot := TREASURY_DATA_SLOT _treasuryData.slot := TREASURY_DATA_SLOT
} }
} }
function setProfileTokenURIContract(address profileTokenURIContract) internal {
assembly {
sstore(PROFILE_TOKEN_URI_CONTRACT_SLOT, profileTokenURIContract)
}
}
function setFollowTokenURIContract(address followTokenURIContract) internal {
assembly {
sstore(FOLLOW_TOKEN_URI_CONTRACT_SLOT, followTokenURIContract)
}
}
function getProfileTokenURIContract() internal view returns (address _profileTokenURIContract) {
assembly {
_profileTokenURIContract := sload(PROFILE_TOKEN_URI_CONTRACT_SLOT)
}
}
function getFollowTokenURIContract() internal view returns (address _followTokenURIContract) {
assembly {
_followTokenURIContract := sload(FOLLOW_TOKEN_URI_CONTRACT_SLOT)
}
}
} }

View File

@@ -4,9 +4,10 @@ pragma solidity ^0.8.15;
import {Base64} from '@openzeppelin/contracts/utils/Base64.sol'; import {Base64} from '@openzeppelin/contracts/utils/Base64.sol';
import {Strings} from '@openzeppelin/contracts/utils/Strings.sol'; import {Strings} from '@openzeppelin/contracts/utils/Strings.sol';
import {FollowSVG} from 'contracts/libraries/token-uris/images/Follow/FollowSVG.sol'; import {FollowSVG} from 'contracts/libraries/svgs/Follow/FollowSVG.sol';
import {IFollowTokenURI} from 'contracts/interfaces/IFollowTokenURI.sol';
library FollowTokenURILib { contract FollowTokenURI is IFollowTokenURI {
using Strings for uint96; using Strings for uint96;
using Strings for uint256; using Strings for uint256;
@@ -14,7 +15,7 @@ library FollowTokenURILib {
uint256 followTokenId, uint256 followTokenId,
uint256 followedProfileId, uint256 followedProfileId,
uint256 originalFollowTimestamp uint256 originalFollowTimestamp
) external pure returns (string memory) { ) external pure override returns (string memory) {
string memory followTokenIdAsString = followTokenId.toString(); string memory followTokenIdAsString = followTokenId.toString();
string memory followedProfileIdAsString = followedProfileId.toString(); string memory followedProfileIdAsString = followedProfileId.toString();
return return

View File

@@ -4,16 +4,17 @@ pragma solidity ^0.8.15;
import {Base64} from '@openzeppelin/contracts/utils/Base64.sol'; import {Base64} from '@openzeppelin/contracts/utils/Base64.sol';
import {Strings} from '@openzeppelin/contracts/utils/Strings.sol'; import {Strings} from '@openzeppelin/contracts/utils/Strings.sol';
import {HandleSVG} from 'contracts/libraries/token-uris/images/Handle/HandleSVG.sol'; import {HandleSVG} from 'contracts/libraries/svgs/Handle/HandleSVG.sol';
import {IHandleTokenURI} from 'contracts/interfaces/IHandleTokenURI.sol';
library HandleTokenURILib { contract HandleTokenURI is IHandleTokenURI {
using Strings for uint256; using Strings for uint256;
function getTokenURI( function getTokenURI(
uint256 tokenId, uint256 tokenId,
string memory localName, string memory localName,
string memory namespace string memory namespace
) external pure returns (string memory) { ) external pure override returns (string memory) {
return return
string.concat( string.concat(
'data:application/json;base64,', 'data:application/json;base64,',

View File

@@ -4,14 +4,14 @@ pragma solidity ^0.8.15;
import {Base64} from '@openzeppelin/contracts/utils/Base64.sol'; import {Base64} from '@openzeppelin/contracts/utils/Base64.sol';
import {Strings} from '@openzeppelin/contracts/utils/Strings.sol'; import {Strings} from '@openzeppelin/contracts/utils/Strings.sol';
import {StorageLib} from 'contracts/libraries/StorageLib.sol'; import {ProfileSVG} from 'contracts/libraries/svgs/Profile/ProfileSVG.sol';
import {ProfileSVG} from 'contracts/libraries/token-uris/images/Profile/ProfileSVG.sol'; import {IProfileTokenURI} from 'contracts/interfaces/IProfileTokenURI.sol';
library ProfileTokenURILib { contract ProfileTokenURI is IProfileTokenURI {
using Strings for uint96; using Strings for uint96;
using Strings for uint256; using Strings for uint256;
function getTokenURI(uint256 profileId) external view returns (string memory) { function getTokenURI(uint256 profileId, uint256 mintTimestamp) external pure override returns (string memory) {
string memory profileIdAsString = profileId.toString(); string memory profileIdAsString = profileId.toString();
return return
string( string(
@@ -32,7 +32,7 @@ library ProfileTokenURILib {
'"},{"trait_type":"DIGITS","value":"', '"},{"trait_type":"DIGITS","value":"',
bytes(profileIdAsString).length.toString(), bytes(profileIdAsString).length.toString(),
'"},{"trait_type":"MINTED AT","value":"', '"},{"trait_type":"MINTED AT","value":"',
StorageLib.getTokenData(profileId).mintTimestamp.toString(), mintTimestamp.toString(),
'"}]}' '"}]}'
) )
) )

View File

@@ -7,7 +7,7 @@ import {ImmutableOwnable} from 'contracts/misc/ImmutableOwnable.sol';
import {ILensHandles} from 'contracts/interfaces/ILensHandles.sol'; import {ILensHandles} from 'contracts/interfaces/ILensHandles.sol';
import {HandlesEvents} from 'contracts/namespaces/constants/Events.sol'; import {HandlesEvents} from 'contracts/namespaces/constants/Events.sol';
import {HandlesErrors} from 'contracts/namespaces/constants/Errors.sol'; import {HandlesErrors} from 'contracts/namespaces/constants/Errors.sol';
import {HandleTokenURILib} from 'contracts/libraries/token-uris/HandleTokenURILib.sol'; import {IHandleTokenURI} from 'contracts/interfaces/IHandleTokenURI.sol';
import {ILensHub} from 'contracts/interfaces/ILensHub.sol'; import {ILensHub} from 'contracts/interfaces/ILensHub.sol';
import {Address} from '@openzeppelin/contracts/utils/Address.sol'; import {Address} from '@openzeppelin/contracts/utils/Address.sol';
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol'; import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
@@ -44,6 +44,8 @@ contract LensHandles is ERC721, ERC2981CollectionRoyalties, ImmutableOwnable, IL
mapping(uint256 tokenId => string localName) internal _localNames; mapping(uint256 tokenId => string localName) internal _localNames;
address internal _handleTokenURIContract;
modifier onlyOwnerOrWhitelistedProfileCreator() { modifier onlyOwnerOrWhitelistedProfileCreator() {
if (msg.sender != OWNER && !ILensHub(LENS_HUB).isProfileCreatorWhitelisted(msg.sender)) { if (msg.sender != OWNER && !ILensHub(LENS_HUB).isProfileCreatorWhitelisted(msg.sender)) {
revert HandlesErrors.NotOwnerNorWhitelisted(); revert HandlesErrors.NotOwnerNorWhitelisted();
@@ -85,12 +87,20 @@ contract LensHandles is ERC721, ERC2981CollectionRoyalties, ImmutableOwnable, IL
return _totalSupply; return _totalSupply;
} }
function setHandleTokenURIContract(address handleTokenURIContract) external override onlyOwner {
_handleTokenURIContract = handleTokenURIContract;
}
function getHandleTokenURIContract() external view override returns (address) {
return _handleTokenURIContract;
}
/** /**
* @dev See {IERC721Metadata-tokenURI}. * @dev See {IERC721Metadata-tokenURI}.
*/ */
function tokenURI(uint256 tokenId) public view override returns (string memory) { function tokenURI(uint256 tokenId) public view override returns (string memory) {
_requireMinted(tokenId); _requireMinted(tokenId);
return HandleTokenURILib.getTokenURI(tokenId, _localNames[tokenId], NAMESPACE); return IHandleTokenURI(_handleTokenURIContract).getTokenURI(tokenId, _localNames[tokenId], NAMESPACE);
} }
/// @inheritdoc ILensHandles /// @inheritdoc ILensHandles

View File

@@ -10,7 +10,7 @@ import {Types} from 'contracts/libraries/constants/Types.sol';
import {Governance} from 'contracts/misc/access/Governance.sol'; import {Governance} from 'contracts/misc/access/Governance.sol';
import {LensHandles} from 'contracts/namespaces/LensHandles.sol'; import {LensHandles} from 'contracts/namespaces/LensHandles.sol';
contract DeploySVG is Script, ForkManagement { contract DeployCore is Script, ForkManagement {
using stdJson for string; using stdJson for string;
struct LensAccount { struct LensAccount {

View File

@@ -0,0 +1,81 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {ForkManagement} from 'script/helpers/ForkManagement.sol';
import 'forge-std/Script.sol';
import {ProfileTokenURI} from 'contracts/misc/token-uris/ProfileTokenURI.sol';
import {HandleTokenURI} from 'contracts/misc/token-uris/HandleTokenURI.sol';
import {FollowTokenURI} from 'contracts/misc/token-uris/FollowTokenURI.sol';
contract DeployTokenURIs is Script, ForkManagement {
using stdJson for string;
struct LensAccount {
uint256 ownerPk;
address owner;
uint256 profileId;
}
LensAccount _deployer;
string mnemonic;
function saveContractAddress(string memory contractName, address deployedAddress) internal {
// console.log('Saving %s (%s) into addresses under %s environment', contractName, deployedAddress, targetEnv);
string[] memory inputs = new string[](5);
inputs[0] = 'node';
inputs[1] = 'script/helpers/saveAddress.js';
inputs[2] = targetEnv;
inputs[3] = contractName;
inputs[4] = vm.toString(deployedAddress);
// bytes memory res =
vm.ffi(inputs);
// string memory output = abi.decode(res, (string));
// console.log(output);
}
function loadPrivateKeys() internal {
if (isEnvSet('MNEMONIC')) {
mnemonic = vm.envString('MNEMONIC');
}
if (bytes(mnemonic).length == 0) {
revert('Missing mnemonic');
}
console.log('\n');
(_deployer.owner, _deployer.ownerPk) = deriveRememberKey(mnemonic, 0);
console.log('Deployer address: %s', address(_deployer.owner));
console.log('\n');
console.log('Current block:', block.number);
}
function deploy() internal {
vm.startBroadcast(_deployer.ownerPk);
address profileTokenURI = address(new ProfileTokenURI());
address handleTokenURI = address(new HandleTokenURI());
address followTokenURI = address(new FollowTokenURI());
vm.stopBroadcast();
console.log('\n');
console.log('ProfileTokenURI address: %s', address(profileTokenURI));
saveContractAddress('ProfileTokenURI', profileTokenURI);
console.log('HandleTokenURI address: %s', address(handleTokenURI));
saveContractAddress('HandleTokenURI', handleTokenURI);
console.log('FollowTokenURI address: %s', address(followTokenURI));
saveContractAddress('FollowTokenURI', followTokenURI);
console.log('\n');
}
function run(string memory targetEnv_) external {
targetEnv = targetEnv_;
loadJson();
checkNetworkParams();
loadBaseAddresses();
loadPrivateKeys();
deploy();
}
}