From fe21cc4f6737747738d97def698e6851d1c22ce5 Mon Sep 17 00:00:00 2001 From: Uladzislau Hubar Date: Thu, 12 Sep 2024 22:31:49 +0700 Subject: [PATCH] Reworked deployment scripts, added skeleton for ParanetNeuroIncentivesPool unit tests, deployed ERC20 Neuro on Base Sepolia --- deploy/001_deploy_hub_v2.ts | 2 +- deploy/002_deploy_hub.ts | 2 +- deploy/100_set_neuroweb_erc20.ts | 24 +- deployments/base_sepolia_dev_contracts.json | 4 + deployments/base_sepolia_test_contracts.json | 4 + hardhat.config.ts | 3 + .../unit/ParanetNeuroIncentivesPool.test.ts | 287 ++++++++++++++++++ utils/helpers.ts | 19 +- 8 files changed, 335 insertions(+), 10 deletions(-) create mode 100644 test/v2/unit/ParanetNeuroIncentivesPool.test.ts diff --git a/deploy/001_deploy_hub_v2.ts b/deploy/001_deploy_hub_v2.ts index a1ecf231..b6c8f68c 100644 --- a/deploy/001_deploy_hub_v2.ts +++ b/deploy/001_deploy_hub_v2.ts @@ -20,7 +20,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const Hub = await hre.deployments.deploy('Hub', { contract: 'HubV2', from: deployer, log: true }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - hre.helpers.updateDeploymentsJson('Hub', Hub.address, Hub.receipt!.blockNumber); + hre.helpers.updateDeploymentsJson('Hub', 'Hub', Hub.address, Hub.receipt!.blockNumber); } } diff --git a/deploy/002_deploy_hub.ts b/deploy/002_deploy_hub.ts index a4196ae8..586063fb 100644 --- a/deploy/002_deploy_hub.ts +++ b/deploy/002_deploy_hub.ts @@ -20,7 +20,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const Hub = await hre.deployments.deploy('Hub', { from: deployer, log: true }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - hre.helpers.updateDeploymentsJson('Hub', Hub.address, Hub.receipt!.blockNumber); + hre.helpers.updateDeploymentsJson('Hub', 'Hub', Hub.address, Hub.receipt!.blockNumber); } } diff --git a/deploy/100_set_neuroweb_erc20.ts b/deploy/100_set_neuroweb_erc20.ts index 18185cdf..da8db55c 100644 --- a/deploy/100_set_neuroweb_erc20.ts +++ b/deploy/100_set_neuroweb_erc20.ts @@ -12,12 +12,34 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const tokenInHub = await Hub['isContract(string)']('NeurowebERC20'); - if (!tokenInHub) { + if (!tokenInHub && hre.network.config.environment !== 'development') { hre.helpers.newContracts.push([ 'NeurowebERC20', hre.helpers.contractDeployments.contracts['NeurowebERC20'].evmAddress, ]); } + } else if (hre.network.config.environment === 'development') { + const Token = await hre.helpers.deploy({ + newContractName: 'Token', + newContractNameInHub: 'NeurowebERC20', + passHubInConstructor: false, + additionalArgs: ['NEURO TEST TOKEN', 'NEURO'], + }); + + const minterRole = await Token.MINTER_ROLE(); + if (!(await Token.hasRole(minterRole, deployer))) { + console.log(`Setting ERC20 Neuro minter role for ${deployer}.`); + const setupMinterRoleTx = await Token.setupRole(deployer, { from: deployer }); + await setupMinterRoleTx.wait(); + } + + const amountToMint = hre.ethers.utils.parseEther(`${10_000_000}`); + const accounts = await hre.ethers.getSigners(); + + for (const acc of accounts) { + const mintTx = await Token.mint(acc.address, amountToMint, { from: deployer, gasLimit: 80_000 }); + await mintTx.wait(); + } } }; diff --git a/deployments/base_sepolia_dev_contracts.json b/deployments/base_sepolia_dev_contracts.json index ad1fa6c9..1988a934 100644 --- a/deployments/base_sepolia_dev_contracts.json +++ b/deployments/base_sepolia_dev_contracts.json @@ -4,6 +4,10 @@ "deployed": true, "evmAddress": "0x4ead53ee0aaeB0bE5920DC2DAA7AD93F11cA5207" }, + "NeurowebERC20": { + "deployed": true, + "evmAddress": "0x008Ea316ff593E82c592fccB6072f82d1393F1Ac" + }, "Hub": { "evmAddress": "0x6C861Cb69300C34DfeF674F7C00E734e840C29C0", "version": "2.0.0", diff --git a/deployments/base_sepolia_test_contracts.json b/deployments/base_sepolia_test_contracts.json index d460a92f..79373109 100644 --- a/deployments/base_sepolia_test_contracts.json +++ b/deployments/base_sepolia_test_contracts.json @@ -4,6 +4,10 @@ "deployed": true, "evmAddress": "0x9b17032749aa066a2DeA40b746AA6aa09CdE67d9" }, + "NeurowebERC20": { + "deployed": true, + "evmAddress": "0x3d4f5831fcca588554125f1782dad85a4a235f00" + }, "Hub": { "evmAddress": "0x144eDa5cbf8926327cb2cceef168A121F0E4A299", "version": "2.0.0", diff --git a/hardhat.config.ts b/hardhat.config.ts index 38b6e967..8b5278e4 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -145,6 +145,9 @@ config.abiExporter = { 'IERC721Metadata.sol', 'IERC721Receiver.sol', 'IERC734Extended.sol', + 'IERC1155.sol', + 'IERC1155MetadataURI.sol', + 'IERC1155Receiver.sol', 'IERC4906.sol', 'Ownable.sol', 'CommitManagerErrorsV2.sol', diff --git a/test/v2/unit/ParanetNeuroIncentivesPool.test.ts b/test/v2/unit/ParanetNeuroIncentivesPool.test.ts new file mode 100644 index 00000000..ca01dbb0 --- /dev/null +++ b/test/v2/unit/ParanetNeuroIncentivesPool.test.ts @@ -0,0 +1,287 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { expect } from 'chai'; +import { BigNumberish } from 'ethers'; +import hre from 'hardhat'; +import { SignerWithAddress } from 'hardhat-deploy-ethers/signers'; + +import { + HubController, + Paranet, + ContentAssetStorageV2, + ContentAssetV2, + ParanetsRegistry, + ParanetServicesRegistry, + ParanetKnowledgeMinersRegistry, + ParanetKnowledgeAssetsRegistry, + HashingProxy, + ServiceAgreementStorageProxy, + Token, + ServiceAgreementV1, + ParanetIncentivesPoolFactory, + Hub, +} from '../../../typechain'; + +type deployParanetFixture = { + accounts: SignerWithAddress[]; + Paranet: Paranet; + HubController: HubController; + ContentAssetV2: ContentAssetV2; + ContentAssetStorageV2: ContentAssetStorageV2; + ParanetsRegistry: ParanetsRegistry; + ParanetServicesRegistry: ParanetServicesRegistry; + ParanetKnowledgeMinersRegistry: ParanetKnowledgeMinersRegistry; + ParanetKnowledgeAssetsRegistry: ParanetKnowledgeAssetsRegistry; + ParanetIncentivesPoolFactory: ParanetIncentivesPoolFactory; + HashingProxy: HashingProxy; + ServiceAgreementStorageProxy: ServiceAgreementStorageProxy; + Token: Token; + NeuroERC20: Token; + ServiceAgreementV1: ServiceAgreementV1; +}; + +type IncentivizationPoolParameters = { + paranetKAStorageContract: string; + paranetKATokenId: BigNumberish; + tracToNeuroEmissionMultiplier: BigNumberish; + paranetOperatorRewardPercentage: BigNumberish; + paranetIncentivizationProposalVotersRewardPercentage: BigNumberish; +}; + +describe('@v2 @unit ParanetNeuroIncentivesPool contract', function () { + let accounts: SignerWithAddress[]; + let Paranet: Paranet; + let HubController: HubController; + let ContentAssetV2: ContentAssetV2; + let ContentAssetStorageV2: ContentAssetStorageV2; + let ParanetsRegistry: ParanetsRegistry; + let ParanetServicesRegistry: ParanetServicesRegistry; + let ParanetKnowledgeMinersRegistry: ParanetKnowledgeMinersRegistry; + let ParanetKnowledgeAssetsRegistry: ParanetKnowledgeAssetsRegistry; + let ParanetIncentivesPoolFactory: ParanetIncentivesPoolFactory; + let HashingProxy: HashingProxy; + let ServiceAgreementStorageProxy: ServiceAgreementStorageProxy; + let Token: Token; + let NeuroERC20: Token; + let ServiceAgreementV1: ServiceAgreementV1; + + async function deployParanetFixture(): Promise { + await hre.deployments.fixture( + [ + 'HubV2', + 'HubController', + 'Paranet', + 'ContentAssetStorageV2', + 'ContentAssetV2', + 'ParanetsRegistry', + 'ParanetServicesRegistry', + 'ParanetKnowledgeMinersRegistry', + 'ParanetKnowledgeAssetsRegistry', + 'ParanetIncentivesPoolFactory', + 'HashingProxy', + 'ServiceAgreementStorageProxy', + 'Token', + 'Neuro', + 'ServiceAgreementV1', + ], + { keepExistingDeployments: false }, + ); + + const Hub = await hre.ethers.getContract('Hub'); + HubController = await hre.ethers.getContract('HubController'); + Paranet = await hre.ethers.getContract('Paranet'); + ContentAssetV2 = await hre.ethers.getContract('ContentAsset'); + ContentAssetStorageV2 = await hre.ethers.getContract('ContentAssetStorage'); + ParanetsRegistry = await hre.ethers.getContract('ParanetsRegistry'); + ParanetServicesRegistry = await hre.ethers.getContract('ParanetServicesRegistry'); + ParanetKnowledgeMinersRegistry = await hre.ethers.getContract( + 'ParanetKnowledgeMinersRegistry', + ); + ParanetKnowledgeAssetsRegistry = await hre.ethers.getContract( + 'ParanetKnowledgeAssetsRegistry', + ); + ParanetIncentivesPoolFactory = await hre.ethers.getContract( + 'ParanetIncentivesPoolFactory', + ); + ServiceAgreementStorageProxy = await hre.ethers.getContract( + 'ServiceAgreementStorageProxy', + ); + HashingProxy = await hre.ethers.getContract('HashingProxy'); + Token = await hre.ethers.getContract('Token'); + const neuroERC20Address = await Hub.getContractAddress('NeurowebERC20'); + NeuroERC20 = await hre.ethers.getContractAt('Token', neuroERC20Address); + ServiceAgreementV1 = await hre.ethers.getContract('ServiceAgreementV1'); + + accounts = await hre.ethers.getSigners(); + await HubController.setContractAddress('HubOwner', accounts[0].address); + + return { + accounts, + Paranet, + HubController, + ContentAssetV2, + ContentAssetStorageV2, + ParanetsRegistry, + ParanetServicesRegistry, + ParanetKnowledgeMinersRegistry, + ParanetKnowledgeAssetsRegistry, + ParanetIncentivesPoolFactory, + HashingProxy, + ServiceAgreementStorageProxy, + Token, + NeuroERC20, + ServiceAgreementV1, + }; + } + + beforeEach(async () => { + hre.helpers.resetDeploymentsJson(); + ({ accounts, Paranet, ParanetIncentivesPoolFactory } = await loadFixture(deployParanetFixture)); + }); + + it('The contract is named "ParanetNeuroIncentivesPool"', async () => { + const { paranetKAStorageContract, paranetKATokenId } = await registerParanet(accounts, Paranet, 1); + + const incentivesPoolParams = { + paranetKAStorageContract, + paranetKATokenId, + tracToNeuroEmissionMultiplier: hre.ethers.utils.parseEther('1'), + paranetOperatorRewardPercentage: 1_000, + paranetIncentivizationProposalVotersRewardPercentage: 1_000, + }; + const IncentivesPool = await deployERC20NeuroIncentivesPool(accounts, incentivesPoolParams, 1); + expect(await IncentivesPool.name()).to.equal('ParanetNeuroIncentivesPool'); + }); + + it('The contract is version "2.2.0"', async () => { + const { paranetKAStorageContract, paranetKATokenId } = await registerParanet(accounts, Paranet, 1); + + const incentivesPoolParams = { + paranetKAStorageContract, + paranetKATokenId, + tracToNeuroEmissionMultiplier: hre.ethers.utils.parseEther('1'), + paranetOperatorRewardPercentage: 1_000, + paranetIncentivizationProposalVotersRewardPercentage: 1_000, + }; + const IncentivesPool = await deployERC20NeuroIncentivesPool(accounts, incentivesPoolParams, 1); + expect(await IncentivesPool.version()).to.equal('2.2.0'); + }); + + it('Should accept ERC20 Neuro and update the balance', async () => { + const { paranetKAStorageContract, paranetKATokenId } = await registerParanet(accounts, Paranet, 1); + + const incentivesPoolParams = { + paranetKAStorageContract, + paranetKATokenId, + tracToNeuroEmissionMultiplier: hre.ethers.utils.parseEther('1'), + paranetOperatorRewardPercentage: 1_000, + paranetIncentivizationProposalVotersRewardPercentage: 1_000, + }; + const IncentivesPool = await deployERC20NeuroIncentivesPool(accounts, incentivesPoolParams, 1); + + const neuroAmount = hre.ethers.utils.parseEther('100000'); + await NeuroERC20.transfer(IncentivesPool.address, neuroAmount); + + expect(await IncentivesPool.getNeuroBalance()).to.be.equal(neuroAmount); + }); + + async function registerParanet(accounts: SignerWithAddress[], Paranet: Paranet, number: number) { + const assetInputArgs = { + assertionId: getHashFromNumber(number), + size: 3, + triplesNumber: 1, + chunksNumber: 1, + epochsNumber: 5, + tokenAmount: hre.ethers.utils.parseEther('105'), + scoreFunctionId: 2, + immutable_: false, + }; + + await Token.connect(accounts[100 + number]).increaseAllowance( + ServiceAgreementV1.address, + assetInputArgs.tokenAmount, + ); + const tx = await ContentAssetV2.connect(accounts[100 + number]).createAsset(assetInputArgs); + const receipt = await tx.wait(); + + const paranetKAStorageContract = ContentAssetStorageV2.address; + const paranetKATokenId = Number(receipt.logs[0].topics[3]); + const paranetName = 'Test paranet 1'; + const paranetDescription = 'Description of Test Paranet'; + + await Paranet.connect(accounts[100 + number]).registerParanet( + paranetKAStorageContract, + paranetKATokenId, + paranetName, + paranetDescription, + ); + + return { + paranetKAStorageContract, + paranetKATokenId, + paranetId: hre.ethers.utils.keccak256( + hre.ethers.utils.solidityPack(['address', 'uint256'], [paranetKAStorageContract, paranetKATokenId]), + ), + }; + } + + async function deployERC20NeuroIncentivesPool( + accounts: SignerWithAddress[], + incentivesPoolParams: IncentivizationPoolParameters, + number: number, + ) { + const tx = await ParanetIncentivesPoolFactory.connect(accounts[100 + number]).deployNeuroIncentivesPool( + false, + incentivesPoolParams.paranetKAStorageContract, + incentivesPoolParams.paranetKATokenId, + incentivesPoolParams.tracToNeuroEmissionMultiplier, + incentivesPoolParams.paranetOperatorRewardPercentage, + incentivesPoolParams.paranetIncentivizationProposalVotersRewardPercentage, + ); + const receipt = await tx.wait(); + + const IncentivesPool = await hre.ethers.getContractAt( + 'ParanetNeuroIncentivesPool', + receipt.events?.[0].args?.incentivesPool.addr, + ); + + return IncentivesPool; + } + + async function createParanetKnowledgeAsset( + paranetKAStorageContract: string, + paranetKATokenId: number, + number: number, + tokenAmount: string, + ) { + const assetInputArgs = { + assertionId: getHashFromNumber(number), + size: 3, + triplesNumber: 1, + chunksNumber: 1, + epochsNumber: 5, + tokenAmount: hre.ethers.utils.parseEther(tokenAmount), + scoreFunctionId: 2, + immutable_: false, + }; + + await Token.connect(accounts[100 + number]).increaseAllowance( + ServiceAgreementV1.address, + assetInputArgs.tokenAmount, + ); + + await Paranet.connect(accounts[100 + number]).mintKnowledgeAsset( + paranetKAStorageContract, + paranetKATokenId, + assetInputArgs, + ); + } + + function getHashFromNumber(number: number) { + return hre.ethers.utils.keccak256(hre.ethers.utils.solidityPack(['uint256'], [number])); + } + + function getknowledgeAssetId(address: string, number: number) { + return hre.ethers.utils.keccak256(hre.ethers.utils.solidityPack(['address', 'uint256'], [address, number])); + } +}); diff --git a/utils/helpers.ts b/utils/helpers.ts index 09d25909..56d87d4d 100644 --- a/utils/helpers.ts +++ b/utils/helpers.ts @@ -155,7 +155,7 @@ export class Helpers { deployer, ); - if (this.hasFunction(nameInHub, 'initialize')) { + if (this.hasFunction(newContractName, 'initialize')) { // TODO: Reinitialize only if any dependency contract was redeployed this.contractsForReinitialization.push(contractInstance.address); } @@ -211,9 +211,9 @@ export class Helpers { } } - if (this.hasFunction(nameInHub, 'initialize')) { + if (this.hasFunction(newContractName, 'initialize')) { if ((setContractInHub || setAssetStorageInHub) && this.hre.network.config.environment === 'development') { - const newContractInterface = new this.hre.ethers.utils.Interface(this.getAbi(nameInHub)); + const newContractInterface = new this.hre.ethers.utils.Interface(this.getAbi(newContractName)); const initializeTx = await HubController.forwardCall( newContract.address, newContractInterface.encodeFunctionData('initialize'), @@ -224,7 +224,7 @@ export class Helpers { } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await this.updateDeploymentsJson(nameInHub, newContract.address, newContract.receipt!.blockNumber); + await this.updateDeploymentsJson(newContractName, nameInHub, newContract.address, newContract.receipt!.blockNumber); if (this.hre.network.config.environment !== 'development') { this.saveDeploymentsJson('deployments'); @@ -338,8 +338,13 @@ export class Helpers { this.contractDeployments = { contracts: {} }; } - public async updateDeploymentsJson(newContractName: string, newContractAddress: string, deploymentBlock: number) { - const contractABI = this.getAbi(newContractName); + public async updateDeploymentsJson( + abiName: string, + newContractName: string, + newContractAddress: string, + deploymentBlock: number, + ) { + const contractABI = this.getAbi(abiName); const isVersionedContract = contractABI.some( (abiEntry) => abiEntry.type === 'function' && abiEntry.name === 'version', ); @@ -347,7 +352,7 @@ export class Helpers { let contractVersion; if (isVersionedContract) { - const VersionedContract = await this.hre.ethers.getContractAt(newContractName, newContractAddress); + const VersionedContract = await this.hre.ethers.getContractAt(abiName, newContractAddress); contractVersion = await VersionedContract.version(); } else { contractVersion = null;