From ec4e5a3b94b62afaaa85193ed435bcc5a86ab23b Mon Sep 17 00:00:00 2001 From: Maxime Date: Tue, 20 Feb 2024 10:43:51 +0000 Subject: [PATCH 1/5] Create lsp23 script for hardhat --- .../deployUniversalProfiileWithLSP23.ts | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 smart-contracts-hardhat/scripts/deployUniversalProfiileWithLSP23.ts diff --git a/smart-contracts-hardhat/scripts/deployUniversalProfiileWithLSP23.ts b/smart-contracts-hardhat/scripts/deployUniversalProfiileWithLSP23.ts new file mode 100644 index 0000000..669cbeb --- /dev/null +++ b/smart-contracts-hardhat/scripts/deployUniversalProfiileWithLSP23.ts @@ -0,0 +1,149 @@ +// libs +import { ethers } from 'hardhat'; +import { AbiCoder } from 'ethers'; +import { ERC725 } from '@erc725/erc725.js'; + +// LSPs artifacts +import LSP23FactoryArtifact from '@lukso/lsp-smart-contracts/artifacts/LSP23LinkedContractsFactory.json'; +import UniversalProfileInitArtifact from '@lukso/lsp-smart-contracts/artifacts/UniversalProfileInit.json'; + +// ERC725.js schemas +import LSP1UniversalReceiverDelegateSchemas from '@erc725/erc725.js/schemas/LSP1UniversalReceiverDelegate.json'; +import LSP3ProfileMetadataSchemas from '@erc725/erc725.js/schemas/LSP3ProfileMetadata.json'; +import LSP6KeyManagerSchemas from '@erc725/erc725.js/schemas/LSP6KeyManager.json'; + +const LSP23_FACTORY_ADDRESS = '0x2300000A84D25dF63081feAa37ba6b62C4c89a30'; +const LSP23_POST_DEPLOYMENT_MODULE = '0x000000000066093407b6704B89793beFfD0D8F00'; +const UNIVERSAL_PROFILE_IMPLEMENTATION_ADDRESS = '0x3024D38EA2434BA6635003Dc1BDC0daB5882ED4F'; +const LSP6_KEY_MANAGER_IMPLEMENTATION_ADDRESS = '0x2Fe3AeD98684E7351aD2D408A43cE09a738BF8a4'; +const UNIVERSAL_RECEIVER_ADDRESS = '0x7870C5B8BC9572A8001C3f96f7ff59961B23500D'; // this will be needed later so we can set the Universal Receiver to the Universal Profile (see https://docs.lukso.tech/standards/generic-standards/lsp1-universal-receiver) +const MAIN_CONTROLLER = '0x3303Ce3b8644D566271DD2Eb54292d32F1458968'; +const SALT = '0x5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed'; + +async function main() { + // Interacting with the LSP23Factory contract + const lsp23FactoryContract = await ethers.getContractAtFromArtifact( + LSP23FactoryArtifact, + LSP23_FACTORY_ADDRESS, + ); + + // Interacting with the UniversalProfileImplementation contract + const universalProfileImplementationContract = await ethers.getContractAtFromArtifact( + UniversalProfileInitArtifact, + UNIVERSAL_PROFILE_IMPLEMENTATION_ADDRESS, + ); + + // create the init structs + const universalProfileInitStruct = { + salt: SALT, + fundingAmount: 0, + implementationContract: UNIVERSAL_PROFILE_IMPLEMENTATION_ADDRESS, + initializationCalldata: universalProfileImplementationContract.interface.encodeFunctionData( + 'initialize', + [LSP23_POST_DEPLOYMENT_MODULE], + ), // this will call the `initialize(...)` function of the Universal Profile and the the LSP23_POST_DEPLOYMENT_MODULE as owner + }; + + const keyManagerInitStruct = { + fundingAmount: 0, + implementationContract: LSP6_KEY_MANAGER_IMPLEMENTATION_ADDRESS, + addPrimaryContractAddress: true, // this will append the primary contract address to the init calldata + initializationCalldata: '0xc4d66de8', // `initialize(...)` function selector + extraInitializationParams: '0x', + }; + + // instantiate the erc725 class + const erc725 = new ERC725([ + ...LSP6KeyManagerSchemas, + ...LSP3ProfileMetadataSchemas, + ...LSP1UniversalReceiverDelegateSchemas, + ]); + + // create the LSP3Metadata data value + const lsp3DataValue = { + verification: { + method: 'keccak256(utf8)', + data: '0x6d6d08aafb0ee059e3e4b6b3528a5be37308a5d4f4d19657d26dd8a5ae799de0', + }, + url: 'ipfs://QmPRoJsaYcNqQiUrQxE7ajTRaXwHyAU29tHqYNctBmK64w', + }; + + // create the permissions data keys + const setDataKeysAndValues = erc725.encodeData([ + { keyName: 'LSP3Profile', value: lsp3DataValue }, // LSP3Metadata data key and value + { + keyName: 'LSP1UniversalReceiverDelegate', + value: UNIVERSAL_RECEIVER_ADDRESS, + }, // Universal Receiver data key and value + { + keyName: 'AddressPermissions:Permissions:
', + dynamicKeyParts: [UNIVERSAL_RECEIVER_ADDRESS], + value: erc725.encodePermissions({ + REENTRANCY: true, + SUPER_SETDATA: true, + }), + }, // Universal Receiver Delegate permissions data key and value + { + keyName: 'AddressPermissions:Permissions:
', + dynamicKeyParts: [MAIN_CONTROLLER], + value: erc725.encodePermissions({ + CHANGEOWNER: true, + ADDCONTROLLER: true, + EDITPERMISSIONS: true, + ADDEXTENSIONS: true, + CHANGEEXTENSIONS: true, + ADDUNIVERSALRECEIVERDELEGATE: true, + CHANGEUNIVERSALRECEIVERDELEGATE: true, + REENTRANCY: false, + SUPER_TRANSFERVALUE: true, + TRANSFERVALUE: true, + SUPER_CALL: true, + CALL: true, + SUPER_STATICCALL: true, + STATICCALL: true, + SUPER_DELEGATECALL: false, + DELEGATECALL: false, + DEPLOY: true, + SUPER_SETDATA: true, + SETDATA: true, + ENCRYPT: true, + DECRYPT: true, + SIGN: true, + EXECUTE_RELAY_CALL: true, + }), // Main Controller permissions data key and value + }, + // length of the Address Permissions array and their respective indexed keys and values + { + keyName: 'AddressPermissions[]', + value: [UNIVERSAL_RECEIVER_ADDRESS, MAIN_CONTROLLER], + }, + ]); + + const abiCoder = new AbiCoder(); + const types = ['bytes32[]', 'bytes[]']; // types of the parameters + + const initializeEncodedBytes = abiCoder.encode(types, [ + setDataKeysAndValues.keys, + setDataKeysAndValues.values, + ]); + + // deploy the Universal Profile and its Key Manager + const [upAddress, keyManagerAddress] = await lsp23FactoryContract.deployERC1167Proxies.staticCall( + universalProfileInitStruct, + keyManagerInitStruct, + LSP23_POST_DEPLOYMENT_MODULE, + initializeEncodedBytes, + ); + console.log('Universal Profile address:', upAddress); + console.log('Key Manager address:', keyManagerAddress); + + const tx = await lsp23FactoryContract.deployERC1167Proxies( + universalProfileInitStruct, + keyManagerInitStruct, + LSP23_POST_DEPLOYMENT_MODULE, + initializeEncodedBytes, + ); + await tx.wait(); +} + +main(); From 07bf7208fc2c7ab9adc4351182176fc2ec8d99b6 Mon Sep 17 00:00:00 2001 From: Felix Hildebrandt Date: Thu, 22 Feb 2024 18:05:40 +0100 Subject: [PATCH 2/5] Add tokenID formatter --- digital-assets/tokenIdFormat.ts | 97 +++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 digital-assets/tokenIdFormat.ts diff --git a/digital-assets/tokenIdFormat.ts b/digital-assets/tokenIdFormat.ts new file mode 100644 index 0000000..1a8faa0 --- /dev/null +++ b/digital-assets/tokenIdFormat.ts @@ -0,0 +1,97 @@ +import { ethers } from 'ethers'; +import lsp8Artifact from '@lukso/lsp-smart-contracts/artifacts/LSP8IdentifiableDigitalAsset.json'; +import { ERC725YDataKeys } from '@lukso/lsp-smart-contracts'; + +const SAMPLE_LSP8_ASSET = '0x8734600968c7e7193BB9B1b005677B4edBaDcD18'; +const RPC_URL = 'https://rpc.testnet.lukso.gateway.fm'; + +const provider = new ethers.JsonRpcProvider(RPC_URL); + +// Create contract instance +const myAssetContract = new ethers.Contract( + SAMPLE_LSP8_ASSET, + lsp8Artifact.abi, + provider, +); + +// Sample Token ID of the contract +// Could be 1, my-token-id, 0x123, etc. +const myTokenId = '1'; + +// https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokenidformat +async function getTokenIdFormat() { + try { + const tokenIdFormat = parseInt( + // https://docs.lukso.tech/tools/erc725js/classes/ERC725#getdata + await myAssetContract.getData(ERC725YDataKeys.LSP8['LSP8TokenIdFormat']), + 16, + ); + return tokenIdFormat; + } catch (err) { + console.error( + 'Could not retrieve LSP8TokenIdFormat. Please provide an LSP8 asset address.', + ); + return null; + } +} + +// Get the global token ID format of the asset +let tokenIdFormat = await getTokenIdFormat(); +console.log('Global ID Format: ', tokenIdFormat); + +// Convert a token ID according to LSP8TokenIdFormat +// https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-8-IdentifiableDigitalAsset.md#lsp8tokenidformat +const convertTokenId = (tokenID: string, tokenIdFormat: number) => { + switch (tokenIdFormat) { + // TokenID is Number + case 0: + case 100: + // uint256 - Number (Left padded) + return ethers.zeroPadValue('0x0' + BigInt(tokenID).toString(16), 32); + // TokenID is String + case 1: + case 101: + // string - String (Right padded) + return ethers.encodeBytes32String(tokenID).padEnd(32, '0'); + // TokenID is Address + case 2: + case 102: + // address - Smart Contract (Left padded) + return ethers.zeroPadValue(tokenID, 32); + // TokenID is Byte Value + case 3: + case 103: + // bytes32 - Unique Bytes (Right padded) + return ethers + .hexlify(ethers.getBytes(tokenID).slice(0, 32)) + .padEnd(66, '0'); + // TokenID is Hash Digest + case 4: + case 104: + // bytes32 - Hash Digest (No padding) + return tokenID; + } +}; + +// Convert the token ID based on the token ID format +if (tokenIdFormat !== null) { + let byte32TokenId = convertTokenId(myTokenId, tokenIdFormat); + console.log('Parsed Byte32 Token ID: ', byte32TokenId); + + // If token ID format is mixed + if (tokenIdFormat >= 100) { + tokenIdFormat = parseInt( + // Retrieve token ID format for the individual token ID + await myAssetContract.getDataForTokenId( + byte32TokenId, + ERC725YDataKeys.LSP8['LSP8TokenIdFormat'], + ), + 16, + ); + // Convert the token ID based on the individual token ID format + byte32TokenId = convertTokenId(myTokenId, tokenIdFormat); + console.log('Individual Token ID Format: ', tokenIdFormat); + } else { + console.log('Asset has a global Token ID Format'); + } +} From 6a1a7cb5cd336e472bccc72d9d294ee263ec5267 Mon Sep 17 00:00:00 2001 From: Felix Hildebrandt Date: Thu, 22 Feb 2024 18:46:29 +0100 Subject: [PATCH 3/5] add docs --- digital-assets/tokenIdFormat.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/digital-assets/tokenIdFormat.ts b/digital-assets/tokenIdFormat.ts index 1a8faa0..79de742 100644 --- a/digital-assets/tokenIdFormat.ts +++ b/digital-assets/tokenIdFormat.ts @@ -82,6 +82,7 @@ if (tokenIdFormat !== null) { if (tokenIdFormat >= 100) { tokenIdFormat = parseInt( // Retrieve token ID format for the individual token ID + // https://docs.lukso.tech/contracts/contracts/LSP8IdentifiableDigitalAsset/#getdatafortokenid await myAssetContract.getDataForTokenId( byte32TokenId, ERC725YDataKeys.LSP8['LSP8TokenIdFormat'], From 6b6d53bb622c6c15ff2d7c979cc706de01da168b Mon Sep 17 00:00:00 2001 From: Maxime Date: Fri, 23 Feb 2024 10:15:52 +0000 Subject: [PATCH 4/5] Applied recommandations --- smart-contracts-hardhat/global.ts | 8 +++ .../deployUniversalProfiileWithLSP23.ts | 57 +++++++++++-------- 2 files changed, 40 insertions(+), 25 deletions(-) create mode 100644 smart-contracts-hardhat/global.ts diff --git a/smart-contracts-hardhat/global.ts b/smart-contracts-hardhat/global.ts new file mode 100644 index 0000000..6d6889c --- /dev/null +++ b/smart-contracts-hardhat/global.ts @@ -0,0 +1,8 @@ +export const LSP23_FACTORY_ADDRESS_TESTNET = '0x2300000A84D25dF63081feAa37ba6b62C4c89a30'; +export const LSP23_POST_DEPLOYMENT_MODULE_ADDRESS_TESTNET = + '0x000000000066093407b6704B89793beFfD0D8F00'; +export const UNIVERSAL_PROFILE_IMPLEMENTATION_ADDRESS_TESTNET = + '0x3024D38EA2434BA6635003Dc1BDC0daB5882ED4F'; +export const LSP6_KEY_MANAGER_IMPLEMENTATION_ADDRESS_TESTNET = + '0x2Fe3AeD98684E7351aD2D408A43cE09a738BF8a4'; +export const LSP1_UNIVERSAL_RECEIVER_ADDRESS_TESTNET = '0x7870C5B8BC9572A8001C3f96f7ff59961B23500D'; diff --git a/smart-contracts-hardhat/scripts/deployUniversalProfiileWithLSP23.ts b/smart-contracts-hardhat/scripts/deployUniversalProfiileWithLSP23.ts index 669cbeb..af8d5f4 100644 --- a/smart-contracts-hardhat/scripts/deployUniversalProfiileWithLSP23.ts +++ b/smart-contracts-hardhat/scripts/deployUniversalProfiileWithLSP23.ts @@ -3,56 +3,63 @@ import { ethers } from 'hardhat'; import { AbiCoder } from 'ethers'; import { ERC725 } from '@erc725/erc725.js'; -// LSPs artifacts +// LSPs Smart Contracts artifacts + import LSP23FactoryArtifact from '@lukso/lsp-smart-contracts/artifacts/LSP23LinkedContractsFactory.json'; import UniversalProfileInitArtifact from '@lukso/lsp-smart-contracts/artifacts/UniversalProfileInit.json'; -// ERC725.js schemas +// ERC725.js Metadata schemas + import LSP1UniversalReceiverDelegateSchemas from '@erc725/erc725.js/schemas/LSP1UniversalReceiverDelegate.json'; import LSP3ProfileMetadataSchemas from '@erc725/erc725.js/schemas/LSP3ProfileMetadata.json'; import LSP6KeyManagerSchemas from '@erc725/erc725.js/schemas/LSP6KeyManager.json'; -const LSP23_FACTORY_ADDRESS = '0x2300000A84D25dF63081feAa37ba6b62C4c89a30'; -const LSP23_POST_DEPLOYMENT_MODULE = '0x000000000066093407b6704B89793beFfD0D8F00'; -const UNIVERSAL_PROFILE_IMPLEMENTATION_ADDRESS = '0x3024D38EA2434BA6635003Dc1BDC0daB5882ED4F'; -const LSP6_KEY_MANAGER_IMPLEMENTATION_ADDRESS = '0x2Fe3AeD98684E7351aD2D408A43cE09a738BF8a4'; -const UNIVERSAL_RECEIVER_ADDRESS = '0x7870C5B8BC9572A8001C3f96f7ff59961B23500D'; // this will be needed later so we can set the Universal Receiver to the Universal Profile (see https://docs.lukso.tech/standards/generic-standards/lsp1-universal-receiver) -const MAIN_CONTROLLER = '0x3303Ce3b8644D566271DD2Eb54292d32F1458968'; +// Constants +import { + LSP23_FACTORY_ADDRESS_TESTNET, + UNIVERSAL_PROFILE_IMPLEMENTATION_ADDRESS_TESTNET, + LSP23_POST_DEPLOYMENT_MODULE_ADDRESS_TESTNET, + LSP6_KEY_MANAGER_IMPLEMENTATION_ADDRESS_TESTNET, + LSP1_UNIVERSAL_RECEIVER_ADDRESS_TESTNET, +} from '../global'; + +// Constants that needs to be overwritten +const MAIN_CONTROLLER_EOA = '0x3303Ce3b8644D566271DD2Eb54292d32F1458968'; const SALT = '0x5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed'; async function main() { // Interacting with the LSP23Factory contract const lsp23FactoryContract = await ethers.getContractAtFromArtifact( LSP23FactoryArtifact, - LSP23_FACTORY_ADDRESS, + LSP23_FACTORY_ADDRESS_TESTNET, ); // Interacting with the UniversalProfileImplementation contract const universalProfileImplementationContract = await ethers.getContractAtFromArtifact( UniversalProfileInitArtifact, - UNIVERSAL_PROFILE_IMPLEMENTATION_ADDRESS, + UNIVERSAL_PROFILE_IMPLEMENTATION_ADDRESS_TESTNET, ); - // create the init structs + // create the init structs of LSP23 Linked Contracts Factory const universalProfileInitStruct = { salt: SALT, fundingAmount: 0, - implementationContract: UNIVERSAL_PROFILE_IMPLEMENTATION_ADDRESS, + implementationContract: UNIVERSAL_PROFILE_IMPLEMENTATION_ADDRESS_TESTNET, initializationCalldata: universalProfileImplementationContract.interface.encodeFunctionData( 'initialize', - [LSP23_POST_DEPLOYMENT_MODULE], - ), // this will call the `initialize(...)` function of the Universal Profile and the the LSP23_POST_DEPLOYMENT_MODULE as owner + [LSP23_POST_DEPLOYMENT_MODULE_ADDRESS_TESTNET], + ), // this will call the `initialize(...)` function of the Universal Profile and set the LSP23_POST_DEPLOYMENT_MODULE as `owner()` }; const keyManagerInitStruct = { fundingAmount: 0, - implementationContract: LSP6_KEY_MANAGER_IMPLEMENTATION_ADDRESS, + implementationContract: LSP6_KEY_MANAGER_IMPLEMENTATION_ADDRESS_TESTNET, addPrimaryContractAddress: true, // this will append the primary contract address to the init calldata - initializationCalldata: '0xc4d66de8', // `initialize(...)` function selector + initializationCalldata: '0xc4d66de8', // `initialize(address)` function selector extraInitializationParams: '0x', }; - // instantiate the erc725 class + // instantiate the erc725.js class const erc725 = new ERC725([ ...LSP6KeyManagerSchemas, ...LSP3ProfileMetadataSchemas, @@ -65,19 +72,19 @@ async function main() { method: 'keccak256(utf8)', data: '0x6d6d08aafb0ee059e3e4b6b3528a5be37308a5d4f4d19657d26dd8a5ae799de0', }, - url: 'ipfs://QmPRoJsaYcNqQiUrQxE7ajTRaXwHyAU29tHqYNctBmK64w', + url: 'ipfs://QmPRoJsaYcNqQiUrQxE7ajTRaXwHyAU29tHqYNctBmK64w', // this is an example of Metadata stored on IPFS }; - // create the permissions data keys + // create the permissions data keys - value pairs to be set const setDataKeysAndValues = erc725.encodeData([ { keyName: 'LSP3Profile', value: lsp3DataValue }, // LSP3Metadata data key and value { keyName: 'LSP1UniversalReceiverDelegate', - value: UNIVERSAL_RECEIVER_ADDRESS, + value: LSP1_UNIVERSAL_RECEIVER_ADDRESS_TESTNET, }, // Universal Receiver data key and value { keyName: 'AddressPermissions:Permissions:
', - dynamicKeyParts: [UNIVERSAL_RECEIVER_ADDRESS], + dynamicKeyParts: [LSP1_UNIVERSAL_RECEIVER_ADDRESS_TESTNET], value: erc725.encodePermissions({ REENTRANCY: true, SUPER_SETDATA: true, @@ -85,7 +92,7 @@ async function main() { }, // Universal Receiver Delegate permissions data key and value { keyName: 'AddressPermissions:Permissions:
', - dynamicKeyParts: [MAIN_CONTROLLER], + dynamicKeyParts: [MAIN_CONTROLLER_EOA], value: erc725.encodePermissions({ CHANGEOWNER: true, ADDCONTROLLER: true, @@ -115,7 +122,7 @@ async function main() { // length of the Address Permissions array and their respective indexed keys and values { keyName: 'AddressPermissions[]', - value: [UNIVERSAL_RECEIVER_ADDRESS, MAIN_CONTROLLER], + value: [LSP1_UNIVERSAL_RECEIVER_ADDRESS_TESTNET, MAIN_CONTROLLER_EOA], }, ]); @@ -131,7 +138,7 @@ async function main() { const [upAddress, keyManagerAddress] = await lsp23FactoryContract.deployERC1167Proxies.staticCall( universalProfileInitStruct, keyManagerInitStruct, - LSP23_POST_DEPLOYMENT_MODULE, + LSP23_POST_DEPLOYMENT_MODULE_ADDRESS_TESTNET, initializeEncodedBytes, ); console.log('Universal Profile address:', upAddress); @@ -140,7 +147,7 @@ async function main() { const tx = await lsp23FactoryContract.deployERC1167Proxies( universalProfileInitStruct, keyManagerInitStruct, - LSP23_POST_DEPLOYMENT_MODULE, + LSP23_POST_DEPLOYMENT_MODULE_ADDRESS_TESTNET, initializeEncodedBytes, ); await tx.wait(); From a19d5799ec0f28b68cda07bcf9b86cde3b164b40 Mon Sep 17 00:00:00 2001 From: Felix Hildebrandt Date: Wed, 28 Feb 2024 20:15:32 +0100 Subject: [PATCH 5/5] Improve var names / sync with docs --- .../scripts/deployTokenWithMetadataAsUP.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smart-contracts-hardhat/scripts/deployTokenWithMetadataAsUP.ts b/smart-contracts-hardhat/scripts/deployTokenWithMetadataAsUP.ts index 417c2ee..5155904 100644 --- a/smart-contracts-hardhat/scripts/deployTokenWithMetadataAsUP.ts +++ b/smart-contracts-hardhat/scripts/deployTokenWithMetadataAsUP.ts @@ -62,7 +62,7 @@ async function deployTokenWithMetadata() { // Create the transaction payload for setting storage data // https://docs.lukso.tech/contracts/contracts/ERC725/#setdata - const lsp4StorageBytecode = token.interface.encodeFunctionData('setData', [ + const setLSP4MetadataPayload = token.interface.encodeFunctionData('setData', [ metadataKey, encodedLSP4Metadata.values[0], ]); @@ -82,7 +82,7 @@ async function deployTokenWithMetadata() { [0, 0], // Value is empty for both operations [ tokenBytecodeWithConstructor, // Payload for contract deployment - lsp4StorageBytecode, // Payload for setting a data key on the deployed contract + setLSP4MetadataPayload, // Payload for setting a data key on the deployed contract ], );