-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #82 from RootstockCollective/rb-dao-759-nftec
- Loading branch information
Showing
5 changed files
with
228 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// SPDX-License-Identifier: MIT | ||
// Compatible with OpenZeppelin Contracts ^5.0.0 | ||
pragma solidity ^0.8.20; | ||
|
||
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; | ||
import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; | ||
import {ERC721UpgradableAirdroppable} from "./ERC721UpgradableAirdroppable.sol"; | ||
import {ERC721UpgradableNonTransferrable} from "./ERC721UpgradableNonTransferrable.sol"; | ||
|
||
contract ExternalContributorsEcosystemPartner is | ||
ERC721UpgradableAirdroppable, | ||
ERC721UpgradableNonTransferrable | ||
{ | ||
/// @custom:oz-upgrades-unsafe-allow constructor | ||
constructor() { | ||
_disableInitializers(); | ||
} | ||
|
||
function initialize(address initialOwner) public initializer { | ||
__ERC721UpgradableBase_init("OGExternalContributorsEcosystemPartner", "OGECEP", initialOwner); | ||
} | ||
|
||
function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {} | ||
|
||
function _baseURI() internal pure override returns (string memory) { | ||
return "ipfs://"; | ||
} | ||
|
||
/* Overrides required by Solidity */ | ||
function approve( | ||
address to, | ||
uint256 tokenId | ||
) public virtual override(IERC721, ERC721Upgradeable, ERC721UpgradableNonTransferrable) { | ||
super.approve(to, tokenId); | ||
} | ||
|
||
function setApprovalForAll( | ||
address operator, | ||
bool approved | ||
) public virtual override(IERC721, ERC721Upgradeable, ERC721UpgradableNonTransferrable) { | ||
super.setApprovalForAll(operator, approved); | ||
} | ||
|
||
function transferFrom( | ||
address from, | ||
address to, | ||
uint256 tokenId | ||
) public virtual override(IERC721, ERC721Upgradeable, ERC721UpgradableNonTransferrable) { | ||
super.transferFrom(from, to, tokenId); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules' | ||
|
||
export const extContributersEpProxyModule = buildModule('ExtContributorsEP', m => { | ||
// deploy implementation | ||
const implementation = m.contract('ExternalContributorsEcosystemPartner', [], { id: 'Implementation' }) | ||
|
||
const deployer = m.getAccount(0) | ||
// deploy proxy | ||
const proxy = m.contract('ERC1967Proxy', [ | ||
implementation, | ||
m.encodeFunctionCall(implementation, 'initialize', [deployer], { | ||
id: 'Proxy', | ||
}), | ||
]) | ||
const ExtContributorsEP = m.contractAt('ExternalContributorsEcosystemPartner', proxy, { | ||
id: 'Contract', | ||
}) | ||
|
||
return { ExtContributorsEP } | ||
}) | ||
|
||
export default extContributersEpProxyModule |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
[ | ||
{ | ||
"receiver": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", | ||
"ipfsCid": "QmeGNJxhSToWYvHucUMX4D2fX8Uk2aL8CujwFuGw2Moy9U" | ||
}, | ||
{ | ||
"receiver": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", | ||
"ipfsCid": "QmPFcgjgqCMoEbBmkbnvwaknENTH3WckjCGYekQhXJFVzn" | ||
}, | ||
{ | ||
"receiver": "0x90F79bf6EB2c4f870365E785982E1f101E93b906", | ||
"ipfsCid": "Qme6NCykZUUv78jXydTgmmehHThVXpJHFxAbZvEJkkPk5o" | ||
}, | ||
{ | ||
"receiver": "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", | ||
"ipfsCid": "QmUrqydS9kLYDJtv6dY7suhT5m5UPxJe1K1yq9b7QoY1VD" | ||
}, | ||
{ | ||
"receiver": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", | ||
"ipfsCid": "QmcbYfu8CuhHRfRmDHwc8P8vqpgnYSCuzLcwtr5n4afMKb" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { expect } from 'chai' | ||
import hre, { ethers, ignition } from 'hardhat' | ||
import { ExternalContributorsEcosystemPartner } from '../typechain-types' | ||
import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers' | ||
import { extContributersEpProxyModule } from '../ignition/modules/ExternalContributorsEcosystemPartner' | ||
import airdropReceivers from '../params/ExtContributorsEP/airdrop-testnet.json' | ||
|
||
describe('ExternalContributorsEcosystemPartner NFT', () => { | ||
let deployer: SignerWithAddress | ||
let alice: SignerWithAddress | ||
const orgGangsters: SignerWithAddress[] = [] | ||
let extContEP: ExternalContributorsEcosystemPartner | ||
|
||
before(async () => { | ||
;[deployer, alice] = await ethers.getSigners() | ||
const contract = await ignition.deploy(extContributersEpProxyModule) | ||
extContEP = contract.ExtContributorsEP as unknown as ExternalContributorsEcosystemPartner | ||
// impersonating airdrop receivers | ||
for (let i = 0; i < airdropReceivers.length; i++) { | ||
const accountAddr = airdropReceivers[i].receiver | ||
await hre.network.provider.request({ | ||
method: 'hardhat_impersonateAccount', | ||
params: [accountAddr], | ||
}) | ||
const account = await ethers.getSigner(accountAddr) | ||
orgGangsters.push(account) | ||
} | ||
}) | ||
|
||
describe('Upon deployment', () => { | ||
it('should set up proper NFT name and symbol', async () => { | ||
expect(await extContEP.connect(deployer).name()).to.equal("OGExternalContributorsEcosystemPartner") | ||
expect(await extContEP.symbol()).to.equal("OGECEP") | ||
}) | ||
|
||
it('should have zero total supply', async () => { | ||
expect(await extContEP.totalSupply()).to.equal(0) | ||
}) | ||
|
||
it('should have an owner', async () => { | ||
expect(await extContEP.owner()).to.equal(deployer.address) | ||
}) | ||
}) | ||
|
||
describe('Airdrop', () => { | ||
it('should execute the initial airdrop after deployment', async () => { | ||
await expect(extContEP.connect(deployer).airdrop(airdropReceivers)) | ||
.to.emit(extContEP, 'AirdropExecuted') | ||
.withArgs(airdropReceivers.length) | ||
}) | ||
it('the Gangsters should own NFTs after the airdrop', async () => { | ||
await Promise.all( | ||
orgGangsters.map(async (gangster, i) => { | ||
expect(await extContEP.balanceOf(gangster.address)).to.equal(1) | ||
// token IDs: 1, 2, 3... | ||
expect(await extContEP.tokenOfOwnerByIndex(gangster.address, 0)).to.equal(i + 1) | ||
}), | ||
) | ||
}) | ||
it('should top up total supply after the airdrop', async () => { | ||
expect(await extContEP.totalSupply()).to.equal(airdropReceivers.length) | ||
}) | ||
it('non-owner cannot execute airdrop', async () => { | ||
await expect(extContEP.connect(alice).airdrop(airdropReceivers)) | ||
.to.be.revertedWithCustomError(extContEP, 'OwnableUnauthorizedAccount') | ||
.withArgs(alice.address) | ||
}) | ||
it('should execute the second airdrop to the same addresses', async () => { | ||
await expect(extContEP.connect(deployer).airdrop(airdropReceivers)) | ||
.to.emit(extContEP, 'AirdropExecuted') | ||
.withArgs(airdropReceivers.length) | ||
}) | ||
it('the Gangsters should own 2 NFTs after the second airdrop', async () => { | ||
await Promise.all( | ||
orgGangsters.map(async (gangster, i) => { | ||
const tokenId = airdropReceivers.length + i + 1 | ||
expect(await extContEP.balanceOf(gangster.address)).to.equal(2) | ||
// token IDs: 6, 7, 8... | ||
expect(await extContEP.tokenOfOwnerByIndex(gangster.address, 1)).to.equal(tokenId) | ||
const cid = airdropReceivers[i].ipfsCid | ||
expect(await extContEP.tokenURI(tokenId)).to.equal(`ipfs://${cid}`) | ||
}), | ||
) | ||
}) | ||
}) | ||
|
||
describe('Transfer functionality is disabled', () => { | ||
it('transfers should be forbidden after airdrop', async () => { | ||
await Promise.all( | ||
orgGangsters.map(async (sender, i) => { | ||
await expect( | ||
extContEP.connect(sender).transferFrom(sender.address, alice.address, i + 1), | ||
).to.be.revertedWithCustomError(extContEP, 'TransfersDisabled') | ||
}), | ||
) | ||
}) | ||
|
||
it('approvals should be forbidden', async () => { | ||
await Promise.all( | ||
orgGangsters.map(async (sender, i) => { | ||
await expect( | ||
Check warning on line 101 in test/ExtContributorsEP.test.ts
|
||
extContEP.connect(sender).approve(alice.address, i + 1), | ||
).to.be.revertedWithCustomError(extContEP, 'TransfersDisabled') | ||
}), | ||
) | ||
}) | ||
|
||
it('setApprovalForAll should be forbidden', async () => { | ||
await Promise.all( | ||
orgGangsters.map(async sender => { | ||
await expect( | ||
extContEP.connect(sender).setApprovalForAll(alice.address, true), | ||
).to.be.revertedWithCustomError(extContEP, 'TransfersDisabled') | ||
}), | ||
) | ||
}) | ||
}) | ||
}) |