test: hardhat (#21)
* chore: update hardhat config * test: add FleekERC721 tests and remove not used SitesNFTs suit * test: verify ERC721 compatibility * Content type on second abi.encodePacked call in tokenURI * Fix abi encoding on tokenURI * chore: update hardhat config * test: add FleekERC721 tests and remove not used SitesNFTs suit * test: verify ERC721 compatibility * Content type on second abi.encodePacked call in tokenURI * test: improve assertion using deep equality * chore: remove 0.4.24 version from hardhat compilers * refactor: clear empty bytes from bytes32 * refactor: change properties from bytes32 to string Co-authored-by: janison <jsonsivar@gmail.com>
This commit is contained in:
parent
700293b94b
commit
ef8baad617
|
|
@ -16,8 +16,8 @@ contract FleekERC721 is ERC721, FleekAccessControl {
|
||||||
event NewTokenName(uint256 indexed token, string indexed name);
|
event NewTokenName(uint256 indexed token, string indexed name);
|
||||||
event NewTokenDescription(uint256 indexed token, string indexed description);
|
event NewTokenDescription(uint256 indexed token, string indexed description);
|
||||||
event NewTokenImage(uint256 indexed token, string indexed image);
|
event NewTokenImage(uint256 indexed token, string indexed image);
|
||||||
event NewTokenExternalURL(uint256 indexed token, bytes32 indexed external_url);
|
event NewTokenExternalURL(uint256 indexed token, string indexed external_url);
|
||||||
event NewTokenENS(uint256 indexed token, bytes32 indexed ENS);
|
event NewTokenENS(uint256 indexed token, string indexed ENS);
|
||||||
|
|
||||||
struct Build {
|
struct Build {
|
||||||
string commit_hash;
|
string commit_hash;
|
||||||
|
|
@ -25,12 +25,17 @@ contract FleekERC721 is ERC721, FleekAccessControl {
|
||||||
string author;
|
string author;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The properties are stored as string to keep consistency with
|
||||||
|
* other token contracts, we might consider changing for bytes32
|
||||||
|
* in the future due to gas optimization
|
||||||
|
*/
|
||||||
struct App {
|
struct App {
|
||||||
string name; // Name of the site
|
string name; // Name of the site
|
||||||
string description; // Description about the site
|
string description; // Description about the site
|
||||||
string image; // Preview Image IPFS Link
|
string image; // Preview Image IPFS Link
|
||||||
bytes32 external_url; // Site URL
|
string external_url; // Site URL
|
||||||
bytes32 ENS; // ENS ID
|
string ENS; // ENS ID
|
||||||
uint256 current_build; // The current build number (Increments by one with each change, starts at zero)
|
uint256 current_build; // The current build number (Increments by one with each change, starts at zero)
|
||||||
mapping(uint256 => Build) builds; // Mapping to build details for each build number
|
mapping(uint256 => Build) builds; // Mapping to build details for each build number
|
||||||
}
|
}
|
||||||
|
|
@ -56,8 +61,8 @@ contract FleekERC721 is ERC721, FleekAccessControl {
|
||||||
string memory name,
|
string memory name,
|
||||||
string memory description,
|
string memory description,
|
||||||
string memory image,
|
string memory image,
|
||||||
bytes32 external_url,
|
string memory external_url,
|
||||||
bytes32 ENS,
|
string memory ENS,
|
||||||
string memory commit_hash,
|
string memory commit_hash,
|
||||||
string memory git_repository,
|
string memory git_repository,
|
||||||
string memory author
|
string memory author
|
||||||
|
|
@ -77,7 +82,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
|
||||||
// The mint interaction is considered to be the first build of the site. Updates from now on all increment the current_build by one and update the mapping.
|
// The mint interaction is considered to be the first build of the site. Updates from now on all increment the current_build by one and update the mapping.
|
||||||
app.current_build = 0;
|
app.current_build = 0;
|
||||||
app.builds[0] = Build(commit_hash, git_repository, author);
|
app.builds[0] = Build(commit_hash, git_repository, author);
|
||||||
|
|
||||||
return tokenId;
|
return tokenId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,12 +102,12 @@ contract FleekERC721 is ERC721, FleekAccessControl {
|
||||||
_requireMinted(tokenId);
|
_requireMinted(tokenId);
|
||||||
address owner = ownerOf(tokenId);
|
address owner = ownerOf(tokenId);
|
||||||
App storage app = _apps[tokenId];
|
App storage app = _apps[tokenId];
|
||||||
|
|
||||||
bytes memory dataURI = abi.encodePacked(
|
bytes memory dataURI = abi.encodePacked(
|
||||||
'{',
|
'{',
|
||||||
'"name":"', app.name, '",',
|
'"name":"', app.name, '",',
|
||||||
'"description":"', app.description, '",',
|
'"description":"', app.description, '",',
|
||||||
'"owner":"', abi.encodePacked(owner), '",',
|
'"owner":"', Strings.toHexString(uint160(owner), 20), '",',
|
||||||
'"ENS":"', app.ENS, '",',
|
|
||||||
'"external_url":"', app.external_url, '",',
|
'"external_url":"', app.external_url, '",',
|
||||||
'"image":"', app.image, '",',
|
'"image":"', app.image, '",',
|
||||||
'"attributes": [',
|
'"attributes": [',
|
||||||
|
|
@ -110,12 +115,12 @@ contract FleekERC721 is ERC721, FleekAccessControl {
|
||||||
'{"trait_type": "Commit Hash", "value":"', app.builds[app.current_build].commit_hash,'"},',
|
'{"trait_type": "Commit Hash", "value":"', app.builds[app.current_build].commit_hash,'"},',
|
||||||
'{"trait_type": "Repository", "value":"', app.builds[app.current_build].git_repository,'"},',
|
'{"trait_type": "Repository", "value":"', app.builds[app.current_build].git_repository,'"},',
|
||||||
'{"trait_type": "Author", "value":"', app.builds[app.current_build].author,'"},',
|
'{"trait_type": "Author", "value":"', app.builds[app.current_build].author,'"},',
|
||||||
'{"trait_type": "Version", "value":"', app.current_build,'"}',
|
'{"trait_type": "Version", "value":"', Strings.toString(app.current_build),'"}',
|
||||||
']',
|
']',
|
||||||
'}'
|
'}'
|
||||||
);
|
);
|
||||||
|
|
||||||
return string(abi.encodePacked(_baseURI(), Base64.encode(dataURI)));
|
return string(abi.encodePacked(_baseURI(), Base64.encode((dataURI))));
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTokenController(
|
function addTokenController(
|
||||||
|
|
@ -146,8 +151,8 @@ contract FleekERC721 is ERC721, FleekAccessControl {
|
||||||
|
|
||||||
function setTokenExternalURL(
|
function setTokenExternalURL(
|
||||||
uint256 tokenId,
|
uint256 tokenId,
|
||||||
bytes32 _tokenExternalURL
|
string memory _tokenExternalURL
|
||||||
) public virtual payable requireTokenController(tokenId) {
|
) public virtual requireTokenController(tokenId) {
|
||||||
_requireMinted(tokenId);
|
_requireMinted(tokenId);
|
||||||
_apps[tokenId].external_url = _tokenExternalURL;
|
_apps[tokenId].external_url = _tokenExternalURL;
|
||||||
emit NewTokenExternalURL(tokenId, _tokenExternalURL);
|
emit NewTokenExternalURL(tokenId, _tokenExternalURL);
|
||||||
|
|
@ -155,8 +160,8 @@ contract FleekERC721 is ERC721, FleekAccessControl {
|
||||||
|
|
||||||
function setTokenENS(
|
function setTokenENS(
|
||||||
uint256 tokenId,
|
uint256 tokenId,
|
||||||
bytes32 _tokenENS
|
string memory _tokenENS
|
||||||
) public virtual payable requireTokenController(tokenId) {
|
) public virtual requireTokenController(tokenId) {
|
||||||
_requireMinted(tokenId);
|
_requireMinted(tokenId);
|
||||||
_apps[tokenId].ENS = _tokenENS;
|
_apps[tokenId].ENS = _tokenENS;
|
||||||
emit NewTokenENS(tokenId, _tokenENS);
|
emit NewTokenENS(tokenId, _tokenENS);
|
||||||
|
|
@ -165,7 +170,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
|
||||||
function setTokenName(
|
function setTokenName(
|
||||||
uint256 tokenId,
|
uint256 tokenId,
|
||||||
string memory _tokenName
|
string memory _tokenName
|
||||||
) public virtual payable requireTokenController(tokenId) {
|
) public virtual requireTokenController(tokenId) {
|
||||||
_requireMinted(tokenId);
|
_requireMinted(tokenId);
|
||||||
_apps[tokenId].name = _tokenName;
|
_apps[tokenId].name = _tokenName;
|
||||||
emit NewTokenName(tokenId, _tokenName);
|
emit NewTokenName(tokenId, _tokenName);
|
||||||
|
|
@ -174,7 +179,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
|
||||||
function setTokenDescription(
|
function setTokenDescription(
|
||||||
uint256 tokenId,
|
uint256 tokenId,
|
||||||
string memory _tokenDescription
|
string memory _tokenDescription
|
||||||
) public virtual payable requireTokenController(tokenId) {
|
) public virtual requireTokenController(tokenId) {
|
||||||
_requireMinted(tokenId);
|
_requireMinted(tokenId);
|
||||||
_apps[tokenId].description = _tokenDescription;
|
_apps[tokenId].description = _tokenDescription;
|
||||||
emit NewTokenDescription(tokenId, _tokenDescription);
|
emit NewTokenDescription(tokenId, _tokenDescription);
|
||||||
|
|
@ -183,7 +188,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
|
||||||
function setTokenImage(
|
function setTokenImage(
|
||||||
uint256 tokenId,
|
uint256 tokenId,
|
||||||
string memory _tokenImage
|
string memory _tokenImage
|
||||||
) public virtual payable requireTokenController(tokenId) {
|
) public virtual requireTokenController(tokenId) {
|
||||||
_requireMinted(tokenId);
|
_requireMinted(tokenId);
|
||||||
_apps[tokenId].image = _tokenImage;
|
_apps[tokenId].image = _tokenImage;
|
||||||
emit NewTokenImage(tokenId, _tokenImage);
|
emit NewTokenImage(tokenId, _tokenImage);
|
||||||
|
|
@ -194,20 +199,22 @@ contract FleekERC721 is ERC721, FleekAccessControl {
|
||||||
string memory _commit_hash,
|
string memory _commit_hash,
|
||||||
string memory _git_repository,
|
string memory _git_repository,
|
||||||
string memory _author
|
string memory _author
|
||||||
) public virtual payable requireTokenController(tokenId) {
|
) public virtual requireTokenController(tokenId) {
|
||||||
_requireMinted(tokenId);
|
_requireMinted(tokenId);
|
||||||
_apps[tokenId].builds[++_apps[tokenId].current_build] = Build(_commit_hash, _git_repository, _author);
|
_apps[tokenId].builds[++_apps[tokenId].current_build] = Build(_commit_hash, _git_repository, _author);
|
||||||
emit NewBuild(tokenId, _commit_hash);
|
emit NewBuild(tokenId, _commit_hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
function burn(uint256 tokenId) public virtual payable requireTokenController(tokenId) {
|
function burn(
|
||||||
|
uint256 tokenId
|
||||||
|
) public virtual requireTokenController(tokenId) {
|
||||||
require(
|
require(
|
||||||
ownerOf(tokenId) == msg.sender,
|
ownerOf(tokenId) == msg.sender,
|
||||||
"FleekERC721: must be token owner"
|
"FleekERC721: must be token owner"
|
||||||
);
|
);
|
||||||
super._burn(tokenId);
|
super._burn(tokenId);
|
||||||
|
|
||||||
if (_apps[tokenId].external_url.length != 0) {
|
if (bytes(_apps[tokenId].external_url).length != 0) {
|
||||||
delete _apps[tokenId];
|
delete _apps[tokenId];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
require('@nomiclabs/hardhat-waffle');
|
|
||||||
require('@nomiclabs/hardhat-etherscan');
|
|
||||||
require('hardhat-deploy');
|
|
||||||
require('solidity-coverage');
|
|
||||||
require('hardhat-gas-reporter');
|
|
||||||
require('hardhat-contract-sizer');
|
|
||||||
require('dotenv').config();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type import('hardhat/config').HardhatUserConfig
|
|
||||||
*/
|
|
||||||
|
|
||||||
const MAINNET_RPC_URL =
|
|
||||||
process.env.MAINNET_RPC_URL ||
|
|
||||||
process.env.ALCHEMY_MAINNET_RPC_URL ||
|
|
||||||
'https://eth-mainnet.alchemyapi.io/v2/your-api-key';
|
|
||||||
const GOERLI_RPC_URL =
|
|
||||||
process.env.GOERLI_RPC_URL ||
|
|
||||||
'https://eth-goerli.alchemyapi.io/v2/your-api-key';
|
|
||||||
const API_URL =
|
|
||||||
process.env.API_URL ||
|
|
||||||
'https://polygon-mainnet.alchemyapi.io/v2/your-api-key';
|
|
||||||
|
|
||||||
const POLYGON_MUMBAI_RPC_URL =
|
|
||||||
process.env.POLYGON_MUMBAI_RPC_URL ||
|
|
||||||
'https://polygon-mumbai.g.alchemy.com/v2/aIjNlC4r4aLYOHrdCTFT_JUX6OJsOsu0';
|
|
||||||
const PRIVATE_KEY = process.env.PRIVATE_KEY || '0x';
|
|
||||||
// optional
|
|
||||||
const MNEMONIC = process.env.MNEMONIC || 'your mnemonic';
|
|
||||||
|
|
||||||
// Your API key for Etherscan, obtain one at https://etherscan.io/
|
|
||||||
const ETHERSCAN_API_KEY =
|
|
||||||
process.env.ETHERSCAN_API_KEY || 'Your etherscan API key';
|
|
||||||
const POLYGONSCAN_API_KEY =
|
|
||||||
process.env.POLYGONSCAN_API_KEY || 'Your polygonscan API key';
|
|
||||||
const REPORT_GAS = process.env.REPORT_GAS || false;
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
defaultNetwork: 'hardhat',
|
|
||||||
networks: {
|
|
||||||
hardhat: {
|
|
||||||
// // If you want to do some forking, uncomment this
|
|
||||||
// forking: {
|
|
||||||
// url: MAINNET_RPC_URL
|
|
||||||
// }
|
|
||||||
chainId: 31337,
|
|
||||||
},
|
|
||||||
localhost: {
|
|
||||||
chainId: 31337,
|
|
||||||
},
|
|
||||||
// goerli: {
|
|
||||||
// url: GOERLI_RPC_URL,
|
|
||||||
// accounts: PRIVATE_KEY !== undefined ? [PRIVATE_KEY] : [],
|
|
||||||
// // accounts: {
|
|
||||||
// // mnemonic: MNEMONIC,
|
|
||||||
// // },
|
|
||||||
// saveDeployments: true,
|
|
||||||
// chainId: 5,
|
|
||||||
// },
|
|
||||||
// mainnet: {
|
|
||||||
// url: MAINNET_RPC_URL,
|
|
||||||
// accounts: PRIVATE_KEY !== undefined ? [PRIVATE_KEY] : [],
|
|
||||||
// // accounts: {
|
|
||||||
// // mnemonic: MNEMONIC,
|
|
||||||
// // },
|
|
||||||
// saveDeployments: true,
|
|
||||||
// chainId: 1,
|
|
||||||
// },
|
|
||||||
// polygon: {
|
|
||||||
// url: POLYGON_MAINNET_RPC_URL,
|
|
||||||
// accounts: PRIVATE_KEY !== undefined ? [PRIVATE_KEY] : [],
|
|
||||||
// saveDeployments: true,
|
|
||||||
// chainId: 137,
|
|
||||||
// },
|
|
||||||
polygonMumbai: {
|
|
||||||
url: API_URL,
|
|
||||||
accounts: PRIVATE_KEY !== undefined ? [PRIVATE_KEY] : [],
|
|
||||||
saveDeployments: true,
|
|
||||||
chainId: 80001,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
etherscan: {
|
|
||||||
// npx hardhat verify --network <NETWORK> <CONTRACT_ADDRESS> <CONSTRUCTOR_PARAMETERS>
|
|
||||||
apiKey: {
|
|
||||||
goerli: ETHERSCAN_API_KEY,
|
|
||||||
polygon: process.env.POLYSCAN_API,
|
|
||||||
polygonMumbai: process.env.POLYSCAN_API,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
gasReporter: {
|
|
||||||
enabled: REPORT_GAS,
|
|
||||||
currency: 'USD',
|
|
||||||
outputFile: 'gas-report.txt',
|
|
||||||
noColors: true,
|
|
||||||
// coinmarketcap: process.env.COINMARKETCAP_API_KEY,
|
|
||||||
},
|
|
||||||
contractSizer: {
|
|
||||||
runOnCompile: false,
|
|
||||||
only: ['NftMarketplace'],
|
|
||||||
},
|
|
||||||
namedAccounts: {
|
|
||||||
deployer: {
|
|
||||||
default: 1, // here this will by default take the first account as deployer
|
|
||||||
1: 0,
|
|
||||||
},
|
|
||||||
privateKey: {
|
|
||||||
default: `privatekey://${PRIVATE_KEY}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
solidity: {
|
|
||||||
compilers: [
|
|
||||||
{
|
|
||||||
version: '0.8.7',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
version: '0.4.24',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
mocha: {
|
|
||||||
timeout: 200000, // 200 seconds max for running tests
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import '@nomiclabs/hardhat-ethers';
|
||||||
|
import '@nomiclabs/hardhat-web3';
|
||||||
|
import '@nomicfoundation/hardhat-chai-matchers';
|
||||||
|
import 'hardhat-deploy';
|
||||||
|
import 'solidity-coverage';
|
||||||
|
import 'hardhat-gas-reporter';
|
||||||
|
import 'hardhat-contract-sizer';
|
||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
import { HardhatUserConfig } from 'hardhat/types';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const {
|
||||||
|
API_URL = 'https://polygon-mainnet.alchemyapi.io/v2/your-api-key',
|
||||||
|
PRIVATE_KEY,
|
||||||
|
REPORT_GAS,
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
const config: HardhatUserConfig = {
|
||||||
|
defaultNetwork: 'hardhat',
|
||||||
|
networks: {
|
||||||
|
hardhat: {
|
||||||
|
chainId: 31337,
|
||||||
|
},
|
||||||
|
localhost: {
|
||||||
|
chainId: 31337,
|
||||||
|
},
|
||||||
|
polygonMumbai: {
|
||||||
|
url: API_URL,
|
||||||
|
accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [],
|
||||||
|
saveDeployments: true,
|
||||||
|
chainId: 80001,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
gasReporter: {
|
||||||
|
enabled: REPORT_GAS === 'true' || false,
|
||||||
|
currency: 'USD',
|
||||||
|
outputFile: 'gas-report.txt',
|
||||||
|
noColors: true,
|
||||||
|
// coinmarketcap: process.env.COINMARKETCAP_API_KEY,
|
||||||
|
},
|
||||||
|
contractSizer: {
|
||||||
|
runOnCompile: false,
|
||||||
|
only: ['NftMarketplace'],
|
||||||
|
},
|
||||||
|
namedAccounts: {
|
||||||
|
deployer: {
|
||||||
|
default: 1, // here this will by default take the first account as deployer
|
||||||
|
1: 0,
|
||||||
|
},
|
||||||
|
privateKey: {
|
||||||
|
default: `privatekey://${PRIVATE_KEY}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
solidity: {
|
||||||
|
compilers: [
|
||||||
|
{
|
||||||
|
version: '0.8.7',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
mocha: {
|
||||||
|
timeout: 200000, // 200 seconds max for running tests
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
11
package.json
11
package.json
|
|
@ -23,14 +23,16 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/FleekHQ/contracts#readme",
|
"homepage": "https://github.com/FleekHQ/contracts#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@nomicfoundation/hardhat-chai-matchers": "^1.0.5",
|
||||||
|
"@nomicfoundation/hardhat-network-helpers": "^1.0.7",
|
||||||
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
|
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
|
||||||
"@nomiclabs/hardhat-ethers": "^2.2.1",
|
"@nomiclabs/hardhat-ethers": "^2.2.1",
|
||||||
"@nomiclabs/hardhat-etherscan": "^3.1.0",
|
"@nomiclabs/hardhat-etherscan": "^3.1.0",
|
||||||
"@nomiclabs/hardhat-waffle": "^2.0.3",
|
"@nomiclabs/hardhat-web3": "^2.0.0",
|
||||||
"@openzeppelin/contracts": "^4.7.3",
|
"@openzeppelin/contracts": "^4.7.3",
|
||||||
|
"@types/mocha": "^10.0.1",
|
||||||
"chai": "^4.3.6",
|
"chai": "^4.3.6",
|
||||||
"dotenv": "^16.0.2",
|
"dotenv": "^16.0.2",
|
||||||
"ethereum-waffle": "^3.4.4",
|
|
||||||
"ethers": "^5.7.2",
|
"ethers": "^5.7.2",
|
||||||
"hardhat": "^2.11.2",
|
"hardhat": "^2.11.2",
|
||||||
"hardhat-contract-sizer": "^2.6.1",
|
"hardhat-contract-sizer": "^2.6.1",
|
||||||
|
|
@ -39,6 +41,9 @@
|
||||||
"minimist": "^1.2.7",
|
"minimist": "^1.2.7",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"prettier-plugin-solidity": "^1.0.0",
|
"prettier-plugin-solidity": "^1.0.0",
|
||||||
"solidity-coverage": "^0.8.2"
|
"solidity-coverage": "^0.8.2",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"typescript": "^4.9.3",
|
||||||
|
"web3": "^1.8.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { ethers } from 'hardhat';
|
||||||
|
import web3 from 'web3';
|
||||||
|
|
||||||
|
describe('FleekERC721', () => {
|
||||||
|
const COLLECTION_OWNER_ROLE = web3.utils.keccak256('COLLECTION_OWNER_ROLE');
|
||||||
|
|
||||||
|
const MINT_PARAMS = Object.freeze({
|
||||||
|
name: 'Fleek Test App',
|
||||||
|
description: 'Fleek Test App Description',
|
||||||
|
image: 'https://fleek.co/image.png',
|
||||||
|
ens: 'fleek.eth',
|
||||||
|
externalUrl: 'https://fleek.co',
|
||||||
|
commitHash: 'b72e47171746b6a9e29b801af9cb655ecf4d665c',
|
||||||
|
gitRepository: 'https://github.com/fleekxyz/contracts',
|
||||||
|
author: 'author',
|
||||||
|
});
|
||||||
|
|
||||||
|
const COLLECTION_PARAMS = Object.freeze({
|
||||||
|
name: 'FleekERC721',
|
||||||
|
symbol: 'FLEEK',
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultFixture = async () => {
|
||||||
|
// Contracts are deployed using the first signer/account by default
|
||||||
|
const [owner, otherAccount] = await ethers.getSigners();
|
||||||
|
|
||||||
|
const Contract = await ethers.getContractFactory('FleekERC721');
|
||||||
|
const contract = await Contract.deploy(
|
||||||
|
COLLECTION_PARAMS.name,
|
||||||
|
COLLECTION_PARAMS.symbol
|
||||||
|
);
|
||||||
|
|
||||||
|
return { owner, otherAccount, contract };
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Deployment', () => {
|
||||||
|
it('should assign the name and the symbol of the ERC721 contract', async () => {
|
||||||
|
const { contract } = await loadFixture(defaultFixture);
|
||||||
|
|
||||||
|
expect(await contract.name()).to.equal(COLLECTION_PARAMS.name);
|
||||||
|
expect(await contract.symbol()).to.equal(COLLECTION_PARAMS.symbol);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should assign the owner of the contract', async () => {
|
||||||
|
const { owner, contract } = await loadFixture(defaultFixture);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await contract.hasRole(COLLECTION_OWNER_ROLE, owner.address)
|
||||||
|
).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support ERC721 interface', async () => {
|
||||||
|
const { contract } = await loadFixture(defaultFixture);
|
||||||
|
|
||||||
|
expect(await contract.supportsInterface('0x80ac58cd')).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Minting', () => {
|
||||||
|
it('should be able to mint a new token', async () => {
|
||||||
|
const { owner, contract } = await loadFixture(defaultFixture);
|
||||||
|
|
||||||
|
const response = await contract.mint(
|
||||||
|
owner.address,
|
||||||
|
MINT_PARAMS.name,
|
||||||
|
MINT_PARAMS.description,
|
||||||
|
MINT_PARAMS.image,
|
||||||
|
MINT_PARAMS.externalUrl,
|
||||||
|
MINT_PARAMS.ens,
|
||||||
|
MINT_PARAMS.commitHash,
|
||||||
|
MINT_PARAMS.gitRepository,
|
||||||
|
MINT_PARAMS.author
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.value).to.be.instanceOf(ethers.BigNumber);
|
||||||
|
expect(response.value.toNumber()).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be able to mint a new token if not the owner', async () => {
|
||||||
|
const { otherAccount, contract } = await loadFixture(defaultFixture);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
contract
|
||||||
|
.connect(otherAccount)
|
||||||
|
.mint(
|
||||||
|
otherAccount.address,
|
||||||
|
MINT_PARAMS.name,
|
||||||
|
MINT_PARAMS.description,
|
||||||
|
MINT_PARAMS.image,
|
||||||
|
MINT_PARAMS.externalUrl,
|
||||||
|
MINT_PARAMS.ens,
|
||||||
|
MINT_PARAMS.commitHash,
|
||||||
|
MINT_PARAMS.gitRepository,
|
||||||
|
MINT_PARAMS.author
|
||||||
|
)
|
||||||
|
).to.be.revertedWith(
|
||||||
|
'FleekAccessControl: must have collection owner role'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Token', () => {
|
||||||
|
let tokenId: number;
|
||||||
|
let fixture: Awaited<ReturnType<typeof defaultFixture>>;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture(defaultFixture);
|
||||||
|
const { contract } = fixture;
|
||||||
|
|
||||||
|
const response = await contract.mint(
|
||||||
|
fixture.owner.address,
|
||||||
|
MINT_PARAMS.name,
|
||||||
|
MINT_PARAMS.description,
|
||||||
|
MINT_PARAMS.image,
|
||||||
|
MINT_PARAMS.externalUrl,
|
||||||
|
MINT_PARAMS.ens,
|
||||||
|
MINT_PARAMS.commitHash,
|
||||||
|
MINT_PARAMS.gitRepository,
|
||||||
|
MINT_PARAMS.author
|
||||||
|
);
|
||||||
|
|
||||||
|
tokenId = response.value.toNumber();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the token URI', async () => {
|
||||||
|
const { contract } = fixture;
|
||||||
|
const tokenURI = await contract.tokenURI(tokenId);
|
||||||
|
|
||||||
|
const tokenURIDecoded = Buffer.from(
|
||||||
|
tokenURI.replace('data:application/json;base64,', ''),
|
||||||
|
'base64'
|
||||||
|
).toString('ascii');
|
||||||
|
|
||||||
|
const parsedURI = JSON.parse(tokenURIDecoded);
|
||||||
|
|
||||||
|
expect(parsedURI).to.eql({
|
||||||
|
owner: fixture.owner.address.toLowerCase(),
|
||||||
|
name: MINT_PARAMS.name,
|
||||||
|
description: MINT_PARAMS.description,
|
||||||
|
image: MINT_PARAMS.image,
|
||||||
|
external_url: MINT_PARAMS.externalUrl,
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
trait_type: 'ENS',
|
||||||
|
value: MINT_PARAMS.ens,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
trait_type: 'Commit Hash',
|
||||||
|
value: MINT_PARAMS.commitHash,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
trait_type: 'Repository',
|
||||||
|
value: MINT_PARAMS.gitRepository,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
trait_type: 'Author',
|
||||||
|
value: MINT_PARAMS.author,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
trait_type: 'Version',
|
||||||
|
value: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match the token owner', async () => {
|
||||||
|
const { contract, owner } = fixture;
|
||||||
|
const tokenOwner = await contract.ownerOf(tokenId);
|
||||||
|
expect(tokenOwner).to.equal(owner.address);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,297 +0,0 @@
|
||||||
const { expect } = require('chai');
|
|
||||||
|
|
||||||
describe('SitesNFTs contract', function () {
|
|
||||||
describe('Deployment', () => {
|
|
||||||
it('Deployment should assign the name and the symbol of the ERC721 contract', async () => {
|
|
||||||
const [owner] = await ethers.getSigners();
|
|
||||||
|
|
||||||
const name = 'Sites NFTs';
|
|
||||||
const symbol = 'SNFT';
|
|
||||||
|
|
||||||
const SitesNFTs = await ethers.getContractFactory('SitesNFTs');
|
|
||||||
|
|
||||||
const hardhatSitesNFTs = await SitesNFTs.deploy('Sites NFTs', 'SNFT');
|
|
||||||
|
|
||||||
const contractName = await hardhatSitesNFTs.name();
|
|
||||||
const contractSymbol = await hardhatSitesNFTs.symbol();
|
|
||||||
|
|
||||||
expect(contractName).to.equal(name);
|
|
||||||
expect(contractSymbol).to.equal(symbol);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Deployment should assign the deployer DEFAULT_ADMIN_ROLE', async () => {
|
|
||||||
const [owner] = await ethers.getSigners();
|
|
||||||
|
|
||||||
const SitesNFTs = await ethers.getContractFactory('SitesNFTs');
|
|
||||||
|
|
||||||
const hardhatSitesNFTs = await SitesNFTs.deploy('Sites NFTs', 'SNFT');
|
|
||||||
|
|
||||||
const DEFAULT_ADMIN_ROLE_STRING = '';
|
|
||||||
|
|
||||||
const hasAdminRole = await hardhatSitesNFTs.hasRole(
|
|
||||||
ethers.utils.formatBytes32String(DEFAULT_ADMIN_ROLE_STRING),
|
|
||||||
await owner.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(hasAdminRole).to.equal(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Deployment should assign initial tokenId to 0', async () => {
|
|
||||||
const [owner] = await ethers.getSigners();
|
|
||||||
|
|
||||||
const SitesNFTs = await ethers.getContractFactory('SitesNFTs');
|
|
||||||
|
|
||||||
const hardhatSitesNFTs = await SitesNFTs.deploy('Sites NFTs', 'SNFT');
|
|
||||||
|
|
||||||
const currentTokenId = await hardhatSitesNFTs.getCurrentTokenId();
|
|
||||||
|
|
||||||
expect(currentTokenId).to.equal(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Access control', () => {
|
|
||||||
it('User with DEFAULT_ADMIN_ROLE should be able to assign MINTER_ROLE to another user', async () => {
|
|
||||||
const [owner, address1] = await ethers.getSigners();
|
|
||||||
|
|
||||||
const SitesNFTs = await ethers.getContractFactory('SitesNFTs');
|
|
||||||
|
|
||||||
const hardhatSitesNFTs = await SitesNFTs.deploy('Sites NFTs', 'SNFT');
|
|
||||||
|
|
||||||
const MINTER_ROLE = 'MINTER_ROLE';
|
|
||||||
|
|
||||||
await hardhatSitesNFTs.grantRole(
|
|
||||||
ethers.utils.formatBytes32String(MINTER_ROLE),
|
|
||||||
await address1.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasMinterRole = await hardhatSitesNFTs.hasRole(
|
|
||||||
ethers.utils.formatBytes32String(MINTER_ROLE),
|
|
||||||
await address1.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(hasMinterRole).to.equal(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('User with DEFAULT_ADMIN_ROLE should be able to assign MINTER_ROLE to himself and still have DEFAULT_ADMIN_ROLE', async () => {
|
|
||||||
const [owner] = await ethers.getSigners();
|
|
||||||
|
|
||||||
const SitesNFTs = await ethers.getContractFactory('SitesNFTs');
|
|
||||||
|
|
||||||
const hardhatSitesNFTs = await SitesNFTs.deploy('Sites NFTs', 'SNFT');
|
|
||||||
|
|
||||||
const MINTER_ROLE = 'MINTER_ROLE';
|
|
||||||
const DEFAULT_ADMIN_ROLE = '';
|
|
||||||
|
|
||||||
await hardhatSitesNFTs.grantRole(
|
|
||||||
ethers.utils.formatBytes32String(MINTER_ROLE),
|
|
||||||
await owner.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasMinterRole = await hardhatSitesNFTs.hasRole(
|
|
||||||
ethers.utils.formatBytes32String(MINTER_ROLE),
|
|
||||||
await owner.getAddress()
|
|
||||||
);
|
|
||||||
const hasAdminRole = await hardhatSitesNFTs.hasRole(
|
|
||||||
ethers.utils.formatBytes32String(DEFAULT_ADMIN_ROLE),
|
|
||||||
await owner.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(hasMinterRole).to.equal(true);
|
|
||||||
expect(hasAdminRole).to.equal(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('User with DEFAULT_ADMIN_ROLE should be able to assign DEFAULT_ADMIN_ROLE to another user', async () => {
|
|
||||||
const [owner, address1] = await ethers.getSigners();
|
|
||||||
|
|
||||||
const SitesNFTs = await ethers.getContractFactory('SitesNFTs');
|
|
||||||
|
|
||||||
const hardhatSitesNFTs = await SitesNFTs.deploy('Sites NFTs', 'SNFT');
|
|
||||||
|
|
||||||
const DEFAULT_ADMIN_ROLE = '';
|
|
||||||
|
|
||||||
await hardhatSitesNFTs.grantRole(
|
|
||||||
ethers.utils.formatBytes32String(DEFAULT_ADMIN_ROLE),
|
|
||||||
await address1.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasAdminRole = await hardhatSitesNFTs.hasRole(
|
|
||||||
ethers.utils.formatBytes32String(DEFAULT_ADMIN_ROLE),
|
|
||||||
await address1.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(hasAdminRole).to.equal(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('User with DEFAULT_ADMIN_ROLE should be able to assign DEFAULT_ADMIN_ROLE to another user and still have DEFAULT_ADMIN_ROLE', async () => {
|
|
||||||
const [owner, address1] = await ethers.getSigners();
|
|
||||||
|
|
||||||
const SitesNFTs = await ethers.getContractFactory('SitesNFTs');
|
|
||||||
|
|
||||||
const hardhatSitesNFTs = await SitesNFTs.deploy('Sites NFTs', 'SNFT');
|
|
||||||
|
|
||||||
const DEFAULT_ADMIN_ROLE = '';
|
|
||||||
|
|
||||||
await hardhatSitesNFTs.grantRole(
|
|
||||||
ethers.utils.formatBytes32String(DEFAULT_ADMIN_ROLE),
|
|
||||||
await address1.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
let hasAdminRole = await hardhatSitesNFTs.hasRole(
|
|
||||||
ethers.utils.formatBytes32String(DEFAULT_ADMIN_ROLE),
|
|
||||||
await address1.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(hasAdminRole).to.equal(true);
|
|
||||||
|
|
||||||
hasAdminRole = await hardhatSitesNFTs.hasRole(
|
|
||||||
ethers.utils.formatBytes32String(DEFAULT_ADMIN_ROLE),
|
|
||||||
await owner.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(hasAdminRole).to.equal(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('User without DEFAULT_ADMIN_ROLE shouldnt be able to assign DEFAULT_ADMIN_ROLE to another user', async () => {
|
|
||||||
const [owner, address1, address2] = await ethers.getSigners();
|
|
||||||
|
|
||||||
const SitesNFTs = await ethers.getContractFactory('SitesNFTs');
|
|
||||||
|
|
||||||
const hardhatSitesNFTs = await SitesNFTs.deploy('Sites NFTs', 'SNFT');
|
|
||||||
|
|
||||||
const DEFAULT_ADMIN_ROLE = '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
await hardhatSitesNFTs
|
|
||||||
.connect(address1)
|
|
||||||
.grantRole(
|
|
||||||
ethers.utils.formatBytes32String(DEFAULT_ADMIN_ROLE),
|
|
||||||
await address2.getAddress()
|
|
||||||
);
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
const hasAdminRole = await hardhatSitesNFTs.hasRole(
|
|
||||||
ethers.utils.formatBytes32String(DEFAULT_ADMIN_ROLE),
|
|
||||||
await address2.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(hasAdminRole).to.equal(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('User without DEFAULT_ADMIN_ROLE shouldnt be able to assign MINTER_ROLE to another user', async () => {
|
|
||||||
const [owner, address1, address2] = await ethers.getSigners();
|
|
||||||
|
|
||||||
const SitesNFTs = await ethers.getContractFactory('SitesNFTs');
|
|
||||||
|
|
||||||
const hardhatSitesNFTs = await SitesNFTs.deploy('Sites NFTs', 'SNFT');
|
|
||||||
|
|
||||||
const MINTER_ROLE = 'MINTER_ROLE';
|
|
||||||
|
|
||||||
try {
|
|
||||||
await hardhatSitesNFTs
|
|
||||||
.connect(address1)
|
|
||||||
.grantRole(
|
|
||||||
ethers.utils.formatBytes32String(MINTER_ROLE),
|
|
||||||
await address2.getAddress()
|
|
||||||
);
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
const hasMinterRole = await hardhatSitesNFTs.hasRole(
|
|
||||||
ethers.utils.formatBytes32String(MINTER_ROLE),
|
|
||||||
await address2.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(hasMinterRole).to.equal(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Minting', () => {
|
|
||||||
it('User with DEFAULT_ADMIN_ROLE should be able to mint', async () => {
|
|
||||||
const [owner, address1] = await ethers.getSigners();
|
|
||||||
|
|
||||||
const SitesNFTs = await ethers.getContractFactory('SitesNFTs');
|
|
||||||
|
|
||||||
const hardhatSitesNFTs = await SitesNFTs.deploy('Sites NFTs', 'SNFT');
|
|
||||||
|
|
||||||
const tokenURI = 'tokenURI';
|
|
||||||
|
|
||||||
await hardhatSitesNFTs.mint(tokenURI, await address1.getAddress());
|
|
||||||
|
|
||||||
const balance = await hardhatSitesNFTs.balanceOf(
|
|
||||||
await address1.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(balance).to.equal(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('User with MINTER_ROLE should be able to mint', async () => {
|
|
||||||
const [owner, address1, address2] = await ethers.getSigners();
|
|
||||||
|
|
||||||
const SitesNFTs = await ethers.getContractFactory('SitesNFTs');
|
|
||||||
|
|
||||||
const hardhatSitesNFTs = await SitesNFTs.deploy('Sites NFTs', 'SNFT');
|
|
||||||
|
|
||||||
const MINTER_ROLE = 'MINTER_ROLE';
|
|
||||||
const tokenURI = 'tokenURI';
|
|
||||||
|
|
||||||
await hardhatSitesNFTs.grantRole(
|
|
||||||
ethers.utils.formatBytes32String(MINTER_ROLE),
|
|
||||||
await address1.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
await hardhatSitesNFTs.hasRole(
|
|
||||||
ethers.utils.formatBytes32String(MINTER_ROLE),
|
|
||||||
await address1.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
await hardhatSitesNFTs
|
|
||||||
.connect(address1)
|
|
||||||
.mint(tokenURI, await address2.getAddress());
|
|
||||||
|
|
||||||
const balance = await hardhatSitesNFTs.balanceOf(
|
|
||||||
await address2.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(balance).to.equal(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('User without MINTER_ROLE or DEFAULT_ADMIN_ROLE shouldnt be able to mint', async () => {
|
|
||||||
const [owner, address1, address2] = await ethers.getSigners();
|
|
||||||
|
|
||||||
const SitesNFTs = await ethers.getContractFactory('SitesNFTs');
|
|
||||||
|
|
||||||
const hardhatSitesNFTs = await SitesNFTs.deploy('Sites NFTs', 'SNFT');
|
|
||||||
|
|
||||||
const tokenURI = 'tokenURI';
|
|
||||||
|
|
||||||
try {
|
|
||||||
await hardhatSitesNFTs
|
|
||||||
.connect(address1)
|
|
||||||
.mint(tokenURI, await address2.getAddress());
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
const balance = await hardhatSitesNFTs.balanceOf(
|
|
||||||
await address2.getAddress()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(balance).to.equal(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Minted NFT should have data:application/json;base64, baseURI', async () => {
|
|
||||||
const [owner, address1] = await ethers.getSigners();
|
|
||||||
|
|
||||||
const SitesNFTs = await ethers.getContractFactory('SitesNFTs');
|
|
||||||
|
|
||||||
const hardhatSitesNFTs = await SitesNFTs.deploy('Sites NFTs', 'SNFT');
|
|
||||||
|
|
||||||
const tokenURI = 'tokenURI';
|
|
||||||
|
|
||||||
await hardhatSitesNFTs.mint(tokenURI, await address1.getAddress());
|
|
||||||
|
|
||||||
const mintedNFT = await hardhatSitesNFTs.tokenURI(0);
|
|
||||||
|
|
||||||
expect(mintedNFT.includes('data:application/json;base64,')).to.equal(
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Loading…
Reference in New Issue