From a942d651a794b0ff7d548d1e82b759a6f82b627f Mon Sep 17 00:00:00 2001 From: Alexander Evchenko Date: Mon, 18 Nov 2024 14:07:19 +0400 Subject: [PATCH 1/2] test: A transfers stRIF, B has no delegatee set, voting power is still 0 --- test/Governor.test.ts | 44 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/test/Governor.test.ts b/test/Governor.test.ts index 0b567a1..65dc676 100644 --- a/test/Governor.test.ts +++ b/test/Governor.test.ts @@ -10,7 +10,7 @@ import { OGFoundersRootstockCollective, } from '../typechain-types' import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers' -import { ContractTransactionResponse, parseEther, solidityPackedKeccak256 } from 'ethers' +import { ContractTransactionResponse, parseEther, solidityPackedKeccak256, ZeroAddress } from 'ethers' import { Proposal, ProposalState, OperationState } from '../types' import { deployContracts } from './deployContracts' import ogFoundersModule from '../ignition/modules/OGFoundersModule' @@ -153,7 +153,7 @@ describe('Governor Contact', () => { describe('Proposal Creation', () => { it('participants should gain voting power proportional to RIF tokens', async () => { await Promise.all( - holders.slice(0, holders.length - 1).map(async (voter, i) => { + holders.slice(0, holders.length).map(async (voter, i) => { const dispenseTx = await rif.transfer(voter.address, dispenseValue) await dispenseTx.wait() const rifBalance = await rif.balanceOf(voter.address) @@ -163,11 +163,15 @@ describe('Governor Contact', () => { await approvalTx.wait() const depositTx = await stRIF.connect(voter).depositFor(voter.address, votingPower) await depositTx.wait() - const delegateTx = await stRIF.connect(voter).delegate(voter.address) - await delegateTx.wait() - const votes = await stRIF.getVotes(voter.address) - expect(votes).to.equal(votingPower) + // prepare for delegation tests + if (i !== holders.length - 1) { + const delegateTx = await stRIF.connect(voter).delegate(voter.address) + await delegateTx.wait() + const votes = await stRIF.getVotes(voter.address) + + expect(votes).to.equal(votingPower) + } }), ) }) @@ -399,6 +403,34 @@ describe('Governor Contact', () => { ) }) + it('A transfers stRIF, B has no delegatee set, voting power is still 0', async () => { + const testedHolders = holders.slice(holders.length - 2) + + const votingPowersBefore = testedHolders.map(async holder => { + return await stRIF.getVotes(holder) + }) + + expect(await votingPowersBefore[0]).to.equal(dispenseValue - sendAmount) + expect(await stRIF.delegates(testedHolders[0])).to.equal(testedHolders[0].address) + expect(await votingPowersBefore[1]).to.equal(0n) + expect(await stRIF.delegates(testedHolders[1])).to.equal(ZeroAddress) + + const balanceOfABefore = await stRIF.balanceOf(testedHolders[0]) + const balanceOfBBefore = await stRIF.balanceOf(testedHolders[1]) + + const transferFromAtoB = await stRIF + .connect(testedHolders[0]) + .transfer(testedHolders[1], sendAmount) + + await transferFromAtoB.wait() + + expect(await stRIF.balanceOf(testedHolders[0])).to.equal(balanceOfABefore - sendAmount) + expect(await stRIF.balanceOf(testedHolders[1])).to.equal(balanceOfBBefore + sendAmount) + + expect(await stRIF.getVotes(testedHolders[0])).to.equal(balanceOfABefore - sendAmount) + expect(await stRIF.getVotes(testedHolders[1])).to.equal(0n) + }) + it('should be possible to claim back votes', async () => { const balance1 = await stRIF.balanceOf(holders[1]) From 4461a036840fe2c400b748b5bb813f6d102e8f06 Mon Sep 17 00:00:00 2001 From: Alexander Evchenko Date: Tue, 3 Dec 2024 15:34:37 +0400 Subject: [PATCH 2/2] test: vanguardNFT gas price(unfinished) --- contracts/test/VanguardNFT.sol | 118 ++++++++++++++++++++++++++ hardhat.config.ts | 3 + ignition/modules/VanguardNFTModule.ts | 27 ++++++ params/VanguardNFT/testnet.json | 7 ++ test/gasCheckForNFT.test.ts | 96 +++++++++++++++++++++ 5 files changed, 251 insertions(+) create mode 100644 contracts/test/VanguardNFT.sol create mode 100644 ignition/modules/VanguardNFTModule.ts create mode 100644 params/VanguardNFT/testnet.json create mode 100644 test/gasCheckForNFT.test.ts diff --git a/contracts/test/VanguardNFT.sol b/contracts/test/VanguardNFT.sol new file mode 100644 index 0000000..401ec8d --- /dev/null +++ b/contracts/test/VanguardNFT.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +// Compatible with OpenZeppelin Contracts ^5.0.0 +pragma solidity ^0.8.20; + +import {ERC721NonTransferrableUpgradable} from "../NFT/ERC721NonTransferrableUpgradable.sol"; +import {GovernorRootstockCollective} from "../GovernorRootstockCollective.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import "hardhat/console.sol"; + +contract VanguardNFTRootstockCollective is ERC721NonTransferrableUpgradable { + using Strings for uint256; + + event IpfsFolderChanged(uint256 newNumFiles, string newIpfs); + + error VanguardCannotMint(); + error MintError(string reason); + + GovernorRootstockCollective public governor; + // Counter for the total number of minted tokens + uint256 private _totalMinted; + // number of metadata files in the IPFS directory + uint256 private _maxSupply; + // IPFS CID of the tokens metadata directory + string private _folderIpfsCid; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize( + address initialOwner, + GovernorRootstockCollective governorAddress, + uint256 maxSupply, + string calldata ipfsFolderCid + ) public initializer { + require(address(governorAddress) != address(0), "VanguardNFTRootstockCollective: No governor address"); + __ERC721UpgradableBase_init("VanguardNFTRootstockCollective", "VanNFT", initialOwner); + governor = governorAddress; + setIpfsFolder(maxSupply, ipfsFolderCid); + } + + /** + * @dev Sets a new IPFS folder and updates the maximum supply of tokens that can be minted. + * This function is meant to be called by an admin when the metadata folder on IPFS is updated. + * It ensures that the new maximum supply is greater than the previous one. + * @param newMaxSupply The new maximum number of tokens that can be minted. + * @param newIpfsCid The new IPFS CID for the metadata folder. + */ + function setIpfsFolder(uint256 newMaxSupply, string calldata newIpfsCid) public virtual onlyOwner { + require(newMaxSupply >= _maxSupply, "VanguardNFTRootstockCollective: Invalid max supply"); + _maxSupply = newMaxSupply; + _folderIpfsCid = newIpfsCid; + emit IpfsFolderChanged(newMaxSupply, newIpfsCid); + } + + function mint() external virtual { + address caller = _msgSender(); + + try governor.proposalCount() returns (uint count) { + uint8 counter = 10; + bool hasEverVoted = false; + + while (counter != 0) { + (uint256 proposalId, , , , ) = governor.proposalDetailsAt(count - counter); + + bool hasVoted = governor.hasVoted(proposalId, caller); + + if (hasVoted) { + hasEverVoted = hasVoted; + break; + } + + counter--; + } + + if (!hasEverVoted) { + revert VanguardCannotMint(); + } + + //here we mint + uint256 tokenId = ++_totalMinted; + string memory fileName = string.concat(tokenId.toString(), ".json"); // 1.json, 2.json ... + _safeMint(caller, tokenId); + _setTokenURI(tokenId, fileName); + } catch Error(string memory reason) { + revert MintError(reason); + } + } + + /** + * @dev Returns the number of tokens available for minting + */ + function tokensAvailable() public view virtual returns (uint256) { + if (_totalMinted >= _maxSupply) return 0; + return _maxSupply - _totalMinted; + } + + /** + * @dev Returns the token ID for a given owner address. + * This is a simplified version of the `tokenOfOwnerByIndex` function without the index + * parameter, since a community member can only own one token. + */ + function tokenIdByOwner(address owner) public view virtual returns (uint256) { + return tokenOfOwnerByIndex(owner, 0); + } + + /** + * @dev Returns the token IPFS URI for the given owner address. + * This utility function combines two view functions. + */ + function tokenUriByOwner(address owner) public view virtual returns (string memory) { + return tokenURI(tokenIdByOwner(owner)); + } + + function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {} +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 6406e22..4f487ce 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -80,6 +80,9 @@ const config: HardhatUserConfig = { }, ], }, + mocha: { + timeout: 100000000, + }, } export default config diff --git a/ignition/modules/VanguardNFTModule.ts b/ignition/modules/VanguardNFTModule.ts new file mode 100644 index 0000000..83230e4 --- /dev/null +++ b/ignition/modules/VanguardNFTModule.ts @@ -0,0 +1,27 @@ +import { buildModule } from '@nomicfoundation/hardhat-ignition/modules' + +export const VanguardNFTModule = buildModule('VanguardNFT', m => { + // deploy implementation + const implementation = m.contract('VanguardNFTRootstockCollective', [], { id: 'Implementation' }) + + // initializer parameters + const deployer = m.getAccount(0) + const governor = m.getParameter('governor') + const maxSupply = m.getParameter('maxSupply') + const ipfsFolderCid = m.getParameter('ipfsFolderCid') + + // deploy proxy + const proxy = m.contract('ERC1967Proxy', [ + implementation, + m.encodeFunctionCall(implementation, 'initialize', [deployer, governor, maxSupply, ipfsFolderCid], { + id: 'Proxy', + }), + ]) + const VanguardNFT = m.contractAt('VanguardNFTRootstockCollective', proxy, { + id: 'Contract', + }) + + return { VanguardNFT } +}) + +export default VanguardNFTModule diff --git a/params/VanguardNFT/testnet.json b/params/VanguardNFT/testnet.json new file mode 100644 index 0000000..1157151 --- /dev/null +++ b/params/VanguardNFT/testnet.json @@ -0,0 +1,7 @@ +{ + "VanguardNFT": { + "governor": "0x2109FF4a9D5548a21F877cA937Ac5847Fde49694", + "maxSupply": 20, + "ipfsFolderCid": "QmPaCP36tFjXp7xqcPi4ggatL7w4dsWKGTv1kpaSVkv9KW" + } +} diff --git a/test/gasCheckForNFT.test.ts b/test/gasCheckForNFT.test.ts new file mode 100644 index 0000000..8c1ab23 --- /dev/null +++ b/test/gasCheckForNFT.test.ts @@ -0,0 +1,96 @@ +import { ethers, ignition } from 'hardhat' +import { GovernorRootstockCollective, VanguardNFTRootstockCollective } from '../typechain-types' +import VanguardNFTModule from '../ignition/modules/VanguardNFTModule' +import { expect } from 'chai' +import { HDNodeWallet, Wallet } from 'ethers' + +const deployVanguard = async () => { + const governor = await ethers.getContractAt( + 'GovernorRootstockCollective', + '0x91a8E4A070B4BA4bf2e2a51Cb42BdeDf8FFB9b5a', + ) + + const vanguardNFT = await ethers.getContractAt( + 'VanguardNFTRootstockCollective', + '0x6c8A59784aD6a2BBcFe4940A587D88DeF24b6A32', + ) + + // const ipfsFolderCid = 'QmPaCP36tFjXp7xqcPi4ggatL7w4dsWKGTv1kpaSVkv9KW' + + const address = await governor.getAddress() + console.log('address', address) + + // const contract = await ignition.deploy(VanguardNFTModule, { + // parameters: { + // VanguardNFT: { + // governor: address, + // maxSupply: 20, + // ipfsFolderCid, + // }, + // }, + // }) + // const vanguardNFT = contract.VanguardNFT as unknown as VanguardNFTRootstockCollective + + console.log('vanguardNFT address', await vanguardNFT.getAddress()) + + return { governor, vanguardNFT } +} + +describe('NFT GAS CHECK', () => { + let vanguardNFT: VanguardNFTRootstockCollective + let governor: GovernorRootstockCollective + + before(async () => { + try { + ;({ governor, vanguardNFT } = await deployVanguard()) + } catch (err) { + console.log('ERROR IN ASYNC', err) + } + }) + + it('should successfully deploy', async () => { + expect(await vanguardNFT.getAddress()).to.be.properAddress + expect(await governor.getAddress()).to.be.properAddress + }) + + it('should not mint', async () => { + const provider = ethers.getDefaultProvider() + const signer = new Wallet('b15963931c7e8bdb293596040e86dd18e4c885868c5ffffc1d91ff2d0fd84f07', provider) + console.log('signer address', await signer.getAddress()) + const tx = await vanguardNFT.connect(signer).mint() + + await expect(tx).to.be.revertedWithCustomError({ interface: vanguardNFT.interface }, 'VanguardCannotMint') + }) + + // it('should fetch proposal count', async () => { + // proposalCount = await governor.proposalCount() + // console.log('proposalCount', proposalCount) + // expect(proposalCount).to.be.greaterThan(0n) + // }) + + // it('should be able to fetch proposalDetails by index', async () => { + // const proposalDetails = await governor.proposalDetailsAt(proposalCount - 1n) + // expect(typeof proposalDetails[0]).to.equal(typeof 0n) + // }) + + // it('check gas', async () => { + // const proposalsHasVoted = [] + + // await Promise.all( + // [10n, 9n, 8n, 7n, 6n, 5n, 4n, 3n, 2n, 1n].map(async i => { + // const proposalDetails = await governor.proposalDetailsAt(proposalCount - i) + // const hasVoted = await governor.hasVoted( + // proposalDetails[0], + // '0x0C48e53E5852780Cb0a5Bf168eb92DE3B88Ebe89', + // ) + // proposalsHasVoted.push({ id: proposalDetails[0], hasVoted }) + // }), + // ) + + // console.log('proposalsHasVoted', proposalsHasVoted, proposalsHasVoted.length) + + // proposalsHasVoted.forEach(obj => { + + // }) + // }) +})