feat: contracts add verified field and allow verifier (#184)
* feat: add token verified flag * feat: add verified flag on tokenURI * feat: add isTokenVerified function * test: add foundry tests for token verified * test: add hardhat tests for token verified
This commit is contained in:
parent
73d48a0e4f
commit
779cf3bb14
|
|
@ -51,6 +51,7 @@ contract FleekERC721 is
|
||||||
uint256 private _appIds;
|
uint256 private _appIds;
|
||||||
mapping(uint256 => Token) private _apps;
|
mapping(uint256 => Token) private _apps;
|
||||||
mapping(uint256 => address) private _tokenVerifier;
|
mapping(uint256 => address) private _tokenVerifier;
|
||||||
|
mapping(uint256 => bool) private _tokenVerified;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
|
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
|
||||||
|
|
@ -133,6 +134,7 @@ contract FleekERC721 is
|
||||||
);
|
);
|
||||||
|
|
||||||
_tokenVerifier[tokenId] = verifier;
|
_tokenVerifier[tokenId] = verifier;
|
||||||
|
_tokenVerified[tokenId] = false;
|
||||||
_setAccessPointAutoApproval(tokenId, accessPointAutoApproval);
|
_setAccessPointAutoApproval(tokenId, accessPointAutoApproval);
|
||||||
|
|
||||||
return tokenId;
|
return tokenId;
|
||||||
|
|
@ -152,9 +154,10 @@ contract FleekERC721 is
|
||||||
_requireMinted(tokenId);
|
_requireMinted(tokenId);
|
||||||
address owner = ownerOf(tokenId);
|
address owner = ownerOf(tokenId);
|
||||||
bool accessPointAutoApproval = _getAccessPointAutoApproval(tokenId);
|
bool accessPointAutoApproval = _getAccessPointAutoApproval(tokenId);
|
||||||
|
bool verified = _tokenVerified[tokenId];
|
||||||
Token storage app = _apps[tokenId];
|
Token storage app = _apps[tokenId];
|
||||||
|
|
||||||
return string(abi.encodePacked(_baseURI(), app.toString(owner, accessPointAutoApproval).toBase64()));
|
return string(abi.encodePacked(_baseURI(), app.toString(owner, accessPointAutoApproval, verified).toBase64()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -437,6 +440,40 @@ contract FleekERC721 is
|
||||||
return _tokenVerifier[tokenId];
|
return _tokenVerifier[tokenId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Sets the verification status of a token.
|
||||||
|
*
|
||||||
|
* May emit a {MetadataUpdate} event.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the tokenId must be minted and valid.
|
||||||
|
* - the sender must be the token verifier.
|
||||||
|
* - the sender must have `CollectionRoles.Verifier` role.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function setTokenVerified(
|
||||||
|
uint256 tokenId,
|
||||||
|
bool verified
|
||||||
|
) public requireCollectionRole(CollectionRoles.Verifier) requireTokenVerifier(tokenId) {
|
||||||
|
_requireMinted(tokenId);
|
||||||
|
_tokenVerified[tokenId] = verified;
|
||||||
|
emit MetadataUpdate(tokenId, "verified", verified, msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns the verification status of a token.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - the tokenId must be minted and valid.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function isTokenVerified(uint256 tokenId) public view returns (bool) {
|
||||||
|
_requireMinted(tokenId);
|
||||||
|
return _tokenVerified[tokenId];
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////
|
||||||
ACCESS POINTS
|
ACCESS POINTS
|
||||||
//////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////*/
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,8 @@ library FleekStrings {
|
||||||
function toString(
|
function toString(
|
||||||
IERCX.Token storage app,
|
IERCX.Token storage app,
|
||||||
address owner,
|
address owner,
|
||||||
bool accessPointAutoApproval
|
bool accessPointAutoApproval,
|
||||||
|
bool verified
|
||||||
) internal view returns (string memory) {
|
) internal view returns (string memory) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return string(abi.encodePacked(
|
return string(abi.encodePacked(
|
||||||
|
|
@ -47,6 +48,7 @@ library FleekStrings {
|
||||||
'"external_url":"', app.externalURL, '",',
|
'"external_url":"', app.externalURL, '",',
|
||||||
'"image":"', FleekSVG.generateBase64(app.name, app.ENS, app.logo, app.color.toColorString()), '",',
|
'"image":"', FleekSVG.generateBase64(app.name, app.ENS, app.logo, app.color.toColorString()), '",',
|
||||||
'"access_point_auto_approval":', accessPointAutoApproval.toString(),',',
|
'"access_point_auto_approval":', accessPointAutoApproval.toString(),',',
|
||||||
|
'"verified":',verified.toString(),',',
|
||||||
'"attributes": [',
|
'"attributes": [',
|
||||||
'{"trait_type": "ENS", "value":"', app.ENS,'"},',
|
'{"trait_type": "ENS", "value":"', app.ENS,'"},',
|
||||||
'{"trait_type": "Commit Hash", "value":"', app.builds[app.currentBuild].commitHash,'"},',
|
'{"trait_type": "Commit Hash", "value":"', app.builds[app.currentBuild].commitHash,'"},',
|
||||||
|
|
|
||||||
|
|
@ -594,6 +594,61 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
||||||
CuT.setPausable(true);
|
CuT.setPausable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_setTokenVerifier() public {
|
||||||
|
address otherVerifier = address(0x1234);
|
||||||
|
CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Verifier, otherVerifier);
|
||||||
|
|
||||||
|
// ColletionOwner
|
||||||
|
vm.prank(collectionOwner);
|
||||||
|
expectRevertWithMustBeTokenOwner(tokenId);
|
||||||
|
CuT.setTokenVerifier(tokenId, otherVerifier);
|
||||||
|
|
||||||
|
// CollectionVerifier
|
||||||
|
vm.prank(collectionVerifier);
|
||||||
|
expectRevertWithMustBeTokenOwner(tokenId);
|
||||||
|
CuT.setTokenVerifier(tokenId, otherVerifier);
|
||||||
|
|
||||||
|
// TokenOwner
|
||||||
|
vm.prank(tokenOwner);
|
||||||
|
CuT.setTokenVerifier(tokenId, otherVerifier);
|
||||||
|
|
||||||
|
// TokenController
|
||||||
|
vm.prank(tokenController);
|
||||||
|
expectRevertWithMustBeTokenOwner(tokenId);
|
||||||
|
CuT.setTokenVerifier(tokenId, collectionVerifier);
|
||||||
|
|
||||||
|
// AnyAddress
|
||||||
|
vm.prank(anyAddress);
|
||||||
|
expectRevertWithMustBeTokenOwner(tokenId);
|
||||||
|
CuT.setTokenVerifier(tokenId, collectionVerifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_setTokenVerified() public {
|
||||||
|
// CollectionOwner
|
||||||
|
vm.prank(collectionOwner);
|
||||||
|
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||||
|
CuT.setTokenVerified(tokenId, true);
|
||||||
|
|
||||||
|
// CollectionVerifier
|
||||||
|
vm.prank(collectionVerifier);
|
||||||
|
CuT.setTokenVerified(tokenId, true);
|
||||||
|
|
||||||
|
// TokenOwner
|
||||||
|
vm.prank(tokenOwner);
|
||||||
|
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||||
|
CuT.setTokenVerified(tokenId, false);
|
||||||
|
|
||||||
|
// TokenController
|
||||||
|
vm.prank(tokenController);
|
||||||
|
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||||
|
CuT.setTokenVerified(tokenId, false);
|
||||||
|
|
||||||
|
// AnyAddress
|
||||||
|
vm.prank(anyAddress);
|
||||||
|
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||||
|
CuT.setTokenVerified(tokenId, false);
|
||||||
|
}
|
||||||
|
|
||||||
function test_cannotHaveLessThanOneCollectionOwner() public {
|
function test_cannotHaveLessThanOneCollectionOwner() public {
|
||||||
CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionOwner);
|
CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionOwner);
|
||||||
expectRevertWithMustHaveAtLeastOneOwner();
|
expectRevertWithMustHaveAtLeastOneOwner();
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,6 @@ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
|
||||||
contract APConstants is Test {
|
contract APConstants is Test {
|
||||||
using Strings for address;
|
using Strings for address;
|
||||||
|
|
||||||
function expectRevertWithMustBeTokenVerifier(uint256 tokenId) public {
|
|
||||||
vm.expectRevert(abi.encodeWithSelector(MustBeTokenVerifier.selector, tokenId));
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertAccessPointJSON(
|
function assertAccessPointJSON(
|
||||||
string memory accessPointName,
|
string memory accessPointName,
|
||||||
string memory _tokenId,
|
string memory _tokenId,
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -38,6 +38,10 @@ abstract contract Test_FleekERC721_Assertions is Test {
|
||||||
function expectRevertWithInvalidTokenId() public {
|
function expectRevertWithInvalidTokenId() public {
|
||||||
vm.expectRevert("ERC721: invalid token ID");
|
vm.expectRevert("ERC721: invalid token ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function expectRevertWithMustBeTokenVerifier(uint256 tokenId) public {
|
||||||
|
vm.expectRevert(abi.encodeWithSelector(MustBeTokenVerifier.selector, tokenId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract contract Test_FleekERC721_Base is Test, Test_FleekERC721_Assertions {
|
abstract contract Test_FleekERC721_Base is Test, Test_FleekERC721_Assertions {
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,49 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.8.17;
|
||||||
|
|
||||||
|
import "./TestBase.sol";
|
||||||
|
import {TestConstants} from "./Constants.sol";
|
||||||
|
import {FleekAccessControl} from "../../../contracts/FleekAccessControl.sol";
|
||||||
|
|
||||||
|
contract Test_FleekERC721_Verified is Test_FleekERC721_Base {
|
||||||
|
uint256 internal tokenId;
|
||||||
|
|
||||||
|
function setUp() public {
|
||||||
|
baseSetUp();
|
||||||
|
tokenId = mintDefault(deployer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_shouldNotBeVerifiedAfterMint() public {
|
||||||
|
assertFalse(CuT.isTokenVerified(tokenId));
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_shouldVerifyToken() public {
|
||||||
|
CuT.setTokenVerified(tokenId, true);
|
||||||
|
assertTrue(CuT.isTokenVerified(tokenId));
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_verifyAndUnverify() public {
|
||||||
|
CuT.setTokenVerified(tokenId, true);
|
||||||
|
assertTrue(CuT.isTokenVerified(tokenId));
|
||||||
|
CuT.setTokenVerified(tokenId, false);
|
||||||
|
assertFalse(CuT.isTokenVerified(tokenId));
|
||||||
|
}
|
||||||
|
|
||||||
|
function testFuzz_shouldNotAllowVerifyIfHasNotVerifierRole(address verifier) public {
|
||||||
|
vm.assume(!CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Verifier, verifier));
|
||||||
|
|
||||||
|
vm.prank(verifier);
|
||||||
|
expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
|
||||||
|
CuT.setTokenVerified(tokenId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testFuzz_shouldNotAllowVerifyIfIsNotTokenVerifier(address verifier) public {
|
||||||
|
vm.assume(CuT.getTokenVerifier(tokenId) != verifier);
|
||||||
|
CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Verifier, verifier);
|
||||||
|
|
||||||
|
vm.prank(verifier);
|
||||||
|
expectRevertWithMustBeTokenVerifier(tokenId);
|
||||||
|
CuT.setTokenVerified(tokenId, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ export const Errors = Object.freeze({
|
||||||
AccessPointAlreadyExists: 'AccessPointAlreadyExists',
|
AccessPointAlreadyExists: 'AccessPointAlreadyExists',
|
||||||
AccessPointScoreCannotBeLower: 'AccessPointScoreCannotBeLower',
|
AccessPointScoreCannotBeLower: 'AccessPointScoreCannotBeLower',
|
||||||
MustBeAccessPointOwner: 'MustBeAccessPointOwner',
|
MustBeAccessPointOwner: 'MustBeAccessPointOwner',
|
||||||
|
MustBeTokenVerifier: 'MustBeTokenVerifier',
|
||||||
MustHaveCollectionRole: 'MustHaveCollectionRole',
|
MustHaveCollectionRole: 'MustHaveCollectionRole',
|
||||||
MustHaveTokenRole: 'MustHaveTokenRole',
|
MustHaveTokenRole: 'MustHaveTokenRole',
|
||||||
MustHaveAtLeastOneOwner: 'MustHaveAtLeastOneOwner',
|
MustHaveAtLeastOneOwner: 'MustHaveAtLeastOneOwner',
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ describe('FleekERC721.TokenURI', () => {
|
||||||
image: TestConstants.ResultantImage.Default,
|
image: TestConstants.ResultantImage.Default,
|
||||||
external_url: TestConstants.MintParams.externalUrl,
|
external_url: TestConstants.MintParams.externalUrl,
|
||||||
access_point_auto_approval: false,
|
access_point_auto_approval: false,
|
||||||
|
verified: false,
|
||||||
attributes: [
|
attributes: [
|
||||||
{
|
{
|
||||||
trait_type: 'ENS',
|
trait_type: 'ENS',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { Errors, Fixtures, TestConstants } from './helpers';
|
||||||
|
|
||||||
|
describe('FleekERC721.GetToken', () => {
|
||||||
|
let fixture: Awaited<ReturnType<typeof Fixtures.withMint>>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
fixture = await loadFixture(Fixtures.withMint);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should mint token in not verified state', async () => {
|
||||||
|
const { contract, tokenId } = fixture;
|
||||||
|
|
||||||
|
expect(await contract.isTokenVerified(tokenId)).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set token to verified state', async () => {
|
||||||
|
const { contract, tokenId } = fixture;
|
||||||
|
|
||||||
|
await contract.setTokenVerified(tokenId, true);
|
||||||
|
|
||||||
|
expect(await contract.isTokenVerified(tokenId)).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set token to verified and unverified states', async () => {
|
||||||
|
const { contract, tokenId } = fixture;
|
||||||
|
|
||||||
|
await contract.setTokenVerified(tokenId, true);
|
||||||
|
await contract.setTokenVerified(tokenId, false);
|
||||||
|
|
||||||
|
expect(await contract.isTokenVerified(tokenId)).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should revert for non verifier call', async () => {
|
||||||
|
const { contract, tokenId, otherAccount } = fixture;
|
||||||
|
|
||||||
|
await expect(contract.connect(otherAccount).setTokenVerified(tokenId, true))
|
||||||
|
.to.be.revertedWithCustomError(contract, Errors.MustHaveCollectionRole)
|
||||||
|
.withArgs(TestConstants.CollectionRoles.Verifier);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should revert for non token verifier', async () => {
|
||||||
|
const { contract, tokenId, otherAccount } = fixture;
|
||||||
|
|
||||||
|
await contract.grantCollectionRole(
|
||||||
|
TestConstants.CollectionRoles.Verifier,
|
||||||
|
otherAccount.address
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(contract.connect(otherAccount).setTokenVerified(tokenId, true))
|
||||||
|
.to.be.revertedWithCustomError(contract, Errors.MustBeTokenVerifier)
|
||||||
|
.withArgs(tokenId);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue