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;
|
||||
mapping(uint256 => Token) private _apps;
|
||||
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.
|
||||
|
|
@ -133,6 +134,7 @@ contract FleekERC721 is
|
|||
);
|
||||
|
||||
_tokenVerifier[tokenId] = verifier;
|
||||
_tokenVerified[tokenId] = false;
|
||||
_setAccessPointAutoApproval(tokenId, accessPointAutoApproval);
|
||||
|
||||
return tokenId;
|
||||
|
|
@ -152,9 +154,10 @@ contract FleekERC721 is
|
|||
_requireMinted(tokenId);
|
||||
address owner = ownerOf(tokenId);
|
||||
bool accessPointAutoApproval = _getAccessPointAutoApproval(tokenId);
|
||||
bool verified = _tokenVerified[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];
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ library FleekStrings {
|
|||
function toString(
|
||||
IERCX.Token storage app,
|
||||
address owner,
|
||||
bool accessPointAutoApproval
|
||||
bool accessPointAutoApproval,
|
||||
bool verified
|
||||
) internal view returns (string memory) {
|
||||
// prettier-ignore
|
||||
return string(abi.encodePacked(
|
||||
|
|
@ -47,6 +48,7 @@ library FleekStrings {
|
|||
'"external_url":"', app.externalURL, '",',
|
||||
'"image":"', FleekSVG.generateBase64(app.name, app.ENS, app.logo, app.color.toColorString()), '",',
|
||||
'"access_point_auto_approval":', accessPointAutoApproval.toString(),',',
|
||||
'"verified":',verified.toString(),',',
|
||||
'"attributes": [',
|
||||
'{"trait_type": "ENS", "value":"', app.ENS,'"},',
|
||||
'{"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);
|
||||
}
|
||||
|
||||
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 {
|
||||
CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionOwner);
|
||||
expectRevertWithMustHaveAtLeastOneOwner();
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
|
|||
contract APConstants is Test {
|
||||
using Strings for address;
|
||||
|
||||
function expectRevertWithMustBeTokenVerifier(uint256 tokenId) public {
|
||||
vm.expectRevert(abi.encodeWithSelector(MustBeTokenVerifier.selector, tokenId));
|
||||
}
|
||||
|
||||
function assertAccessPointJSON(
|
||||
string memory accessPointName,
|
||||
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 {
|
||||
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 {
|
||||
|
|
|
|||
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',
|
||||
AccessPointScoreCannotBeLower: 'AccessPointScoreCannotBeLower',
|
||||
MustBeAccessPointOwner: 'MustBeAccessPointOwner',
|
||||
MustBeTokenVerifier: 'MustBeTokenVerifier',
|
||||
MustHaveCollectionRole: 'MustHaveCollectionRole',
|
||||
MustHaveTokenRole: 'MustHaveTokenRole',
|
||||
MustHaveAtLeastOneOwner: 'MustHaveAtLeastOneOwner',
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ describe('FleekERC721.TokenURI', () => {
|
|||
image: TestConstants.ResultantImage.Default,
|
||||
external_url: TestConstants.MintParams.externalUrl,
|
||||
access_point_auto_approval: false,
|
||||
verified: false,
|
||||
attributes: [
|
||||
{
|
||||
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