feat: access point auto approval settings for tokens (#121)

* feat: add accessPointAutoApprovalSettings field to mint, app struct, and expose a function for changing the field later + an event.

* feat: add checks for the autoapproval settings on function addAccessPoint.

* feat: add setApprovalForAccessPoint function and ChangeAccessPointApprovalStatus event.

* test: add new constant variables to the hardhat tests and update mint tests.

* feat: update removeAccessPoint function to check the status and also update getAccessPointJSON to include status.

* test: add two access point test files and fix errors and mismatches in them with the auto approval set up

* feat: remove the access point mapping in the App struct and wherever it was used.

* chore: update foundry tests to match the new interface of the contract.

* test: add new tests for the approval settings

* chore: update foundry tests to match new interface.

* test: update foundry tests and the settings for auto approvals

* feat: keep history of removed APs. Update tests.

* fix: make changes to the contract and tests to fix the tests.

* chore: apply changes Zoruka requested.

* fix: change name of setAutoApprovalSettings function in foundry tests.

* perf: revert back to enums, update hardhat and foundry tests.

* fix: apply requested changes by janison.

* fix: error in hardhat test.

* fix: mint params of a foundry test.

* fix: merge errors.

* fix: revert back to tokenOwner for setAutoApproval functions.

* chore: remove comment for accessPointAutoApproval
This commit is contained in:
Shredder 2023-02-23 12:29:53 +03:30 committed by GitHub
parent 770ab78668
commit cfea9a90ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 602 additions and 61 deletions

View File

@ -27,6 +27,13 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
event NewAccessPoint(string apName, uint256 indexed tokenId, address indexed owner); event NewAccessPoint(string apName, uint256 indexed tokenId, address indexed owner);
event RemoveAccessPoint(string apName, uint256 indexed tokenId, address indexed owner); event RemoveAccessPoint(string apName, uint256 indexed tokenId, address indexed owner);
event ChangeAccessPointAutoApproval(
uint256 indexed token,
bool indexed settings,
address indexed triggeredBy
);
event ChangeAccessPointScore(string apName, uint256 indexed tokenId, uint256 score, address indexed triggeredBy); event ChangeAccessPointScore(string apName, uint256 indexed tokenId, uint256 score, address indexed triggeredBy);
event ChangeAccessPointNameVerify( event ChangeAccessPointNameVerify(
@ -41,6 +48,13 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
bool indexed verified, bool indexed verified,
address indexed triggeredBy address indexed triggeredBy
); );
event ChangeAccessPointStatus(
string apName,
uint256 tokenId,
AccessPointCreationStatus status,
address indexed triggeredBy
);
/** /**
* The properties are stored as string to keep consistency with * The properties are stored as string to keep consistency with
* other token contracts, we might consider changing for bytes32 * other token contracts, we might consider changing for bytes32
@ -55,6 +69,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
mapping(uint256 => Build) builds; // Mapping to build details for each build number mapping(uint256 => Build) builds; // Mapping to build details for each build number
string logo; string logo;
uint24 color; // Color of the nft uint24 color; // Color of the nft
bool accessPointAutoApproval; // AP Auto Approval
} }
/** /**
@ -65,6 +80,16 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
string gitRepository; string gitRepository;
} }
/**
* Creation status enums for access points
*/
enum AccessPointCreationStatus {
DRAFT,
APPROVED,
REJECTED,
REMOVED
}
/** /**
* The stored data for each AccessPoint. * The stored data for each AccessPoint.
*/ */
@ -74,6 +99,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
bool contentVerified; bool contentVerified;
bool nameVerified; bool nameVerified;
address owner; address owner;
AccessPointCreationStatus status;
} }
Counters.Counter private _appIds; Counters.Counter private _appIds;
@ -117,7 +143,8 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
string memory commitHash, string memory commitHash,
string memory gitRepository, string memory gitRepository,
string memory logo, string memory logo,
uint24 color uint24 color,
bool accessPointAutoApproval
) public payable requireCollectionRole(CollectionRoles.Owner) returns (uint256) { ) public payable requireCollectionRole(CollectionRoles.Owner) returns (uint256) {
uint256 tokenId = _appIds.current(); uint256 tokenId = _appIds.current();
_mint(to, tokenId); _mint(to, tokenId);
@ -130,6 +157,7 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
app.ENS = ENS; app.ENS = ENS;
app.logo = logo; app.logo = logo;
app.color = color; app.color = color;
app.accessPointAutoApproval = accessPointAutoApproval;
// The mint interaction is considered to be the first build of the site. Updates from now on all increment the currentBuild 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 currentBuild by one and update the mapping.
app.currentBuild = 0; app.currentBuild = 0;
@ -225,6 +253,26 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
return "data:application/json;base64,"; return "data:application/json;base64,";
} }
/**
* @dev Updates the `accessPointAutoApproval` settings on minted `tokenId`.
*
* May emit a {ChangeAccessPointAutoApproval} event.
*
* Requirements:
*
* - the tokenId must be minted and valid.
* - the sender must have the `tokenController` role.
*
*/
function setAccessPointAutoApproval(
uint256 tokenId,
bool _apAutoApproval
) public virtual requireTokenOwner(tokenId) {
_requireMinted(tokenId);
_apps[tokenId].accessPointAutoApproval = _apAutoApproval;
emit ChangeAccessPointAutoApproval(tokenId, _apAutoApproval, msg.sender);
}
/** /**
* @dev Updates the `externalURL` metadata field of a minted `tokenId`. * @dev Updates the `externalURL` metadata field of a minted `tokenId`.
* *
@ -380,9 +428,56 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
_requireMinted(tokenId); _requireMinted(tokenId);
require(_accessPoints[apName].owner == address(0), "FleekERC721: AP already exists"); require(_accessPoints[apName].owner == address(0), "FleekERC721: AP already exists");
_accessPoints[apName] = AccessPoint(tokenId, 0, false, false, msg.sender);
emit NewAccessPoint(apName, tokenId, msg.sender); emit NewAccessPoint(apName, tokenId, msg.sender);
if (_apps[tokenId].accessPointAutoApproval) {
// Auto Approval is on.
_accessPoints[apName] = AccessPoint(tokenId, 0, false, false, msg.sender, AccessPointCreationStatus.APPROVED);
emit ChangeAccessPointStatus(apName, tokenId, AccessPointCreationStatus.APPROVED, msg.sender);
} else {
// Auto Approval is off. Should wait for approval.
_accessPoints[apName] = AccessPoint(tokenId, 0, false, false, msg.sender, AccessPointCreationStatus.DRAFT);
emit ChangeAccessPointStatus(apName, tokenId, AccessPointCreationStatus.DRAFT, msg.sender);
}
}
/**
* @dev Set approval settings for an access point.
* It will add the access point to the token's AP list, if `approved` is true.
*
* May emit a {ChangeAccessPointApprovalStatus} event.
*
* Requirements:
*
* - the tokenId must exist and be the same as the tokenId that is set for the AP.
* - the AP must exist.
* - must be called by a token controller.
*/
function setApprovalForAccessPoint(
uint256 tokenId,
string memory apName,
bool approved
) public requireTokenOwner(tokenId) {
AccessPoint storage accessPoint = _accessPoints[apName];
require(
accessPoint.tokenId == tokenId,
"FleekERC721: the passed tokenId is not the same as the access point's tokenId."
);
require(
accessPoint.status == AccessPointCreationStatus.DRAFT,
"FleekERC721: the access point creation status has been set before."
);
if (approved) {
// Approval
accessPoint.status = AccessPointCreationStatus.APPROVED;
emit ChangeAccessPointStatus(apName, tokenId, AccessPointCreationStatus.APPROVED, msg.sender);
} else {
// Not Approved
accessPoint.status = AccessPointCreationStatus.REJECTED;
emit ChangeAccessPointStatus(apName, tokenId, AccessPointCreationStatus.REJECTED, msg.sender);
}
} }
/** /**
@ -400,9 +495,9 @@ contract FleekERC721 is Initializable, ERC721Upgradeable, FleekAccessControl, Fl
*/ */
function removeAccessPoint(string memory apName) public whenNotPaused requireAP(apName) { function removeAccessPoint(string memory apName) public whenNotPaused requireAP(apName) {
require(msg.sender == _accessPoints[apName].owner, "FleekERC721: must be AP owner"); require(msg.sender == _accessPoints[apName].owner, "FleekERC721: must be AP owner");
_accessPoints[apName].status = AccessPointCreationStatus.REMOVED;
uint256 tokenId = _accessPoints[apName].tokenId; uint256 tokenId = _accessPoints[apName].tokenId;
emit ChangeAccessPointStatus(apName, tokenId, AccessPointCreationStatus.REMOVED, msg.sender);
delete _accessPoints[apName];
emit RemoveAccessPoint(apName, tokenId, msg.sender); emit RemoveAccessPoint(apName, tokenId, msg.sender);
} }

View File

@ -41,6 +41,7 @@ library FleekStrings {
'"owner":"', uint160(owner).toHexString(20), '",', '"owner":"', uint160(owner).toHexString(20), '",',
'"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":',app.accessPointAutoApproval.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,'"},',
@ -63,7 +64,8 @@ library FleekStrings {
'"score":', ap.score.toString(), ",", '"score":', ap.score.toString(), ",",
'"nameVerified":', ap.nameVerified.toString(), ",", '"nameVerified":', ap.nameVerified.toString(), ",",
'"contentVerified":', ap.contentVerified.toString(), ",", '"contentVerified":', ap.contentVerified.toString(), ",",
'"owner":"', uint160(ap.owner).toHexString(20), '"', '"owner":"', uint160(ap.owner).toHexString(20), '",',
'"status":',uint(ap.status).toString(),
"}" "}"
)); ));
} }

View File

@ -2,28 +2,16 @@
pragma solidity ^0.8.17; pragma solidity ^0.8.17;
import "./TestBase.sol"; import "../TestBase.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {FleekAccessControl} from "contracts/FleekAccessControl.sol"; import {FleekAccessControl} from "contracts/FleekAccessControl.sol";
import "../../../../contracts/FleekERC721.sol";
import './ApBase.sol';
contract Test_FleekERC721_AccessPoint is Test_FleekERC721_Base { contract Test_FleekERC721_AccessPoint is Test_FleekERC721_Base, APConstants {
using Strings for address; using Strings for address;
uint256 internal tokenId; uint256 internal tokenId;
function assertAccessPointJSON(
string memory accessPointName,
string memory _tokenId,
string memory score,
string memory nameVerified,
string memory contentVerified,
address owner
) internal {
string memory current = CuT.getAccessPointJSON(accessPointName);
// prettier-ignore
string memory expectedJSON = string(abi.encodePacked('{"tokenId":', _tokenId, ',"score":', score, ',"nameVerified":', nameVerified, ',"contentVerified":', contentVerified, ',"owner":"', owner.toHexString(), '"}'));
assertEq(current, expectedJSON);
}
function setUp() public { function setUp() public {
baseSetUp(); baseSetUp();
tokenId = mintDefault(deployer); tokenId = mintDefault(deployer);
@ -33,7 +21,7 @@ contract Test_FleekERC721_AccessPoint is Test_FleekERC721_Base {
string memory accessPointName = "accesspoint.com"; string memory accessPointName = "accesspoint.com";
CuT.addAccessPoint(tokenId, accessPointName); CuT.addAccessPoint(tokenId, accessPointName);
assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer); APConstants.assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer, "0", CuT.getAccessPointJSON(accessPointName));
} }
function test_removeAccessPoint() public { function test_removeAccessPoint() public {
@ -41,24 +29,14 @@ contract Test_FleekERC721_AccessPoint is Test_FleekERC721_Base {
CuT.addAccessPoint(tokenId, accessPointName); CuT.addAccessPoint(tokenId, accessPointName);
CuT.removeAccessPoint(accessPointName); CuT.removeAccessPoint(accessPointName);
expectRevertWithInvalidAP(); APConstants.assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer, "3", CuT.getAccessPointJSON(accessPointName));
CuT.getAccessPointJSON(accessPointName);
} }
function test_cannotRemoveNonexistentAccessPoint() public { function test_cannotRemoveNonExistentAccessPoint() public {
expectRevertWithInvalidAP(); expectRevertWithInvalidAP();
CuT.removeAccessPoint("accesspoint.com"); CuT.removeAccessPoint("accesspoint.com");
} }
function test_cannotTwiceRemoveAccessPoint() public {
string memory accessPointName = "accesspoint.com";
CuT.addAccessPoint(tokenId, accessPointName);
CuT.removeAccessPoint(accessPointName);
expectRevertWithInvalidAP();
CuT.removeAccessPoint(accessPointName);
}
function test_isAccessPointNameVerified() public { function test_isAccessPointNameVerified() public {
string memory accessPointName = "accesspoint.com"; string memory accessPointName = "accesspoint.com";
CuT.addAccessPoint(tokenId, accessPointName); CuT.addAccessPoint(tokenId, accessPointName);
@ -69,20 +47,20 @@ contract Test_FleekERC721_AccessPoint is Test_FleekERC721_Base {
function test_increaseAccessPointScore() public { function test_increaseAccessPointScore() public {
string memory accessPointName = "accesspoint.com"; string memory accessPointName = "accesspoint.com";
CuT.addAccessPoint(tokenId, accessPointName); CuT.addAccessPoint(tokenId, accessPointName);
assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer); APConstants.assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer, "0", CuT.getAccessPointJSON(accessPointName));
CuT.increaseAccessPointScore(accessPointName); CuT.increaseAccessPointScore(accessPointName);
assertAccessPointJSON(accessPointName, "0", "1", "false", "false", deployer); APConstants.assertAccessPointJSON(accessPointName, "0", "1", "false", "false", deployer, "0", CuT.getAccessPointJSON(accessPointName));
CuT.increaseAccessPointScore(accessPointName); CuT.increaseAccessPointScore(accessPointName);
assertAccessPointJSON(accessPointName, "0", "2", "false", "false", deployer); APConstants.assertAccessPointJSON(accessPointName, "0", "2", "false", "false", deployer, "0", CuT.getAccessPointJSON(accessPointName));
} }
function test_cannotDecreaseAccessPointScoreToMinusOne() public { function test_cannotDecreaseAccessPointScoreToMinusOne() public {
string memory accessPointName = "accesspoint.com"; string memory accessPointName = "accesspoint.com";
CuT.addAccessPoint(tokenId, accessPointName); CuT.addAccessPoint(tokenId, accessPointName);
assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer); APConstants.assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer, "0", CuT.getAccessPointJSON(accessPointName));
expectRevertWithMinimalScore(); expectRevertWithMinimalScore();
CuT.decreaseAccessPointScore(accessPointName); CuT.decreaseAccessPointScore(accessPointName);
} }
@ -91,11 +69,11 @@ contract Test_FleekERC721_AccessPoint is Test_FleekERC721_Base {
string memory accessPointName = "accesspoint.com"; string memory accessPointName = "accesspoint.com";
CuT.addAccessPoint(tokenId, accessPointName); CuT.addAccessPoint(tokenId, accessPointName);
assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer); APConstants.assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer, "0", CuT.getAccessPointJSON(accessPointName));
CuT.increaseAccessPointScore(accessPointName); CuT.increaseAccessPointScore(accessPointName);
assertAccessPointJSON(accessPointName, "0", "1", "false", "false", deployer); APConstants.assertAccessPointJSON(accessPointName, "0", "1", "false", "false", deployer, "0", CuT.getAccessPointJSON(accessPointName));
CuT.decreaseAccessPointScore(accessPointName); CuT.decreaseAccessPointScore(accessPointName);
assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer); APConstants.assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer, "0", CuT.getAccessPointJSON(accessPointName));
} }
function test_cannotAddAccessPointToNonexistentToken() public { function test_cannotAddAccessPointToNonexistentToken() public {
@ -122,6 +100,6 @@ contract Test_FleekERC721_AccessPoint is Test_FleekERC721_Base {
CuT.setAccessPointContentVerify(accessPointName, true); CuT.setAccessPointContentVerify(accessPointName, true);
vm.stopPrank(); vm.stopPrank();
assertAccessPointJSON(accessPointName, "0", "0", "true", "true", deployer); APConstants.assertAccessPointJSON(accessPointName, "0", "0", "true", "true", deployer, "0", CuT.getAccessPointJSON(accessPointName));
} }
} }

View File

@ -0,0 +1,107 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "../TestBase.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {FleekAccessControl} from "contracts/FleekAccessControl.sol";
import "../../../../contracts/FleekERC721.sol";
import "./ApBase.sol";
contract Test_FleekERC721_AccessPoint is Test_FleekERC721_Base, APConstants {
using Strings for address;
uint256 internal tokenId;
function setUp() public {
baseSetUp();
tokenId = mintDefault(deployer);
CuT.setAccessPointAutoApproval(0, true);
}
function test_getAccessPointJSON() public {
string memory accessPointName = "accesspoint.com";
CuT.addAccessPoint(tokenId, accessPointName);
APConstants.assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer, "1", CuT.getAccessPointJSON(accessPointName));
}
function test_removeAccessPoint() public {
string memory accessPointName = "accesspoint.com";
CuT.addAccessPoint(tokenId, accessPointName);
CuT.removeAccessPoint(accessPointName);
APConstants.assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer, "3", CuT.getAccessPointJSON(accessPointName));
}
function test_cannotRemoveNonExistentAccessPoint() public {
expectRevertWithInvalidAP();
CuT.removeAccessPoint("accesspoint.com");
}
function test_isAccessPointNameVerified() public {
string memory accessPointName = "accesspoint.com";
CuT.addAccessPoint(tokenId, accessPointName);
assertFalse(CuT.isAccessPointNameVerified(accessPointName));
CuT.setAccessPointNameVerify(accessPointName, true);
assertEq(CuT.isAccessPointNameVerified(accessPointName), true);
}
function test_increaseAccessPointScore() public {
string memory accessPointName = "accesspoint.com";
CuT.addAccessPoint(tokenId, accessPointName);
APConstants.assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer, "1", CuT.getAccessPointJSON(accessPointName));
CuT.increaseAccessPointScore(accessPointName);
APConstants.assertAccessPointJSON(accessPointName, "0", "1", "false", "false", deployer, "1", CuT.getAccessPointJSON(accessPointName));
CuT.increaseAccessPointScore(accessPointName);
APConstants.assertAccessPointJSON(accessPointName, "0", "2", "false", "false", deployer, "1", CuT.getAccessPointJSON(accessPointName));
}
function test_cannotDecreaseAccessPointScoreToMinusOne() public {
string memory accessPointName = "accesspoint.com";
CuT.addAccessPoint(tokenId, accessPointName);
APConstants.assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer, "1", CuT.getAccessPointJSON(accessPointName));
expectRevertWithMinimalScore();
CuT.decreaseAccessPointScore(accessPointName);
}
function test_decreaseAccessPointScore() public {
string memory accessPointName = "accesspoint.com";
CuT.addAccessPoint(tokenId, accessPointName);
APConstants.assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer, "1", CuT.getAccessPointJSON(accessPointName));
CuT.increaseAccessPointScore(accessPointName);
APConstants.assertAccessPointJSON(accessPointName, "0", "1", "false", "false", deployer, "1", CuT.getAccessPointJSON(accessPointName));
CuT.decreaseAccessPointScore(accessPointName);
APConstants.assertAccessPointJSON(accessPointName, "0", "0", "false", "false", deployer, "1", CuT.getAccessPointJSON(accessPointName));
}
function test_cannotAddAccessPointToNonExistentToken() public {
expectRevertWithInvalidTokenId();
CuT.addAccessPoint(1, "accesspoint.com");
}
function test_setAccessPointVerifiesWithCorrectRole() public {
string memory accessPointName = "accesspoint.com";
address randomAddress = address(12);
CuT.addAccessPoint(tokenId, accessPointName);
vm.startPrank(randomAddress);
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setAccessPointNameVerify(accessPointName, true);
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
CuT.setAccessPointContentVerify(accessPointName, true);
vm.stopPrank();
CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
vm.startPrank(randomAddress);
CuT.setAccessPointNameVerify(accessPointName, true);
CuT.setAccessPointContentVerify(accessPointName, true);
vm.stopPrank();
APConstants.assertAccessPointJSON(accessPointName, "0", "0", "true", "true", deployer, "1", CuT.getAccessPointJSON(accessPointName));
}
}

View File

@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "../TestBase.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
contract APConstants is Test {
using Strings for address;
function assertAccessPointJSON(
string memory accessPointName,
string memory _tokenId,
string memory score,
string memory nameVerified,
string memory contentVerified,
address owner,
string memory status,
string memory current // the json result from getAccessPointJSON
) public {
// prettier-ignore
string memory expectedJSON = string(abi.encodePacked('{"tokenId":', _tokenId, ',"score":', score, ',"nameVerified":', nameVerified, ',"contentVerified":', contentVerified, ',"owner":"', owner.toHexString(), '","status":', status,'}'));
assertEq(current, expectedJSON);
}
}

View File

@ -35,13 +35,31 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
"94e8ba38568aea4fb277a37a4c472d94a6ce880a", "94e8ba38568aea4fb277a37a4c472d94a6ce880a",
"https://github.com/a-different/repository", "https://github.com/a-different/repository",
TestConstants.LOGO_1, TestConstants.LOGO_1,
0x654321 0x654321,
false
); );
assertEq(firstMint, 0); assertEq(firstMint, 0);
assertEq(secondMint, 1); assertEq(secondMint, 1);
} }
function test_mintWithAutoApprovalAPsOn() public {
uint256 mint = CuT.mint(
address(12),
"Different App Name",
"This is a different description for another app.",
"https://fleek.xyz",
"fleek.eth",
"94e8ba38568aea4fb277a37a4c472d94a6ce880a",
"https://github.com/a-different/repository",
TestConstants.LOGO_1,
0x654321,
true
);
assertEq(mint, 0);
}
function test_balanceOfDeployerAfterAndBeforeMinting() public { function test_balanceOfDeployerAfterAndBeforeMinting() public {
assertEq(CuT.balanceOf(deployer), 0); assertEq(CuT.balanceOf(deployer), 0);
@ -59,10 +77,22 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
string memory commitHash, string memory commitHash,
string memory gitRepository, string memory gitRepository,
string memory logo, string memory logo,
uint24 color uint24 color,
bool autoApprovalAp
) public { ) public {
vm.assume(to != address(0)); vm.assume(to != address(0));
uint256 tokenId = CuT.mint(to, appName, description, externalURL, ens, commitHash, gitRepository, logo, color); uint256 tokenId = CuT.mint(
to,
appName,
description,
externalURL,
ens,
commitHash,
gitRepository,
logo,
color,
autoApprovalAp
);
assertEq(tokenId, 0); assertEq(tokenId, 0);
assertEq(CuT.ownerOf(tokenId), to); assertEq(CuT.ownerOf(tokenId), to);

View File

@ -60,7 +60,8 @@ abstract contract Test_FleekERC721_Base is Test, Test_FleekERC721_Assertions {
TestConstants.APP_COMMIT_HASH, TestConstants.APP_COMMIT_HASH,
TestConstants.APP_GIT_REPOSITORY, TestConstants.APP_GIT_REPOSITORY,
TestConstants.LOGO_0, TestConstants.LOGO_0,
TestConstants.APP_COLOR TestConstants.APP_COLOR,
false // Auto Approval Is OFF
); );
return mint; return mint;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,255 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai';
import { before } from 'mocha';
import { TestConstants, Fixtures } from '../helpers';
const { AccessPointStatus } = TestConstants;
describe('FleekERC721.AccessPoints.AutoApprovalOff', () => {
let fixture: Awaited<ReturnType<typeof Fixtures.withMint>>;
beforeEach(async () => {
fixture = await loadFixture(Fixtures.withMint);
});
it('should add an AP with draft status', async () => {
const { contract, owner, tokenId } = fixture;
await expect(contract.addAccessPoint(tokenId, 'accesspoint.com'))
.to.emit(contract, 'NewAccessPoint')
.withArgs('accesspoint.com', tokenId, owner.address);
let ap = await contract.getAccessPointJSON('accesspoint.com');
const parsedAp = JSON.parse(ap);
expect(parsedAp).to.eql({
tokenId,
score: 0,
owner: owner.address.toLowerCase(),
contentVerified: false,
nameVerified: false,
status: AccessPointStatus.DRAFT,
});
});
it('should return a AP json object', async () => {
const { contract, owner, tokenId } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint.com');
const ap = await contract.getAccessPointJSON('accesspoint.com');
const parsedAp = JSON.parse(ap);
expect(parsedAp).to.eql({
tokenId,
score: 0,
owner: owner.address.toLowerCase(),
contentVerified: false,
nameVerified: false,
status: AccessPointStatus.DRAFT,
});
});
it('should revert if AP does not exist', async () => {
const { contract } = fixture;
await expect(
contract.getAccessPointJSON('accesspoint.com')
).to.be.revertedWith('FleekERC721: invalid AP');
});
it('should increase the AP score', async () => {
const { contract, owner, tokenId } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint.com');
await contract.increaseAccessPointScore('accesspoint.com');
const ap = await contract.getAccessPointJSON('accesspoint.com');
const parsedAp = JSON.parse(ap);
expect(parsedAp).to.eql({
tokenId,
score: 1,
owner: owner.address.toLowerCase(),
contentVerified: false,
nameVerified: false,
status: AccessPointStatus.DRAFT,
});
});
it('should decrease the AP score', async () => {
const { contract, owner, tokenId } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint.com');
await contract.increaseAccessPointScore('accesspoint.com');
await contract.increaseAccessPointScore('accesspoint.com');
await contract.decreaseAccessPointScore('accesspoint.com');
const ap = await contract.getAccessPointJSON('accesspoint.com');
const parsedAp = JSON.parse(ap);
expect(parsedAp).to.eql({
tokenId,
score: 1,
owner: owner.address.toLowerCase(),
contentVerified: false,
nameVerified: false,
status: AccessPointStatus.DRAFT,
});
});
it('should allow anyone to change AP score', async () => {
const { contract, otherAccount, tokenId } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint.com');
await contract.increaseAccessPointScore('accesspoint.com');
await contract
.connect(otherAccount)
.increaseAccessPointScore('accesspoint.com');
});
it('should remove an AP', async () => {
const { contract, owner, tokenId } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint.com');
await expect(contract.removeAccessPoint('accesspoint.com'))
.to.emit(contract, 'RemoveAccessPoint')
.withArgs('accesspoint.com', tokenId, owner.address);
});
it('should allow only AP owner to remove it', async () => {
const { contract, otherAccount, tokenId } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint.com');
await expect(
contract.connect(otherAccount).removeAccessPoint('accesspoint.com')
).to.be.revertedWith('FleekERC721: must be AP owner');
});
it('should not be allowed to add the same AP more than once', async () => {
const { contract, tokenId } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint.com');
await expect(
contract.addAccessPoint(tokenId, 'accesspoint.com')
).to.be.revertedWith('FleekERC721: AP already exists');
});
it('should change "contentVerified" to true', async () => {
const { contract, tokenId } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint.com');
await contract.setAccessPointContentVerify('accesspoint.com', true);
const ap = await contract.getAccessPointJSON('accesspoint.com');
const parsedAp = JSON.parse(ap);
expect(parsedAp.contentVerified).to.be.true;
});
it('should change "contentVerified" to false', async () => {
const { contract, tokenId } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint.com');
const beforeAp = await contract.getAccessPointJSON('accesspoint.com');
const beforeParsedAp = JSON.parse(beforeAp);
expect(beforeParsedAp.contentVerified).to.be.false;
await contract.setAccessPointContentVerify('accesspoint.com', true);
await contract.setAccessPointContentVerify('accesspoint.com', false);
const ap = await contract.getAccessPointJSON('accesspoint.com');
const parsedAp = JSON.parse(ap);
expect(parsedAp.contentVerified).to.be.false;
});
it('should change "nameVerified" to true', async () => {
const { contract, tokenId } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint.com');
await contract.setAccessPointNameVerify('accesspoint.com', true);
const ap = await contract.getAccessPointJSON('accesspoint.com');
const parsedAp = JSON.parse(ap);
expect(parsedAp.nameVerified).to.be.true;
});
it('should change "nameVerified" to false', async () => {
const { contract, tokenId } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint.com');
const beforeAp = await contract.getAccessPointJSON('accesspoint.com');
const beforeParsedAp = JSON.parse(beforeAp);
expect(beforeParsedAp.nameVerified).to.be.false;
await contract.setAccessPointNameVerify('accesspoint.com', true);
await contract.setAccessPointNameVerify('accesspoint.com', false);
const ap = await contract.getAccessPointJSON('accesspoint.com');
const parsedAp = JSON.parse(ap);
expect(parsedAp.nameVerified).to.be.false;
});
it('should token owner be able to change the auto approval settings to on', async () => {
const { contract, tokenId, owner } = fixture;
await contract
.connect(owner)
.setAccessPointAutoApproval(tokenId, true);
await contract.addAccessPoint(tokenId, 'accesspoint.com');
const beforeAp = await contract.getAccessPointJSON('accesspoint.com');
const beforeParsedAp = JSON.parse(beforeAp);
expect(beforeParsedAp.status).to.be.eql(AccessPointStatus.APPROVED); //APPROVED STATUS
});
it('should token owner be able to approve a draft ap', async () => {
const { contract, tokenId, owner } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint.com');
const beforeAp = await contract.getAccessPointJSON('accesspoint.com');
const beforeParsedAp = JSON.parse(beforeAp);
expect(beforeParsedAp.status).to.be.eql(AccessPointStatus.DRAFT); //DRAFT STATUS
await contract
.connect(owner)
.setApprovalForAccessPoint(tokenId, 'accesspoint.com', true);
const afterAp = await contract.getAccessPointJSON('accesspoint.com');
const afterParsedAp = JSON.parse(afterAp);
expect(afterParsedAp.status).to.be.eql(AccessPointStatus.APPROVED); //APPROVED STATUS
});
it('should token owner be able to reject a draft ap', async () => {
const { contract, tokenId, owner } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint.com');
const beforeAp = await contract.getAccessPointJSON('accesspoint.com');
const beforeParsedAp = JSON.parse(beforeAp);
expect(beforeParsedAp.status).to.be.eql(AccessPointStatus.DRAFT); //DRAFT STATUS
await contract
.connect(owner)
.setApprovalForAccessPoint(tokenId, 'accesspoint.com', false);
const afterAp = await contract.getAccessPointJSON('accesspoint.com');
const afterParsedAp = JSON.parse(afterAp);
expect(afterParsedAp.status).to.be.eql(AccessPointStatus.REJECTED); //REJECTED STATUS
});
});

View File

@ -1,27 +1,34 @@
import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai'; import { expect } from 'chai';
import { Fixtures } from './helpers'; import { before } from 'mocha';
import { TestConstants, Fixtures } from '../helpers';
const { AccessPointStatus } = TestConstants;
describe('AccessPoints', () => { describe('FleekERC721.AccessPoints.AutoApprovalOn', () => {
let fixture: Awaited<ReturnType<typeof Fixtures.withMint>>; let fixture: Awaited<ReturnType<typeof Fixtures.withMint>>;
const DefaultAP = 'accesspoint.com'; const DefaultAP = 'accesspoint.com';
beforeEach(async () => { beforeEach(async () => {
fixture = await loadFixture(Fixtures.withMint); fixture = await loadFixture(Fixtures.withMint);
fixture.contract.setAccessPointAutoApproval(fixture.tokenId, true);
fixture.contract.addAccessPoint(fixture.tokenId, DefaultAP); fixture.contract.addAccessPoint(fixture.tokenId, DefaultAP);
}); });
it('should add an AP', async () => { it('should add an AP with approved status', async () => {
const { contract, owner, tokenId } = fixture; const { contract, owner, tokenId } = fixture;
await expect(contract.addAccessPoint(tokenId, 'random.com')) await expect(contract.addAccessPoint(tokenId, 'random.com'))
.to.emit(contract, 'NewAccessPoint') .to.emit(contract, 'ChangeAccessPointStatus')
.withArgs('random.com', tokenId, owner.address); .withArgs(
'random.com',
tokenId,
AccessPointStatus.APPROVED,
owner.address
);
}); });
it('should return a AP json object', async () => { it('should return a AP json object', async () => {
const { contract, owner, tokenId } = fixture; const { contract, owner, tokenId } = fixture;
const ap = await contract.getAccessPointJSON(DefaultAP); const ap = await contract.getAccessPointJSON(DefaultAP);
const parsedAp = JSON.parse(ap); const parsedAp = JSON.parse(ap);
@ -31,6 +38,7 @@ describe('AccessPoints', () => {
owner: owner.address.toLowerCase(), owner: owner.address.toLowerCase(),
contentVerified: false, contentVerified: false,
nameVerified: false, nameVerified: false,
status: AccessPointStatus.APPROVED,
}); });
}); });
@ -56,6 +64,7 @@ describe('AccessPoints', () => {
owner: owner.address.toLowerCase(), owner: owner.address.toLowerCase(),
contentVerified: false, contentVerified: false,
nameVerified: false, nameVerified: false,
status: AccessPointStatus.APPROVED,
}); });
}); });
@ -75,6 +84,7 @@ describe('AccessPoints', () => {
owner: owner.address.toLowerCase(), owner: owner.address.toLowerCase(),
contentVerified: false, contentVerified: false,
nameVerified: false, nameVerified: false,
status: AccessPointStatus.APPROVED,
}); });
}); });
@ -91,6 +101,18 @@ describe('AccessPoints', () => {
await expect(contract.removeAccessPoint(DefaultAP)) await expect(contract.removeAccessPoint(DefaultAP))
.to.emit(contract, 'RemoveAccessPoint') .to.emit(contract, 'RemoveAccessPoint')
.withArgs(DefaultAP, tokenId, owner.address); .withArgs(DefaultAP, tokenId, owner.address);
const ap = await contract.getAccessPointJSON(DefaultAP);
const parsedAp = JSON.parse(ap);
expect(parsedAp).to.eql({
tokenId,
score: 0,
owner: owner.address.toLowerCase(),
contentVerified: false,
nameVerified: false,
status: AccessPointStatus.REMOVED,
});
}); });
it('should allow only AP owner to remove it', async () => { it('should allow only AP owner to remove it', async () => {
@ -162,4 +184,17 @@ describe('AccessPoints', () => {
expect(parsedAp.nameVerified).to.be.false; expect(parsedAp.nameVerified).to.be.false;
}); });
it('should token owner be able to change the auto approval settings to off', async () => {
const { contract, tokenId } = fixture;
await contract.setAccessPointAutoApproval(tokenId, false);
await contract.addAccessPoint(tokenId, 'random.com');
const beforeAp = await contract.getAccessPointJSON('random.com');
const beforeParsedAp = JSON.parse(beforeAp);
expect(beforeParsedAp.status).to.be.eql(AccessPointStatus.DRAFT); //DRAFT STATUS
});
}); });

View File

@ -18,7 +18,8 @@ describe('FleekERC721.GetLastTokenId', () => {
TestConstants.MintParams.commitHash, TestConstants.MintParams.commitHash,
TestConstants.MintParams.gitRepository, TestConstants.MintParams.gitRepository,
TestConstants.MintParams.logo, TestConstants.MintParams.logo,
TestConstants.MintParams.color TestConstants.MintParams.color,
false
); );
return response; return response;

View File

@ -5,6 +5,12 @@ export const TestConstants = Object.freeze({
TokenRoles: { TokenRoles: {
Controller: 0, Controller: 0,
}, },
AccessPointStatus: {
DRAFT: 0,
APPROVED: 1,
REJECTED: 2,
REMOVED: 3,
},
MintParams: { MintParams: {
name: 'Fleek Test App', name: 'Fleek Test App',
description: 'Fleek Test App Description', description: 'Fleek Test App Description',
@ -14,6 +20,7 @@ export const TestConstants = Object.freeze({
gitRepository: 'https://github.com/fleekxyz/non-fungible-apps', gitRepository: 'https://github.com/fleekxyz/non-fungible-apps',
logo: 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI1MDAiIHdpZHRoPSIyMTgzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjQgMTQxLjUzMTk5OTk5OTk5OTk4Ij48cGF0aCBkPSJNMTAuMzgzIDEyNi44OTRMMCAwbDEyNCAuMjU1LTEwLjk3OSAxMjYuNjM5LTUwLjU1MyAxNC42Mzh6IiBmaWxsPSIjZTM0ZjI2Ii8+PHBhdGggZD0iTTYyLjQ2OCAxMjkuMjc3VjEyLjA4NWw1MS4wNjQuMTctOS4xMDYgMTA0Ljg1MXoiIGZpbGw9IiNlZjY1MmEiLz48cGF0aCBkPSJNOTkuNDkgNDEuMzYybDEuNDQ2LTE1LjQ5SDIyLjM4M2w0LjM0IDQ3LjQ5aDU0LjIxM0w3OC44MSA5My42MTdsLTE3LjM2MiA0LjY4LTE3LjYxNy01LjEwNi0uOTM2LTEyLjA4NUgyNy4zMTlsMi4xMjggMjQuNjgxIDMyIDguOTM2IDMyLjI1NS04LjkzNiA0LjM0LTQ4LjE3SDQxLjEwN0wzOS40OSA0MS4zNjJ6IiBmaWxsPSIjZmZmIi8+PC9zdmc+', logo: 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI1MDAiIHdpZHRoPSIyMTgzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjQgMTQxLjUzMTk5OTk5OTk5OTk4Ij48cGF0aCBkPSJNMTAuMzgzIDEyNi44OTRMMCAwbDEyNCAuMjU1LTEwLjk3OSAxMjYuNjM5LTUwLjU1MyAxNC42Mzh6IiBmaWxsPSIjZTM0ZjI2Ii8+PHBhdGggZD0iTTYyLjQ2OCAxMjkuMjc3VjEyLjA4NWw1MS4wNjQuMTctOS4xMDYgMTA0Ljg1MXoiIGZpbGw9IiNlZjY1MmEiLz48cGF0aCBkPSJNOTkuNDkgNDEuMzYybDEuNDQ2LTE1LjQ5SDIyLjM4M2w0LjM0IDQ3LjQ5aDU0LjIxM0w3OC44MSA5My42MTdsLTE3LjM2MiA0LjY4LTE3LjYxNy01LjEwNi0uOTM2LTEyLjA4NUgyNy4zMTlsMi4xMjggMjQuNjgxIDMyIDguOTM2IDMyLjI1NS04LjkzNiA0LjM0LTQ4LjE3SDQxLjEwN0wzOS40OSA0MS4zNjJ6IiBmaWxsPSIjZmZmIi8+PC9zdmc+',
color: 0xe34f26, color: 0xe34f26,
accessPointAutoApprovalSettings: false,
}, },
CollectionParams: { CollectionParams: {
name: 'FleekERC721', name: 'FleekERC721',

View File

@ -42,7 +42,8 @@ export abstract class Fixtures {
TestConstants.MintParams.commitHash, TestConstants.MintParams.commitHash,
TestConstants.MintParams.gitRepository, TestConstants.MintParams.gitRepository,
TestConstants.MintParams.logo, TestConstants.MintParams.logo,
TestConstants.MintParams.color TestConstants.MintParams.color,
TestConstants.MintParams.accessPointAutoApprovalSettings
); );
const tokenId = response.value.toNumber(); const tokenId = response.value.toNumber();

View File

@ -18,7 +18,8 @@ describe('FleekERC721.Minting', () => {
MintParams.commitHash, MintParams.commitHash,
MintParams.gitRepository, MintParams.gitRepository,
MintParams.logo, MintParams.logo,
MintParams.color MintParams.color,
MintParams.accessPointAutoApprovalSettings
); );
expect(response.value).to.be.instanceOf(ethers.BigNumber); expect(response.value).to.be.instanceOf(ethers.BigNumber);
@ -40,7 +41,8 @@ describe('FleekERC721.Minting', () => {
MintParams.commitHash, MintParams.commitHash,
MintParams.gitRepository, MintParams.gitRepository,
MintParams.logo, MintParams.logo,
MintParams.color MintParams.color,
MintParams.accessPointAutoApprovalSettings
) )
) )
.to.be.revertedWithCustomError(contract, Errors.MustHaveCollectionRole) .to.be.revertedWithCustomError(contract, Errors.MustHaveCollectionRole)
@ -61,7 +63,8 @@ describe('FleekERC721.Minting', () => {
MintParams.commitHash, MintParams.commitHash,
MintParams.gitRepository, MintParams.gitRepository,
MintParams.logo, MintParams.logo,
MintParams.color MintParams.color,
MintParams.accessPointAutoApprovalSettings
); );
const tokenId = response.value.toNumber(); const tokenId = response.value.toNumber();

View File

@ -19,7 +19,8 @@ describe('FleekERC721.Pausable', () => {
MintParams.commitHash, MintParams.commitHash,
MintParams.gitRepository, MintParams.gitRepository,
MintParams.logo, MintParams.logo,
MintParams.color MintParams.color,
false
); );
}; };

View File

@ -21,6 +21,7 @@ describe('FleekERC721.TokenURI', () => {
description: TestConstants.MintParams.description, description: TestConstants.MintParams.description,
image: TestConstants.ResultantImage.Default, image: TestConstants.ResultantImage.Default,
external_url: TestConstants.MintParams.externalUrl, external_url: TestConstants.MintParams.externalUrl,
access_point_auto_approval: false,
attributes: [ attributes: [
{ {
trait_type: 'ENS', trait_type: 'ENS',