feat: fleek erc721 (#5)
* wip: refactor on current nft contract * wip: FleekERC721 contract * refactor: FleekERC721 * feat: add token controller control functions * fix: sintax wise issues for compilation * Fix errors and make the contract environment ready for compiling. * Remove fleekbuilds.sol & update erc721 with a fix * Update config files. * fix: working deploy * Make set methods public, add comments and notes to clarify issues. * Update package.json to add the deploy script & remove package-lock.json from the repository * Add two deploy scripts for local and mumbai deployments, update hardhat config to match two types and package.json * Update TokenURI and the metadata struct * Update deploy script and package.json to match the mumbai deploy script * Add setTokenName, setTokenDescription, setTokenImage * Add events to all set functions * Foundry init configs * Add foundry tests init (name, symbol, placeholder functions) * 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> * feat: add interaction scripts * feat: add function signature to remove token controllers on transfer functions * Update test commands & add forge-cache and out to .gitignore * refactor: change token controller role validation to _beforeTokenTransfer function * refactor: remove upgradeTokenBuild and fix burn requirement * refactor: add isTokenController and move _clearTokenControllers to FleekAccessControl contract * refactor: remove localhost and wrong mumbai deployments * refactor: rename polygonMumbai to mumbai * refactor: remove twiced name on gitignore * chore: mumbai deployments * refactor: util script to get contract using hardhat defined network * chore: move forge-std as a submodule * chore: move forge-std as a submodule Co-authored-by: EmperorOrokuSaki <artie.eth@gmail.com> Co-authored-by: daltoncoder <71679972+daltoncoder@users.noreply.github.com> Co-authored-by: janison <jsonsivar@gmail.com>
This commit is contained in:
parent
315672243b
commit
94c364836e
|
|
@ -0,0 +1,3 @@
|
|||
API_URL=https://matic-mumbai.chainstacklabs.com/
|
||||
PRIVATE_KEY =0x
|
||||
POLYSCAN_API=
|
||||
|
|
@ -3,4 +3,12 @@ node_modules
|
|||
|
||||
# hardhat
|
||||
cache
|
||||
artifacts
|
||||
artifacts
|
||||
deployments/localhost
|
||||
|
||||
# NPM
|
||||
package-lock.json
|
||||
|
||||
# Foundry
|
||||
out
|
||||
forge-cache
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "lib/forge-std"]
|
||||
path = lib/forge-std
|
||||
url = https://github.com/foundry-rs/forge-std
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
import "../interfaces/IFleek.sol";
|
||||
import "./FleekBuilds.sol";
|
||||
import "./FleekAccessControl.sol";
|
||||
|
||||
abstract contract Fleek is IFleek, FleekBuilds {
|
||||
string name;
|
||||
string description;
|
||||
|
||||
constructor(string memory _name, string memory _description) {
|
||||
name = _name;
|
||||
description = _description;
|
||||
}
|
||||
|
||||
function setName(
|
||||
string calldata _name
|
||||
) external override requireController {
|
||||
name = _name;
|
||||
emit MetadataUpdated(name, description);
|
||||
}
|
||||
|
||||
function setDescription(
|
||||
string calldata _description
|
||||
) external override requireController {
|
||||
description = _description;
|
||||
emit MetadataUpdated(name, description);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,33 +2,59 @@
|
|||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
import "../interfaces/IFleekSite.sol";
|
||||
import "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
|
||||
abstract contract FleekAccessControl is AccessControl {
|
||||
bytes32 public constant OWNER_ROLE = keccak256("OWNER_ROLE");
|
||||
bytes32 public constant CONTROLLER_ROLE = keccak256("CONTROLLER_ROLE");
|
||||
bytes32 public constant COLLECTION_OWNER_ROLE =
|
||||
keccak256("COLLECTION_OWNER_ROLE");
|
||||
bytes32 public constant COLLECTION_CONTROLLER_ROLE =
|
||||
keccak256("COLLECTION_CONTROLLER_ROLE");
|
||||
|
||||
constructor() {
|
||||
_setRoleAdmin(OWNER_ROLE, DEFAULT_ADMIN_ROLE);
|
||||
_grantRole(OWNER_ROLE, msg.sender);
|
||||
_setRoleAdmin(COLLECTION_OWNER_ROLE, DEFAULT_ADMIN_ROLE);
|
||||
_grantRole(COLLECTION_OWNER_ROLE, msg.sender);
|
||||
}
|
||||
|
||||
modifier requireOwner() {
|
||||
modifier requireCollectionOwner() {
|
||||
require(
|
||||
hasRole(OWNER_ROLE, msg.sender),
|
||||
"FleekAccessControl: must have owner role"
|
||||
hasRole(COLLECTION_OWNER_ROLE, msg.sender),
|
||||
"FleekAccessControl: must have collection owner role"
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier requireController() {
|
||||
bool hasPermission = hasRole(CONTROLLER_ROLE, msg.sender) ||
|
||||
hasRole(DEFAULT_ADMIN_ROLE, msg.sender);
|
||||
modifier requireCollectionController() {
|
||||
require(
|
||||
hasPermission,
|
||||
"FleekAccessControl: caller is not a controller"
|
||||
hasRole(COLLECTION_OWNER_ROLE, msg.sender) ||
|
||||
hasRole(COLLECTION_CONTROLLER_ROLE, msg.sender),
|
||||
"FleekAccessControl: must have collection controller role"
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier requireTokenController(uint256 tokenId) {
|
||||
require(
|
||||
hasRole(_tokenRole(tokenId, "CONTROLLER"), msg.sender),
|
||||
"FleekAccessControl: must have token role"
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
function isTokenController(
|
||||
uint256 tokenId,
|
||||
address account
|
||||
) public view returns (bool) {
|
||||
return hasRole(_tokenRole(tokenId, "CONTROLLER"), account);
|
||||
}
|
||||
|
||||
function _tokenRole(
|
||||
uint256 tokenId,
|
||||
string memory role
|
||||
) internal pure returns (bytes32) {
|
||||
return keccak256(abi.encodePacked("TOKEN_", role, tokenId));
|
||||
}
|
||||
|
||||
function _clearTokenControllers(uint256 tokenId) internal {
|
||||
// TODO: Remove token controllers from AccessControl
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
import "../interfaces/IFleekBuilds.sol";
|
||||
import "./FleekAccessControl.sol";
|
||||
|
||||
abstract contract FleekBuilds is IFleekBuilds, FleekAccessControl {
|
||||
build[] public builds;
|
||||
|
||||
function update(
|
||||
build calldata _newBuild
|
||||
) external override requireController {
|
||||
builds.push(_newBuild);
|
||||
emit Upgraded(_newBuild);
|
||||
}
|
||||
|
||||
function getCurrentBuild() external view override returns (build memory) {
|
||||
return builds[builds.length - 1];
|
||||
}
|
||||
|
||||
function getBuildById(
|
||||
uint256 _buildId
|
||||
) external view override returns (build memory) {
|
||||
return builds[_buildId];
|
||||
}
|
||||
|
||||
function getBuilds() external view override returns (build[] memory) {
|
||||
return builds;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
||||
import "@openzeppelin/contracts/utils/Counters.sol";
|
||||
import "@openzeppelin/contracts/utils/Base64.sol";
|
||||
import "./FleekAccessControl.sol";
|
||||
|
||||
contract FleekERC721 is ERC721, FleekAccessControl {
|
||||
using Strings for uint256;
|
||||
using Counters for Counters.Counter;
|
||||
|
||||
event NewBuild(uint256 indexed token, string indexed commit_hash);
|
||||
event NewTokenName(uint256 indexed token, string indexed name);
|
||||
event NewTokenDescription(uint256 indexed token, string indexed description);
|
||||
event NewTokenImage(uint256 indexed token, string indexed image);
|
||||
event NewTokenExternalURL(uint256 indexed token, string indexed external_url);
|
||||
event NewTokenENS(uint256 indexed token, string indexed ENS);
|
||||
|
||||
struct Build {
|
||||
string commit_hash;
|
||||
string git_repository;
|
||||
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 {
|
||||
string name; // Name of the site
|
||||
string description; // Description about the site
|
||||
string image; // Preview Image IPFS Link
|
||||
string external_url; // Site URL
|
||||
string ENS; // ENS ID
|
||||
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
|
||||
}
|
||||
|
||||
Counters.Counter private _tokenIds;
|
||||
mapping(uint256 => App) private _apps;
|
||||
|
||||
constructor(
|
||||
string memory _name,
|
||||
string memory _symbol
|
||||
) ERC721(_name, _symbol) {}
|
||||
|
||||
modifier requireTokenOwner(uint256 tokenId) {
|
||||
require(
|
||||
msg.sender == ownerOf(tokenId),
|
||||
"FleekERC721: must be token owner"
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
function mint(
|
||||
address to,
|
||||
string memory name,
|
||||
string memory description,
|
||||
string memory image,
|
||||
string memory external_url,
|
||||
string memory ENS,
|
||||
string memory commit_hash,
|
||||
string memory git_repository,
|
||||
string memory author
|
||||
) public payable requireCollectionOwner returns (uint256) {
|
||||
uint256 tokenId = _tokenIds.current();
|
||||
_mint(to, tokenId);
|
||||
_tokenIds.increment();
|
||||
|
||||
App storage app = _apps[tokenId];
|
||||
app.name = name;
|
||||
app.description = description;
|
||||
app.image = image;
|
||||
app.external_url = external_url;
|
||||
app.ENS = ENS;
|
||||
|
||||
// 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.builds[0] = Build(commit_hash, git_repository, author);
|
||||
|
||||
return tokenId;
|
||||
}
|
||||
|
||||
function tokenURI(
|
||||
uint256 tokenId
|
||||
) public view virtual override returns (string memory) {
|
||||
_requireMinted(tokenId);
|
||||
address owner = ownerOf(tokenId);
|
||||
App storage app = _apps[tokenId];
|
||||
|
||||
bytes memory dataURI = abi.encodePacked(
|
||||
'{',
|
||||
'"name":"', app.name, '",',
|
||||
'"description":"', app.description, '",',
|
||||
'"owner":"', Strings.toHexString(uint160(owner), 20), '",',
|
||||
'"external_url":"', app.external_url, '",',
|
||||
'"image":"', app.image, '",',
|
||||
'"attributes": [',
|
||||
'{"trait_type": "ENS", "value":"', app.ENS,'"},',
|
||||
'{"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": "Author", "value":"', app.builds[app.current_build].author,'"},',
|
||||
'{"trait_type": "Version", "value":"', Strings.toString(app.current_build),'"}',
|
||||
']',
|
||||
'}'
|
||||
);
|
||||
|
||||
return string(abi.encodePacked(_baseURI(), Base64.encode((dataURI))));
|
||||
}
|
||||
|
||||
function addTokenController(
|
||||
uint256 tokenId,
|
||||
address controller
|
||||
) public requireTokenOwner(tokenId) {
|
||||
_requireMinted(tokenId);
|
||||
_grantRole(_tokenRole(tokenId, "CONTROLLER"), controller);
|
||||
}
|
||||
|
||||
function removeTokenController(
|
||||
uint256 tokenId,
|
||||
address controller
|
||||
) public requireTokenOwner(tokenId) {
|
||||
_requireMinted(tokenId);
|
||||
_revokeRole(_tokenRole(tokenId, "CONTROLLER"), controller);
|
||||
}
|
||||
|
||||
function supportsInterface(
|
||||
bytes4 interfaceId
|
||||
) public view virtual override(ERC721, AccessControl) returns (bool) {
|
||||
return super.supportsInterface(interfaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Override of _beforeTokenTransfer of ERC721.
|
||||
* Here it needs to update the token controller roles for mint, burn and transfer.
|
||||
* IMPORTANT: The function for clearing token controllers is not implemented yet.
|
||||
*/
|
||||
function _beforeTokenTransfer(
|
||||
address from,
|
||||
address to,
|
||||
uint256 tokenId,
|
||||
uint256 batchSize
|
||||
) internal virtual override {
|
||||
if (from != address(0) && to != address(0)) {
|
||||
// Transfer
|
||||
_clearTokenControllers(tokenId);
|
||||
_grantRole(_tokenRole(tokenId, "CONTROLLER"), to);
|
||||
} else if (from == address(0)) {
|
||||
// Mint
|
||||
_grantRole(_tokenRole(tokenId, "CONTROLLER"), to);
|
||||
} else if (to == address(0)) {
|
||||
// Burn
|
||||
_clearTokenControllers(tokenId);
|
||||
}
|
||||
super._beforeTokenTransfer(from, to, tokenId, batchSize);
|
||||
}
|
||||
|
||||
function _baseURI() internal view virtual override returns (string memory) {
|
||||
return "data:application/json;base64,";
|
||||
}
|
||||
|
||||
function setTokenExternalURL(
|
||||
uint256 tokenId,
|
||||
string memory _tokenExternalURL
|
||||
) public virtual requireTokenController(tokenId) {
|
||||
_requireMinted(tokenId);
|
||||
_apps[tokenId].external_url = _tokenExternalURL;
|
||||
emit NewTokenExternalURL(tokenId, _tokenExternalURL);
|
||||
}
|
||||
|
||||
function setTokenENS(
|
||||
uint256 tokenId,
|
||||
string memory _tokenENS
|
||||
) public virtual requireTokenController(tokenId) {
|
||||
_requireMinted(tokenId);
|
||||
_apps[tokenId].ENS = _tokenENS;
|
||||
emit NewTokenENS(tokenId, _tokenENS);
|
||||
}
|
||||
|
||||
function setTokenName(
|
||||
uint256 tokenId,
|
||||
string memory _tokenName
|
||||
) public virtual requireTokenController(tokenId) {
|
||||
_requireMinted(tokenId);
|
||||
_apps[tokenId].name = _tokenName;
|
||||
emit NewTokenName(tokenId, _tokenName);
|
||||
}
|
||||
|
||||
function setTokenDescription(
|
||||
uint256 tokenId,
|
||||
string memory _tokenDescription
|
||||
) public virtual requireTokenController(tokenId) {
|
||||
_requireMinted(tokenId);
|
||||
_apps[tokenId].description = _tokenDescription;
|
||||
emit NewTokenDescription(tokenId, _tokenDescription);
|
||||
}
|
||||
|
||||
function setTokenImage(
|
||||
uint256 tokenId,
|
||||
string memory _tokenImage
|
||||
) public virtual requireTokenController(tokenId) {
|
||||
_requireMinted(tokenId);
|
||||
_apps[tokenId].image = _tokenImage;
|
||||
emit NewTokenImage(tokenId, _tokenImage);
|
||||
}
|
||||
|
||||
function setTokenBuild(
|
||||
uint256 tokenId,
|
||||
string memory _commit_hash,
|
||||
string memory _git_repository,
|
||||
string memory _author
|
||||
) public virtual requireTokenController(tokenId) {
|
||||
_requireMinted(tokenId);
|
||||
_apps[tokenId].builds[++_apps[tokenId].current_build] = Build(
|
||||
_commit_hash,
|
||||
_git_repository,
|
||||
_author
|
||||
);
|
||||
emit NewBuild(tokenId, _commit_hash);
|
||||
}
|
||||
|
||||
function burn(
|
||||
uint256 tokenId
|
||||
) public virtual requireTokenOwner(tokenId) {
|
||||
super._burn(tokenId);
|
||||
|
||||
if (bytes(_apps[tokenId].external_url).length != 0) {
|
||||
delete _apps[tokenId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
import "./Fleek.sol";
|
||||
import "../interfaces/IFleekSite.sol";
|
||||
|
||||
contract FleekSite is IFleekSite, Fleek {
|
||||
string thumbnail;
|
||||
string external_url;
|
||||
|
||||
constructor(
|
||||
string memory _name,
|
||||
string memory _description,
|
||||
string memory _thumbnail,
|
||||
string memory _external_url
|
||||
) Fleek(_name, _description) {
|
||||
thumbnail = _thumbnail;
|
||||
external_url = _external_url;
|
||||
}
|
||||
|
||||
function setThumbnail(
|
||||
string calldata _thumbnail
|
||||
) external override requireController {
|
||||
thumbnail = _thumbnail;
|
||||
emit SiteMetadataUpdated(name, description, thumbnail, external_url);
|
||||
}
|
||||
|
||||
function setExternalUrl(
|
||||
string calldata _external_url
|
||||
) external override requireController {
|
||||
external_url = _external_url;
|
||||
emit SiteMetadataUpdated(name, description, thumbnail, external_url);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
|
||||
import "@openzeppelin/contracts/utils/Counters.sol";
|
||||
import "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
import "../interfaces/ISitesNFTs.sol";
|
||||
|
||||
contract SitesNFTs is ISitesNFTs, ERC721URIStorage, AccessControl {
|
||||
using Counters for Counters.Counter;
|
||||
Counters.Counter private _tokenIds;
|
||||
string private baseURI;
|
||||
|
||||
bytes32 public constant MINTER_ROLE =
|
||||
0x4d494e5445525f524f4c45000000000000000000000000000000000000000000; // "MINTER_ROLE"
|
||||
|
||||
modifier canMint() {
|
||||
bool isMinterOrAdmin = hasRole(MINTER_ROLE, msg.sender) ||
|
||||
hasRole(DEFAULT_ADMIN_ROLE, msg.sender);
|
||||
require(isMinterOrAdmin, "Caller has no permission to mint.");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier canChangeBaseURI() {
|
||||
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender));
|
||||
_;
|
||||
}
|
||||
|
||||
constructor(string memory name, string memory symbol) ERC721(name, symbol) {
|
||||
baseURI = "data:application/json;base64,";
|
||||
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
|
||||
}
|
||||
|
||||
function mint(
|
||||
string memory base64EncodedMetadata,
|
||||
address account
|
||||
) public override canMint returns (uint256) {
|
||||
uint256 newItemId = _tokenIds.current();
|
||||
_safeMint(account, newItemId);
|
||||
_setTokenURI(newItemId, base64EncodedMetadata);
|
||||
|
||||
_tokenIds.increment();
|
||||
return newItemId;
|
||||
}
|
||||
|
||||
function updateTokenURI(
|
||||
address tokenHolderAddress,
|
||||
uint256 tokenId,
|
||||
string memory base64EncodedMetadata
|
||||
) public override canMint {
|
||||
address tokenOwner = ownerOf(tokenId);
|
||||
require(
|
||||
tokenOwner == tokenHolderAddress,
|
||||
"Address does not own provided tokenId"
|
||||
);
|
||||
_setTokenURI(tokenId, base64EncodedMetadata);
|
||||
}
|
||||
|
||||
function supportsInterface(
|
||||
bytes4 interfaceId
|
||||
)
|
||||
public
|
||||
view
|
||||
virtual
|
||||
override(IERC165, ERC721, AccessControl)
|
||||
returns (bool)
|
||||
{
|
||||
return super.supportsInterface(interfaceId);
|
||||
}
|
||||
|
||||
function setBaseURI(
|
||||
string memory _newBbaseURI
|
||||
) public override canChangeBaseURI returns (string memory) {
|
||||
baseURI = _newBbaseURI;
|
||||
return baseURI;
|
||||
}
|
||||
|
||||
function getCurrentTokenId() public view override returns (uint256) {
|
||||
return _tokenIds.current();
|
||||
}
|
||||
|
||||
function _baseURI() internal view override returns (string memory) {
|
||||
return baseURI;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
module.exports = async ({ getNamedAccounts, deployments }) => {
|
||||
const { deploy, log } = deployments;
|
||||
const namedAccounts = await getNamedAccounts();
|
||||
const { deployer } = namedAccounts;
|
||||
|
||||
const deployResult = await deploy('FleekERC721', {
|
||||
from: deployer,
|
||||
args: ['FleekSites', 'FLKSITE'],
|
||||
});
|
||||
if (deployResult.newlyDeployed) {
|
||||
log(
|
||||
`contract FleekSites deployed at ${deployResult.address} using ${deployResult.receipt.gasUsed} gas`
|
||||
);
|
||||
} else {
|
||||
log(`using pre-existing contract FleekSites at ${deployResult.address}`);
|
||||
}
|
||||
};
|
||||
//You can put an array of tags below. Tags can be anything and say when a this script should be run. So you can write different scripts for local, prod or other deploys
|
||||
//For example when you run 'npx hardhat --network hardhat deploy --tags local' hardhat will run all deploy scripts that have the tag local, could be multiple dif scripts
|
||||
module.exports.tags = ['local'];
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
module.exports = async ({ getNamedAccounts, deployments }) => {
|
||||
const { deploy, log } = deployments;
|
||||
const namedAccounts = await getNamedAccounts();
|
||||
const { privateKey } = namedAccounts;
|
||||
|
||||
const deployResult = await deploy('FleekERC721', {
|
||||
from: privateKey,
|
||||
args: ['FleekSites', 'FLKSITE'],
|
||||
});
|
||||
if (deployResult.newlyDeployed) {
|
||||
log(
|
||||
`contract FleekSites deployed at ${deployResult.address} using ${deployResult.receipt.gasUsed} gas`
|
||||
);
|
||||
} else {
|
||||
log(`using pre-existing contract FleekSites at ${deployResult.address}`);
|
||||
}
|
||||
};
|
||||
//You can put an array of tags below. Tags can be anything and say when a this script should be run. So you can write different scripts for local, prod or other deploys
|
||||
//For example when you run 'npx hardhat --network hardhat deploy --tags local' hardhat will run all deploy scripts that have the tag local, could be multiple dif scripts
|
||||
module.exports.tags = ['mumbai'];
|
||||
|
|
@ -0,0 +1 @@
|
|||
80001
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,6 @@
|
|||
[profile.default]
|
||||
src = 'contracts'
|
||||
out = 'out'
|
||||
libs = ['node_modules', 'lib']
|
||||
test = 'test/foundry'
|
||||
cache_path = 'forge-cache'
|
||||
|
|
@ -1,122 +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 POLYGON_MAINNET_RPC_URL =
|
||||
process.env.POLYGON_MAINNET_RPC_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,
|
||||
// },
|
||||
// poligonMumbai: {
|
||||
// url: POLYGON_MUMBAI_RPC_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: POLYGONSCAN_API_KEY,
|
||||
},
|
||||
},
|
||||
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: 0, // here this will by default take the first account as deployer
|
||||
1: 0, // similarly on mainnet it will take the first account as deployer. Note though that depending on how hardhat network are configured, the account 0 on one network can be different than on another
|
||||
},
|
||||
player: {
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
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,
|
||||
},
|
||||
mumbai: {
|
||||
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;
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
import "./IFleekBuilds.sol";
|
||||
import "@openzeppelin/contracts/access/IAccessControl.sol";
|
||||
|
||||
interface IFleek is IFleekBuilds, IAccessControl {
|
||||
event MetadataUpdated(string name, string description);
|
||||
|
||||
function setName(string calldata _name) external;
|
||||
|
||||
function setDescription(string calldata _description) external;
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
interface IFleekBuilds {
|
||||
struct build {
|
||||
string _uri;
|
||||
string _hash;
|
||||
string _repo;
|
||||
string _repository;
|
||||
}
|
||||
|
||||
event InitialVersionDeploy();
|
||||
|
||||
event Upgraded(build _build);
|
||||
|
||||
function update(build calldata _newBuild) external;
|
||||
|
||||
function getCurrentBuild() external view returns (build memory);
|
||||
|
||||
function getBuildById(
|
||||
uint256 _buildId
|
||||
) external view returns (build memory);
|
||||
|
||||
function getBuilds() external view returns (build[] memory);
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
import "./IFleek.sol";
|
||||
|
||||
interface IFleekSite is IFleek {
|
||||
event SiteMetadataUpdated(
|
||||
string name,
|
||||
string description,
|
||||
string thumbnail,
|
||||
string external_url
|
||||
);
|
||||
|
||||
function setThumbnail(string calldata _thumbnail) external;
|
||||
|
||||
function setExternalUrl(string calldata _external_url) external;
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
import "@openzeppelin/contracts/interfaces/IERC721.sol";
|
||||
import "@openzeppelin/contracts/access/IAccessControl.sol";
|
||||
|
||||
/// @title SitesNFTs - A contract for managing sites NFTs
|
||||
/// @dev See
|
||||
interface ISitesNFTs is IERC721 {
|
||||
function mint(
|
||||
string memory base64EncodedMetadata,
|
||||
address account
|
||||
) external returns (uint256);
|
||||
|
||||
function updateTokenURI(
|
||||
address tokenHolderAddress,
|
||||
uint256 tokenId,
|
||||
string memory base64EncodedMetadata
|
||||
) external;
|
||||
|
||||
function setBaseURI(
|
||||
string memory _newBbaseURI
|
||||
) external returns (string memory);
|
||||
|
||||
function getCurrentTokenId() external view returns (uint256);
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit cd7d533f9a0ee0ec02ad81e0a8f262bc4203c653
|
||||
35
package.json
35
package.json
|
|
@ -1,37 +1,50 @@
|
|||
{
|
||||
"name": "sites_nfts",
|
||||
"version": "1.0.0",
|
||||
"name": "fleek_contracts",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "hardhat test",
|
||||
"format": "prettier --write \"./**/*.{js,ts,sol}\""
|
||||
"test": "hardhat test && forge test --via-ir",
|
||||
"test:foundry": "forge test --via-ir",
|
||||
"test:hardhat": "hardhat test",
|
||||
"format": "prettier --write \"./**/*.{js,ts,sol}\"",
|
||||
"node:hardhat": "hardhat node --tags local",
|
||||
"deploy:local": "hardhat deploy --tags local",
|
||||
"deploy:mumbai": "hardhat deploy --tags mumbai --network mumbai",
|
||||
"compile": "hardhat compile"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/FleekHQ/sites_nfts.git"
|
||||
"url": "git+https://github.com/FleekHQ/contracts.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/FleekHQ/sites_nfts/issues"
|
||||
"url": "https://github.com/FleekHQ/contracts/issues"
|
||||
},
|
||||
"homepage": "https://github.com/FleekHQ/sites_nfts#readme",
|
||||
"homepage": "https://github.com/FleekHQ/contracts#readme",
|
||||
"devDependencies": {
|
||||
"@nomicfoundation/hardhat-chai-matchers": "^1.0.5",
|
||||
"@nomicfoundation/hardhat-network-helpers": "^1.0.7",
|
||||
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
|
||||
"@nomiclabs/hardhat-ethers": "^2.1.1",
|
||||
"@nomiclabs/hardhat-ethers": "^2.2.1",
|
||||
"@nomiclabs/hardhat-etherscan": "^3.1.0",
|
||||
"@nomiclabs/hardhat-waffle": "^2.0.3",
|
||||
"@nomiclabs/hardhat-web3": "^2.0.0",
|
||||
"@openzeppelin/contracts": "^4.7.3",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"chai": "^4.3.6",
|
||||
"dotenv": "^16.0.2",
|
||||
"ethereum-waffle": "^3.4.4",
|
||||
"ethers": "^5.7.2",
|
||||
"hardhat": "^2.11.2",
|
||||
"hardhat-contract-sizer": "^2.6.1",
|
||||
"hardhat-deploy": "^0.11.15",
|
||||
"hardhat-gas-reporter": "^1.0.9",
|
||||
"minimist": "^1.2.7",
|
||||
"prettier": "^2.7.1",
|
||||
"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,2 @@
|
|||
ds-test/=lib/forge-std/lib/ds-test/src/
|
||||
forge-std/=lib/forge-std/src/
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
async function main() {
|
||||
const [deployer] = await ethers.getSigners();
|
||||
|
||||
console.log('Deploying contracts with the account:', deployer.address);
|
||||
|
||||
console.log('Account balance:', (await deployer.getBalance()).toString());
|
||||
|
||||
const SitesNFTs = await ethers.getContractFactory('SitesNFTs');
|
||||
const sitesNFTs = await SitesNFTs.deploy('Sites NFTs', 'SNFT');
|
||||
|
||||
console.log('SitesNFTs address:', sitesNFTs.address);
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// npx hardhat run scripts/mint.js --network mumbai
|
||||
const { getContract } = require('./util');
|
||||
|
||||
// TODO: make this arguments
|
||||
const params = [
|
||||
'0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049', // to
|
||||
'Fleek App', // name
|
||||
'Description', // description
|
||||
'https://fleek.network/fleek-network-logo-minimal.png', // image
|
||||
'https://fleek.co/', // external url
|
||||
'fleek.eth', // ens
|
||||
'6ea6ad16c46ae85faced7e50555ff7368422f57', // commit hash
|
||||
'https://github.com/org/repo', // repo
|
||||
'fleek', // author
|
||||
];
|
||||
|
||||
(async () => {
|
||||
const contract = getContract('FleekERC721');
|
||||
|
||||
const transaction = await contract.mint(...params);
|
||||
|
||||
console.log('Response: ', transaction);
|
||||
})();
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// npx hardhat run scripts/tokenURI.js --network mumbai
|
||||
const { getContract } = require('./util');
|
||||
|
||||
// TODO: make this arguments
|
||||
const tokenId = 1;
|
||||
|
||||
(async () => {
|
||||
const contract = await getContract('FleekERC721');
|
||||
|
||||
const transaction = await contract.tokenURI(tokenId);
|
||||
|
||||
const parsed = JSON.parse(
|
||||
Buffer.from(transaction.slice(29), 'base64').toString('utf-8')
|
||||
);
|
||||
|
||||
console.log('Response: ', parsed);
|
||||
})();
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// npx hardhat run scripts/upgrade.js --network mumbai
|
||||
const { getContract } = require('./util');
|
||||
|
||||
// TODO: make this arguments
|
||||
const params = [
|
||||
1, // tokenId
|
||||
'97e7908f70f0862d753c66689ff09e70caa43df2', // commit hash
|
||||
'https://github.com/org/new-repo', // repo
|
||||
'new-author', // author
|
||||
];
|
||||
|
||||
(async () => {
|
||||
const contract = await getContract('FleekERC721');
|
||||
|
||||
const transaction = await contract.setTokenBuild(...params);
|
||||
|
||||
console.log('Response: ', transaction);
|
||||
})();
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
module.exports.getContract = async function (contractName) {
|
||||
const {
|
||||
address,
|
||||
} = require(`../deployments/${hre.network.name}/${contractName}.json`);
|
||||
|
||||
return hre.ethers.getContractAt(contractName, address);
|
||||
};
|
||||
|
|
@ -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,50 +0,0 @@
|
|||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('ethereum-waffle');
|
||||
const { ethers } = require('hardhat');
|
||||
const hre = require('hardhat');
|
||||
|
||||
describe('FleekSite contract', function () {
|
||||
//TODO check values are setted right on the contract
|
||||
const _name = 'Fleek Site';
|
||||
const _description = 'Fleek Site Description';
|
||||
const _thumbnail = 'https://fleek.co';
|
||||
const _externalUrl = 'https://fleek.co';
|
||||
|
||||
async function deploy() {
|
||||
const [owner] = await hre.ethers.getSigners();
|
||||
|
||||
const FleekSite = await hre.ethers.getContractFactory('FleekSite');
|
||||
|
||||
const hardhatFleekSite = await FleekSite.deploy(
|
||||
'Fleek Site',
|
||||
'Fleek Site Description',
|
||||
'https://fleek.co',
|
||||
'https://fleek.co'
|
||||
);
|
||||
|
||||
return { owner, hardhatFleekSite };
|
||||
}
|
||||
describe('Deployment', () => {
|
||||
it('Deploy FleekSit contract with name Fleek Site and builds[] should be 0', async () => {
|
||||
const { hardhatFleekSite } = await loadFixture(deploy);
|
||||
|
||||
const currentBuilds = await hardhatFleekSite.getBuilds();
|
||||
expect(currentBuilds.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('Deployment should assign to OWNER_ROLE the DEFAULT_ADMIN_ROLE', async () => {
|
||||
const { hardhatFleekSite } = await loadFixture(deploy);
|
||||
|
||||
const OWNER_ROLE = 'OWNER_ROLE';
|
||||
const DEFAULT_ADMIN_ROLE_STRING = '';
|
||||
|
||||
const role = await hardhatFleekSite.getRoleAdmin(
|
||||
ethers.utils.formatBytes32String(OWNER_ROLE)
|
||||
);
|
||||
|
||||
expect(role).to.equal(
|
||||
ethers.utils.formatBytes32String(DEFAULT_ADMIN_ROLE_STRING)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,262 +0,0 @@
|
|||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('ethereum-waffle');
|
||||
|
||||
describe('SitesNFTs contract', function () {
|
||||
const name = 'Sites NFTs';
|
||||
const symbol = 'SNFT';
|
||||
|
||||
async function deploy() {
|
||||
const [owner, address1, address2] = await hre.ethers.getSigners();
|
||||
|
||||
const SitesNFTs = await hre.ethers.getContractFactory('SitesNFTs');
|
||||
|
||||
const hardhatSitesNFTs = await SitesNFTs.deploy(name, symbol);
|
||||
|
||||
return { owner, address1, address2, hardhatSitesNFTs };
|
||||
}
|
||||
|
||||
describe('Deployment', () => {
|
||||
it('Deployment should assign the name and the symbol of the ERC721 contract', async () => {
|
||||
const { hardhatSitesNFTs } = await loadFixture(deploy);
|
||||
|
||||
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 { hardhatSitesNFTs, owner } = await loadFixture(deploy);
|
||||
|
||||
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 { hardhatSitesNFTs } = await loadFixture(deploy);
|
||||
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 { hardhatSitesNFTs, address1 } = await loadFixture(deploy);
|
||||
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 { hardhatSitesNFTs, owner } = await loadFixture(deploy);
|
||||
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 { hardhatSitesNFTs, address1 } = await loadFixture(deploy);
|
||||
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 { hardhatSitesNFTs, owner, address1 } = await loadFixture(deploy);
|
||||
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 { hardhatSitesNFTs, address1 } = await loadFixture(deploy);
|
||||
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 { hardhatSitesNFTs, address1, address2 } = await loadFixture(
|
||||
deploy
|
||||
);
|
||||
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 { hardhatSitesNFTs, address1 } = await loadFixture(deploy);
|
||||
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
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
pragma solidity ^0.8.7;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "../../contracts/FleekERC721.sol";
|
||||
|
||||
contract ContractBTest is Test {
|
||||
FleekERC721 fleekContract;
|
||||
uint256 testNumber;
|
||||
|
||||
function setUp() public {
|
||||
fleekContract = new FleekERC721('Test Contract', 'FLKAPS');
|
||||
}
|
||||
|
||||
function testName() public {
|
||||
assertEq(fleekContract.name(), 'Test Contract'));
|
||||
}
|
||||
|
||||
function testSymbol() public {
|
||||
assertEq(fleekContract.symbol(), 'FLKAPS'));
|
||||
}
|
||||
|
||||
function testMint() public {
|
||||
}
|
||||
|
||||
function testTokenURI() public {
|
||||
}
|
||||
|
||||
function testBurn() public {
|
||||
}
|
||||
|
||||
function testSetTokenName() public {
|
||||
}
|
||||
|
||||
function testSetTokenDescription() public {
|
||||
}
|
||||
|
||||
function testSetTokenImage() public {
|
||||
}
|
||||
|
||||
function testSetTokenExternalURL() public {
|
||||
}
|
||||
|
||||
function testSetTokenBuild() public {
|
||||
}
|
||||
|
||||
function testUpgradeTokenBuild() public {
|
||||
}
|
||||
|
||||
function testSetTokenENS() public {
|
||||
}
|
||||
|
||||
function testAddTokenController() public {
|
||||
}
|
||||
|
||||
function testRemoveTokenController() public {
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue