feat: add app access points and libraries clean up (#69)

* feat: add mirror mapping and management

* test: add mirrors tests

* chore: add new functions header comments

* feat: add isMirrorVerified function

* feat: add list of mirrors to token

* feat: add require minted to appMirrors function

* chore: update solidity compiler on hardhat config

* refactor: add token id to other mirror events

* refactor: change from mirror to access point and update its metadata

* test: updates tests due to contract changes

* refactor: clean up string parser from main contract

* refactor: remove wronge requirement comments

* refactor: strings library (#71)

* refactor: move string parse functions to a library

* refactor: remove not used modifier

* refactor: move svg generation to library

* refactor: remove source from aps

* refactor: rename accessPoint function
This commit is contained in:
Felipe Mendes 2023-01-18 11:39:44 -03:00 committed by GitHub
parent 66e041f63f
commit fc20f02b7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 562 additions and 62 deletions

View File

@ -6,10 +6,13 @@ import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
import "./FleekAccessControl.sol";
import "./util/FleekStrings.sol";
contract FleekERC721 is ERC721, FleekAccessControl {
using Strings for uint256;
using Counters for Counters.Counter;
using FleekStrings for FleekERC721.App;
using FleekStrings for FleekERC721.AccessPoint;
using FleekStrings for string;
event NewBuild(uint256 indexed token, string indexed commitHash, address indexed triggeredBy);
event NewTokenName(uint256 indexed token, string indexed name, address indexed triggeredBy);
@ -18,6 +21,27 @@ contract FleekERC721 is ERC721, FleekAccessControl {
event NewTokenExternalURL(uint256 indexed token, string indexed externalURL, address indexed triggeredBy);
event NewTokenENS(uint256 indexed token, string indexed ENS, address indexed triggeredBy);
event NewAccessPoint(string indexed apName, uint256 indexed tokenId, address indexed owner);
event RemoveAccessPoint(string indexed apName, uint256 indexed tokenId, address indexed owner);
event ChangeAccessPointScore(
string indexed apName,
uint256 indexed tokenId,
uint256 score,
address indexed triggeredBy
);
event ChangeAccessPointNameVerify(
string indexed apName,
uint256 tokenId,
bool indexed verified,
address indexed triggeredBy
);
event ChangeAccessPointContentVerify(
string indexed apName,
uint256 tokenId,
bool indexed verified,
address indexed triggeredBy
);
/**
* The properties are stored as string to keep consistency with
* other token contracts, we might consider changing for bytes32
@ -30,6 +54,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
string ENS; // ENS ID
uint256 currentBuild; // 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
string[] accessPoints; // List of app AccessPoint
}
/**
@ -40,8 +65,21 @@ contract FleekERC721 is ERC721, FleekAccessControl {
string gitRepository;
}
Counters.Counter private _tokenIds;
/**
* The stored data for each AccessPoint.
*/
struct AccessPoint {
uint256 tokenId;
uint256 index;
uint256 score;
bool contentVerified;
bool nameVerified;
address owner;
}
Counters.Counter private _appIds;
mapping(uint256 => App) private _apps;
mapping(string => AccessPoint) private _accessPoints;
/**
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
@ -49,47 +87,13 @@ contract FleekERC721 is ERC721, FleekAccessControl {
constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {}
/**
* @dev Checks if msg.sender has the role of tokenOwner for a certain tokenId.
* @dev Checks if the AccessPoint exists.
*/
modifier requireTokenOwner(uint256 tokenId) {
require(msg.sender == ownerOf(tokenId), "FleekERC721: must be token owner");
modifier requireAP(string memory apName) {
require(_accessPoints[apName].owner != address(0), "FleekERC721: invalid AP");
_;
}
/**
* @dev Generates a SVG image.
*/
function _generateSVG(string memory name, string memory ENS) internal view returns (string memory) {
return (
string(
abi.encodePacked(
_baseURI(),
Base64.encode(
abi.encodePacked(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="640" height="480" viewBox="0 0 640 480" xml:space="preserve">',
"<defs>",
"</defs>",
'<g transform="matrix(3.42 0 0 3.42 300.98 252.98)" >',
'<polygon style="stroke: rgb(0,0,0); stroke-width: 8; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(152,152,183); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" points="-50,-50 -50,50 50,50 50,-50 " />',
"</g>",
'<g transform="matrix(1 0 0 1 303.5 115.67)" style="" >',
'<text xml:space="preserve" font-family="Open Sans" font-size="24" font-style="normal" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1; white-space: pre;" ><tspan x="-45.7" y="5.65" style="stroke-width: 1; font-family: "Open Sans", sans-serif; font-size: 18px; font-style: normal; font-weight: normal; fill: rgb(0,0,0); ">Fleek NFAs</tspan></text>',
"</g>",
'<g transform="matrix(1 0 0 1 302 261.47)" style="" >',
'<text xml:space="preserve" font-family="Open Sans" font-size="28" font-style="normal" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1; white-space: pre;" ><tspan x="-44.26" y="-6.14" style="stroke-width: 1; font-family: "Open Sans", sans-serif; font-size: 18px; font-style: normal; font-weight: normal; fill: rgb(0,0,0); ">',
name,
'</tspan><tspan x="-37.14" y="17.45" style="stroke-width: 1; font-family: "Open Sans", sans-serif; font-size: 18px; font-style: normal; font-weight: normal; fill: rgb(0,0,0); ">',
ENS,
"</tspan></text>",
"</g>",
"</svg>"
)
)
)
)
);
}
/**
* @dev Mints a token and returns a tokenId.
*
@ -109,9 +113,9 @@ contract FleekERC721 is ERC721, FleekAccessControl {
string memory commitHash,
string memory gitRepository
) public payable requireCollectionRole(Roles.Owner) returns (uint256) {
uint256 tokenId = _tokenIds.current();
uint256 tokenId = _appIds.current();
_mint(to, tokenId);
_tokenIds.increment();
_appIds.increment();
App storage app = _apps[tokenId];
app.name = name;
@ -122,6 +126,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
// The mint interaction is considered to be the first build of the site. Updates from now on all increment the currentBuild by one and update the mapping.
app.currentBuild = 0;
app.builds[0] = Build(commitHash, gitRepository);
app.accessPoints = new string[](0);
return tokenId;
}
@ -141,24 +146,7 @@ contract FleekERC721 is ERC721, FleekAccessControl {
address owner = ownerOf(tokenId);
App storage app = _apps[tokenId];
// prettier-ignore
bytes memory dataURI = abi.encodePacked(
'{',
'"name":"', app.name, '",',
'"description":"', app.description, '",',
'"owner":"', Strings.toHexString(uint160(owner), 20), '",',
'"external_url":"', app.externalURL, '",',
'"image":"', _generateSVG(app.name, app.ENS), '",',
'"attributes": [',
'{"trait_type": "ENS", "value":"', app.ENS,'"},',
'{"trait_type": "Commit Hash", "value":"', app.builds[app.currentBuild].commitHash,'"},',
'{"trait_type": "Repository", "value":"', app.builds[app.currentBuild].gitRepository,'"},',
'{"trait_type": "Version", "value":"', Strings.toString(app.currentBuild),'"}',
']',
'}'
);
return string(abi.encodePacked(_baseURI(), Base64.encode((dataURI))));
return string(abi.encodePacked(_baseURI(), app.toString(owner).toBase64()));
}
/**
@ -278,6 +266,168 @@ contract FleekERC721 is ERC721, FleekAccessControl {
emit NewTokenDescription(tokenId, _tokenDescription, msg.sender);
}
/**
* @dev Add a new AccessPoint register for an app token.
* The AP name should be a DNS or ENS url and it should be unique.
* Anyone can add an AP but it should requires a payment.
*
* May emit a {NewAccessPoint} event.
*
* Requirements:
*
* - the tokenId must be minted and valid.
*
* IMPORTANT: The payment is not set yet
*/
function addAccessPoint(uint256 tokenId, string memory apName) public payable {
// require(msg.value == 0.1 ether, "You need to pay at least 0.1 ETH"); // TODO: define a minimum price
_requireMinted(tokenId);
require(_accessPoints[apName].owner == address(0), "FleekERC721: AP already exists");
_accessPoints[apName] = AccessPoint(tokenId, _apps[tokenId].accessPoints.length, 0, false, false, msg.sender);
_apps[tokenId].accessPoints.push(apName);
emit NewAccessPoint(apName, tokenId, msg.sender);
}
/**
* @dev Remove an AccessPoint registry for an app token.
* It will also remove the AP from the app token APs list.
*
* May emit a {RemoveAccessPoint} event.
*
* Requirements:
*
* - the AP must exist.
* - must be called by the AP owner.
*/
function removeAccessPoint(string memory apName) public requireAP(apName) {
require(msg.sender == _accessPoints[apName].owner, "FleekERC721: must be AP owner");
uint256 tokenId = _accessPoints[apName].tokenId;
App storage _app = _apps[tokenId];
// the index of the AP to remove
uint256 indexToRemove = _accessPoints[apName].index;
// the last item is reposited in the index to remove
string memory lastAP = _app.accessPoints[_app.accessPoints.length - 1];
_app.accessPoints[indexToRemove] = lastAP;
_accessPoints[lastAP].index = indexToRemove;
// remove the last item
_app.accessPoints.pop();
delete _accessPoints[apName];
emit RemoveAccessPoint(apName, tokenId, msg.sender);
}
/**
* @dev A view function to gether information about an AccessPoint.
* It returns a JSON string representing the AccessPoint information.
*
* Requirements:
*
* - the AP must exist.
*
*/
function getAccessPointJSON(string memory apName) public view requireAP(apName) returns (string memory) {
AccessPoint storage _ap = _accessPoints[apName];
return _ap.toString();
}
/**
* @dev A view function to check if a AccessPoint is verified.
*
* Requirements:
*
* - the AP must exist.
*
*/
function isAccessPointNameVerified(string memory apName) public view requireAP(apName) returns (bool) {
return _accessPoints[apName].nameVerified;
}
/**
* @dev Increases the score of a AccessPoint registry.
*
* May emit a {ChangeAccessPointScore} event.
*
* Requirements:
*
* - the AP must exist.
*
*/
function increaseAccessPointScore(string memory apName) public requireAP(apName) {
_accessPoints[apName].score++;
emit ChangeAccessPointScore(apName, _accessPoints[apName].tokenId, _accessPoints[apName].score, msg.sender);
}
/**
* @dev Decreases the score of a AccessPoint registry if is greater than 0.
*
* May emit a {ChangeAccessPointScore} event.
*
* Requirements:
*
* - the AP must exist.
*
*/
function decreaseAccessPointScore(string memory apName) public requireAP(apName) {
require(_accessPoints[apName].score > 0, "FleekERC721: score cant be lower");
_accessPoints[apName].score--;
emit ChangeAccessPointScore(apName, _accessPoints[apName].tokenId, _accessPoints[apName].score, msg.sender);
}
/**
* @dev Set the content verification of a AccessPoint registry.
*
* May emit a {ChangeAccessPointContentVerify} event.
*
* Requirements:
*
* - the AP must exist.
* - the sender must have the token controller role.
*
*/
function setAccessPointContentVerify(
string memory apName,
bool verified
) public requireAP(apName) requireTokenRole(_accessPoints[apName].tokenId, Roles.Controller) {
_accessPoints[apName].contentVerified = verified;
emit ChangeAccessPointContentVerify(apName, _accessPoints[apName].tokenId, verified, msg.sender);
}
/**
* @dev Set the name verification of a AccessPoint registry.
*
* May emit a {ChangeAccessPointNameVerify} event.
*
* Requirements:
*
* - the AP must exist.
* - the sender must have the token controller role.
*
*/
function setAccessPointNameVerify(
string memory apName,
bool verified
) public requireAP(apName) requireTokenRole(_accessPoints[apName].tokenId, Roles.Controller) {
_accessPoints[apName].nameVerified = verified;
emit ChangeAccessPointNameVerify(apName, _accessPoints[apName].tokenId, verified, msg.sender);
}
/**
* @dev A view function to gether the list of mirrros for a given app.
*
* Requirements:
* - the tokenId must be minted and valid.
*
*/
function appAccessPoints(uint256 tokenId) public view returns (string[] memory) {
_requireMinted(tokenId);
return _apps[tokenId].accessPoints;
}
/**
* @dev Adds a new build to a minted `tokenId`'s builds mapping.
*

View File

@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "../FleekERC721.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
library FleekSVG {
/**
* @dev Generates a SVG image.
*/
function generateBase64(string memory name, string memory ENS) internal pure returns (string memory) {
return (
string(
abi.encodePacked(
"data:application/json;base64,",
Base64.encode(
abi.encodePacked(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="640" height="480" viewBox="0 0 640 480" xml:space="preserve">',
"<defs>",
"</defs>",
'<g transform="matrix(3.42 0 0 3.42 300.98 252.98)" >',
'<polygon style="stroke: rgb(0,0,0); stroke-width: 8; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(152,152,183); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" points="-50,-50 -50,50 50,50 50,-50 " />',
"</g>",
'<g transform="matrix(1 0 0 1 303.5 115.67)" style="" >',
'<text xml:space="preserve" font-family="Open Sans" font-size="24" font-style="normal" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1; white-space: pre;" ><tspan x="-45.7" y="5.65" style="stroke-width: 1; font-family: "Open Sans", sans-serif; font-size: 18px; font-style: normal; font-weight: normal; fill: rgb(0,0,0); ">Fleek NFAs</tspan></text>',
"</g>",
'<g transform="matrix(1 0 0 1 302 261.47)" style="" >',
'<text xml:space="preserve" font-family="Open Sans" font-size="28" font-style="normal" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1; white-space: pre;" ><tspan x="-44.26" y="-6.14" style="stroke-width: 1; font-family: "Open Sans", sans-serif; font-size: 18px; font-style: normal; font-weight: normal; fill: rgb(0,0,0); ">',
name,
'</tspan><tspan x="-37.14" y="17.45" style="stroke-width: 1; font-family: "Open Sans", sans-serif; font-size: 18px; font-style: normal; font-weight: normal; fill: rgb(0,0,0); ">',
ENS,
"</tspan></text>",
"</g>",
"</svg>"
)
)
)
)
);
}
}

View File

@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "../FleekERC721.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
import "./FleekSVG.sol";
library FleekStrings {
using Strings for uint256;
using Strings for uint160;
using FleekStrings for bool;
/**
* @dev Converts a boolean value to a string.
*/
function toString(bool _bool) internal pure returns (string memory) {
return _bool ? "true" : "false";
}
/**
* @dev Converts a string to a base64 string.
*/
function toBase64(string memory str) internal pure returns (string memory) {
return Base64.encode(bytes(str));
}
/**
* @dev Converts FleekERC721.App to a JSON string.
* It requires to receive owner address as a parameter.
*/
function toString(FleekERC721.App storage app, address owner) internal view returns (string memory) {
// prettier-ignore
return string(abi.encodePacked(
'{',
'"name":"', app.name, '",',
'"description":"', app.description, '",',
'"owner":"', uint160(owner).toHexString(20), '",',
'"external_url":"', app.externalURL, '",',
'"image":"', FleekSVG.generateBase64(app.name, app.ENS), '",',
'"attributes": [',
'{"trait_type": "ENS", "value":"', app.ENS,'"},',
'{"trait_type": "Commit Hash", "value":"', app.builds[app.currentBuild].commitHash,'"},',
'{"trait_type": "Repository", "value":"', app.builds[app.currentBuild].gitRepository,'"},',
'{"trait_type": "Version", "value":"', app.currentBuild.toString(),'"}',
']',
'}'
));
}
/**
* @dev Converts FleekERC721.AccessPoint to a JSON string.
*/
function toString(FleekERC721.AccessPoint storage ap) internal view returns (string memory) {
// prettier-ignore
return string(abi.encodePacked(
"{",
'"tokenId":', ap.tokenId.toString(), ",",
'"score":', ap.score.toString(), ",",
'"nameVerified":', ap.nameVerified.toString(), ",",
'"contentVerified":', ap.contentVerified.toString(), ",",
'"owner":"', uint160(ap.owner).toHexString(20), '"',
"}"
));
}
}

View File

@ -52,16 +52,16 @@ const config: HardhatUserConfig = {
},
},
solidity: {
version: '0.8.7',
version: '0.8.12',
settings: {
optimizer: {
enabled: true,
runs: 200,
details: {
yul: false,
yul: true,
},
},
viaIR: false,
viaIR: true,
},
},
mocha: {

View File

@ -600,4 +600,244 @@ describe('FleekERC721', () => {
.withArgs(ROLES.CONTROLLER, otherAccount.address, owner.address);
});
});
describe('AccessPoints', () => {
let tokenId: number;
let fixture: Awaited<ReturnType<typeof defaultFixture>>;
const getDefaultAddParams = () => [tokenId, 'accesspoint.com'];
beforeEach(async () => {
fixture = await loadFixture(defaultFixture);
const { contract } = fixture;
const response = await contract.mint(
fixture.owner.address,
MINT_PARAMS.name,
MINT_PARAMS.description,
MINT_PARAMS.externalUrl,
MINT_PARAMS.ens,
MINT_PARAMS.commitHash,
MINT_PARAMS.gitRepository
);
tokenId = response.value.toNumber();
});
it('should add an AP', async () => {
const { contract, owner } = fixture;
await expect(contract.addAccessPoint(...getDefaultAddParams()))
.to.emit(contract, 'NewAccessPoint')
.withArgs('accesspoint.com', tokenId, owner.address);
expect(await contract.appAccessPoints(tokenId)).eql(['accesspoint.com']);
});
it('should return a AP json object', async () => {
const { contract, owner } = fixture;
await contract.addAccessPoint(...getDefaultAddParams());
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,
});
});
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 } = fixture;
await contract.addAccessPoint(...getDefaultAddParams());
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,
});
});
it('should decrease the AP score', async () => {
const { contract, owner } = fixture;
await contract.addAccessPoint(...getDefaultAddParams());
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,
});
});
it('should allow anyone to change AP score', async () => {
const { contract, otherAccount } = fixture;
await contract.addAccessPoint(...getDefaultAddParams());
await contract.increaseAccessPointScore('accesspoint.com');
await contract
.connect(otherAccount)
.increaseAccessPointScore('accesspoint.com');
});
it('should remove an AP', async () => {
const { contract, owner } = fixture;
await contract.addAccessPoint(...getDefaultAddParams());
await expect(contract.removeAccessPoint('accesspoint.com'))
.to.emit(contract, 'RemoveAccessPoint')
.withArgs('accesspoint.com', tokenId, owner.address);
expect(await contract.appAccessPoints(tokenId)).eql([]);
});
it('should allow only AP owner to remove it', async () => {
const { contract, otherAccount } = fixture;
await contract.addAccessPoint(...getDefaultAddParams());
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 } = fixture;
await contract.addAccessPoint(...getDefaultAddParams());
await expect(
contract.addAccessPoint(...getDefaultAddParams())
).to.be.revertedWith('FleekERC721: AP already exists');
});
it('should change "contentVerified" to true', async () => {
const { contract } = fixture;
await contract.addAccessPoint(...getDefaultAddParams());
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 } = fixture;
await contract.addAccessPoint(...getDefaultAddParams());
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 } = fixture;
await contract.addAccessPoint(...getDefaultAddParams());
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 } = fixture;
await contract.addAccessPoint(...getDefaultAddParams());
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 get a list of added APs for an app', async () => {
const { contract } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint1.com');
await contract.addAccessPoint(tokenId, 'accesspoint2.com');
await contract.addAccessPoint(tokenId, 'accesspoint3.com');
await contract.addAccessPoint(tokenId, 'accesspoint4.com');
const aps = await contract.appAccessPoints(tokenId);
expect(aps).to.eql([
'accesspoint1.com',
'accesspoint2.com',
'accesspoint3.com',
'accesspoint4.com',
]);
});
it('should get a list of added APs for an app after removing one', async () => {
const { contract } = fixture;
await contract.addAccessPoint(tokenId, 'accesspoint1.com');
await contract.addAccessPoint(tokenId, 'accesspoint2.com');
await contract.addAccessPoint(tokenId, 'accesspoint3.com');
await contract.addAccessPoint(tokenId, 'accesspoint4.com');
await contract.removeAccessPoint('accesspoint2.com');
const aps = await contract.appAccessPoints(tokenId);
expect(aps).to.eql([
'accesspoint1.com',
'accesspoint4.com',
'accesspoint3.com',
]);
});
});
});