Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPIKE - understand estimation of cost of fetching past voting status when minting an NFT #99

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions contracts/test/VanguardNFT.sol
Original file line number Diff line number Diff line change
@@ -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 {}
}
3 changes: 3 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ const config: HardhatUserConfig = {
},
],
},
mocha: {
timeout: 100000000,
},
}

export default config
27 changes: 27 additions & 0 deletions ignition/modules/VanguardNFTModule.ts
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions params/VanguardNFT/testnet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"VanguardNFT": {
"governor": "0x2109FF4a9D5548a21F877cA937Ac5847Fde49694",
"maxSupply": 20,
"ipfsFolderCid": "QmPaCP36tFjXp7xqcPi4ggatL7w4dsWKGTv1kpaSVkv9KW"
}
}
44 changes: 38 additions & 6 deletions test/Governor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
}),
)
})
Expand Down Expand Up @@ -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])

Expand Down
96 changes: 96 additions & 0 deletions test/gasCheckForNFT.test.ts
Original file line number Diff line number Diff line change
@@ -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 => {

// })
// })
})
Loading