From 2d2404cba7eb7a22caa02d3f76f984ab5d5672ec Mon Sep 17 00:00:00 2001 From: Andres Adjimann Date: Tue, 10 Dec 2024 10:52:26 -0300 Subject: [PATCH] feat: deployment scripts for nft-collection --- .../change_owner_factory.ts | 13 ++- ...01_deploy_nft_collection_implementation.ts | 24 +++++ .../02_deploy_nft_collection_beacon.ts | 88 +++++++++++++++++++ .../100_deploy_nft_collection_mock.ts | 83 +++++++++++++++++ packages/deploy/hardhat.config.ts | 9 +- packages/deploy/utils/hardhatDeployUtils.ts | 39 ++++++++ 6 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 packages/deploy/deploy/28_nft_collection/01_deploy_nft_collection_implementation.ts create mode 100644 packages/deploy/deploy/28_nft_collection/02_deploy_nft_collection_beacon.ts create mode 100644 packages/deploy/deploy/28_nft_collection/100_deploy_nft_collection_mock.ts create mode 100644 packages/deploy/utils/hardhatDeployUtils.ts diff --git a/packages/deploy/deploy/25_avatar_collection_factory/change_owner_factory.ts b/packages/deploy/deploy/25_avatar_collection_factory/change_owner_factory.ts index 11b40dfbb8..975792d040 100644 --- a/packages/deploy/deploy/25_avatar_collection_factory/change_owner_factory.ts +++ b/packages/deploy/deploy/25_avatar_collection_factory/change_owner_factory.ts @@ -10,17 +10,22 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const owner = await read('CollectionFactory', 'owner'); if (nftCollectionAdmin?.toLocaleLowerCase() !== owner?.toLocaleLowerCase()) { - await catchUnknownSigner( + await catchUnknownSigner(async () => { // This only starts the fist step in a 2step ownership transfer // the second step involves the "nftCollectionAdmin" multisig to accept the new ownership // by calling the "acceptOwnership" function on the CollectionFactory - execute( + await execute( 'CollectionFactory', {from: owner, log: true}, 'transferOwnership', nftCollectionAdmin - ) - ); + ); + await execute( + 'CollectionFactory', + {from: nftCollectionAdmin, log: true}, + 'acceptOwnership' + ); + }); } }; diff --git a/packages/deploy/deploy/28_nft_collection/01_deploy_nft_collection_implementation.ts b/packages/deploy/deploy/28_nft_collection/01_deploy_nft_collection_implementation.ts new file mode 100644 index 0000000000..520f11ef22 --- /dev/null +++ b/packages/deploy/deploy/28_nft_collection/01_deploy_nft_collection_implementation.ts @@ -0,0 +1,24 @@ +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {DEPLOY_TAGS} from '../../hardhat.config'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const {deployments, getNamedAccounts} = hre; + const {deployer} = await getNamedAccounts(); + + await deployments.deploy('NFTCollection_Implementation', { + from: deployer, + contract: 'NFTCollection', + log: true, + skipIfAlreadyDeployed: true, + }); +}; + +export default func; +func.tags = [ + 'PolygonNFTCollection', + 'PolygonNFTCollectionImplementation_deploy', + DEPLOY_TAGS.L2, + DEPLOY_TAGS.L2_PROD, + DEPLOY_TAGS.L2_TEST, +]; diff --git a/packages/deploy/deploy/28_nft_collection/02_deploy_nft_collection_beacon.ts b/packages/deploy/deploy/28_nft_collection/02_deploy_nft_collection_beacon.ts new file mode 100644 index 0000000000..60d1009982 --- /dev/null +++ b/packages/deploy/deploy/28_nft_collection/02_deploy_nft_collection_beacon.ts @@ -0,0 +1,88 @@ +import {ZeroAddress} from 'ethers'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {DEPLOY_TAGS} from '../../hardhat.config'; +import { + getEventArgsFromReceipt, + saveDeployment, +} from '../../utils/hardhatDeployUtils'; +import {getNamedAccounts} from 'hardhat'; + +// hardhat-deploy don't support factory and beacons the way we use it +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const {deployments, ethers} = hre; + const beaconAlias = ethers.encodeBytes32String('nft-collection-v2'); + const implementation = await deployments.get('NFTCollection_Implementation'); + const beaconAddress = await deployments.read( + 'CollectionFactory', + 'aliasToBeacon', + beaconAlias + ); + if (beaconAddress == ZeroAddress) { + await deployments.catchUnknownSigner( + deployBeacon(hre, beaconAlias, implementation) + ); + } else { + await performSanityChecks(hre, beaconAddress, implementation); + } +}; + +async function deployBeacon(hre, beaconAlias, implementation) { + const {deployments, ethers} = hre; + const {nftCollectionAdmin} = await getNamedAccounts(); + const receipt = await deployments.execute( + 'CollectionFactory', + {from: nftCollectionAdmin, log: true}, + 'deployBeacon', + implementation.address, + beaconAlias + ); + const eventArgs: {beaconAlias: string; beaconAddress: string} = + getEventArgsFromReceipt( + await ethers.getContract('CollectionFactory'), + receipt, + 'BeaconAdded' + ); + await saveDeployment( + deployments, + eventArgs.beaconAddress, + 'NFTCollection_Beacon', + 'UpgradeableBeacon', + receipt + ); +} + +async function performSanityChecks(hre, beaconAddress: string, implementation) { + const {deployments, ethers} = hre; + const beaconArtifact = await deployments.getArtifact('UpgradeableBeacon'); + const beacon = await ethers.getContractAt(beaconArtifact.abi, beaconAddress); + + const i = await beacon.implementation(); + if (i != implementation.address) { + throw new Error( + 'something went wrong: Beacon already deployed but has wrong implementation address, must call updateBeaconImplementation' + ); + } + const factory = await deployments.get('CollectionFactory'); + const o = await beacon.owner(); + if (o != factory.address) { + throw new Error( + 'something went wrong: Beacon already deployed but has wrong owner' + ); + } +} + +export default func; +func.tags = [ + 'PolygonNFTCollection', + 'PolygonNFTCollection_Beacon', + 'PolygonNFTCollectionBeacon_deploy', + DEPLOY_TAGS.L2, + DEPLOY_TAGS.L2_PROD, + DEPLOY_TAGS.L2_TEST, +]; +func.dependencies = [ + 'CollectionFactory_deploy', + 'CollectionFactory_change_admin', + 'PolygonNFTCollectionImplementation_deploy', +]; diff --git a/packages/deploy/deploy/28_nft_collection/100_deploy_nft_collection_mock.ts b/packages/deploy/deploy/28_nft_collection/100_deploy_nft_collection_mock.ts new file mode 100644 index 0000000000..26b5208a02 --- /dev/null +++ b/packages/deploy/deploy/28_nft_collection/100_deploy_nft_collection_mock.ts @@ -0,0 +1,83 @@ +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {DEPLOY_TAGS} from '../../hardhat.config'; +import { + getEventArgsFromReceipt, + saveDeployment, +} from '../../utils/hardhatDeployUtils'; + +// Collections are created via backoffice, this script creates a collection +// for testing (TO BE USED ONLY ON TESTNETS) +// hardhat-deploy don't support factory and beacons the way we use it +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const {deployments, getNamedAccounts, ethers} = hre; + + const skipIfAlreadyExists = !!(await deployments.getOrNull( + 'NFTCollection_CollectionProxy' + )); + if (skipIfAlreadyExists) { + console.log('skip NFTCollection_CollectionProxy already exist'); + return; + } + const {treasury, raffleSignWallet, nftCollectionAdmin} = + await getNamedAccounts(); + + // TODO: set the right arguments + const metadataUrl = + 'https://contracts.sandbox.game/avatarcollection-unrevealed/'; + const collectionName = 'NFTCollectionTest'; + const collectionSymbol = 'TEST'; + const MAX_SUPPLY = 500; + + const TRUSTED_FORWARDER = await deployments.get('TRUSTED_FORWARDER_V2'); + const sandContract = await deployments.get('PolygonSand'); + const implementation = await ethers.getContract( + 'NFTCollection_Implementation' + ); + + await deployments.catchUnknownSigner(async () => { + const receipt = await deployments.execute( + 'CollectionFactory', + {from: nftCollectionAdmin, log: true}, + 'deployCollection', + ethers.encodeBytes32String('nft-collection-v2'), + implementation.interface.encodeFunctionData('initialize', [ + nftCollectionAdmin, + metadataUrl, + collectionName, + collectionSymbol, + treasury, + raffleSignWallet, + TRUSTED_FORWARDER.address, + sandContract.address, + MAX_SUPPLY, + ]) + ); + const eventArgs: {collectionProxy: string; beaconAddress: string} = + getEventArgsFromReceipt( + await ethers.getContract('CollectionFactory'), + receipt, + 'CollectionAdded' + ); + await saveDeployment( + deployments, + eventArgs.collectionProxy, + 'NFTCollectionMat_Proxy', + 'CollectionProxy', + receipt, + await implementation.getAddress() + ); + }); +}; + +export default func; +func.tags = [ + 'PolygonNFTCollectionTest_deploy', + DEPLOY_TAGS.L2, + DEPLOY_TAGS.L2_TEST, +]; +func.dependencies = [ + 'PolygonNFTCollectionBeacon_deploy', + 'PolygonSand_deploy', + 'TRUSTED_FORWARDER_V2', +]; diff --git a/packages/deploy/hardhat.config.ts b/packages/deploy/hardhat.config.ts index 0c0ed99e9b..b061ff8d4a 100644 --- a/packages/deploy/hardhat.config.ts +++ b/packages/deploy/hardhat.config.ts @@ -15,7 +15,13 @@ import { // Package name : solidity source code path const importedPackages = { - '@sandbox-smart-contracts/avatar': 'contracts/', + '@sandbox-smart-contracts/avatar': [ + 'contracts/nft-collection/NFTCollection.sol', + 'contracts/avatar/AvatarCollection.sol', + 'contracts/proxy', + 'contracts/raffle', + 'contracts/raffleold/contracts', + ], '@sandbox-smart-contracts/asset@1.1.0': [ 'contracts/Asset.sol', 'contracts/AssetCreate.sol', @@ -312,6 +318,7 @@ const namedAccounts = { default: 'sandAdmin', mainnet: null, polygon: '0xF06dD9b61d480704Cc7bEF717e5Ea6efB6Af75bE', // Final admin should be 0xE79AF6BEb7D31c7faF7a1b891d9684960522D22e + amoy: '0x4BF86138e9DC66Fb65F8b9387C53aB4439FC41FF', }, lazyMintingCatSeller: { default: 4, diff --git a/packages/deploy/utils/hardhatDeployUtils.ts b/packages/deploy/utils/hardhatDeployUtils.ts new file mode 100644 index 0000000000..de354e49a4 --- /dev/null +++ b/packages/deploy/utils/hardhatDeployUtils.ts @@ -0,0 +1,39 @@ +// Hardhat-deploy don't support factory and beacons the way we use it +// We are forced to save the deployment by hand +import { + DeploymentsExtension, + DeploymentSubmission, + Receipt, +} from 'hardhat-deploy/types'; +import {Contract} from 'ethers'; + +export async function saveDeployment( + deployments: DeploymentsExtension, + address: string, + artifactName: string, + contractName: string, + receipt: Receipt, + implementationAddress?: string +) { + const extendedArtifact = await deployments.getExtendedArtifact(contractName); + console.log( + `saving "${artifactName}" (tx: ${receipt.transactionHash})...: deployed at ${address} with ${receipt.gasUsed} gas` + ); + await deployments.save(artifactName, { + address, + ...extendedArtifact, + receipt, + transactionHash: receipt.transactionHash, + ...(implementationAddress ? {implementation: implementationAddress} : {}), + } as DeploymentSubmission); +} + +export function getEventArgsFromReceipt( + contract: Contract, + receipt: Receipt, + eventName: string +) { + const fragment = contract.filters[eventName].fragment; + const ev = receipt.events.find((x) => x.topics[0] == fragment.topicHash); + return ev.args; +}