Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/sift tokens #621

Open
wants to merge 31 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4ef804a
init
astrimaitis Feb 8, 2023
7c0a2cb
edits before sleep
astrimaitis Feb 8, 2023
3d1017d
updates
astrimaitis Feb 8, 2023
1079a77
edits
astrimaitis Feb 13, 2023
42acd4f
nuke block comments
astrimaitis Feb 13, 2023
16f8e3f
Merge branch 'develop' into feature/siftTokens
astrimaitis Feb 13, 2023
d6fee29
updated package.json
astrimaitis Feb 13, 2023
ec0630f
yarn.lock edits
astrimaitis Feb 13, 2023
4bca44f
compiles and runs without error
astrimaitis Feb 13, 2023
43ed2c7
Hacked some examples into sift.sol
TtheBC01 Feb 14, 2023
b273808
more general setup
astrimaitis Feb 14, 2023
bea472d
fixed testing
astrimaitis Feb 15, 2023
3467eb7
removed ERC721URIStorageUpgradeable
astrimaitis Feb 15, 2023
7960d12
Merge branch 'develop' into feature/siftTokens
astrimaitis Feb 15, 2023
97685f8
OG yarn.lock
astrimaitis Feb 15, 2023
e091a86
newline
astrimaitis Feb 15, 2023
99d5fc6
newline
astrimaitis Feb 15, 2023
a794ce0
remove unneeded variables
astrimaitis Feb 15, 2023
dbed3b1
small comment
TtheBC01 Feb 15, 2023
8ab57a2
addressed Charlie's comments
astrimaitis Feb 15, 2023
8030bfc
burn override
astrimaitis Feb 15, 2023
4b5e273
sift testing
astrimaitis Feb 16, 2023
6d1fd3a
sift tasks working!
astrimaitis Feb 17, 2023
da7654e
updated contracts-sdk and scamfilter
astrimaitis Feb 18, 2023
4693797
Merge branch 'develop' into feature/siftTokens
astrimaitis Feb 18, 2023
f6cb5b2
changes before insight dev went down
astrimaitis Feb 18, 2023
cc6797e
switching branch
astrimaitis Feb 22, 2023
26e3acd
check in with Sean
astrimaitis Feb 22, 2023
80588f8
Merge branch 'develop' into feature/siftTokens
astrimaitis Feb 27, 2023
5fe8f37
edits
astrimaitis Feb 27, 2023
881cb85
Merge branch 'develop' into feature/siftTokens
astrimaitis Mar 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 58 additions & 46 deletions packages/contracts/contracts/registry/Sift.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// SPDX-License-Identifier: MIT
// S// SPDX-License-Identifier: MIT
astrimaitis marked this conversation as resolved.
Show resolved Hide resolved
pragma solidity ^0.8.4;

import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol";
Expand All @@ -18,20 +17,28 @@ import "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgrad
/// @dev Each url that enters the registry is mapped to a token id that has the corresponding tokenURI describe above
/// @dev If the url does not have a tokenId minted against it, the contract returns the 'NOT VERIFIED' status

contract Sift is Initializable, ERC721Upgradeable, ERC721URIStorageUpgradeable, ERC721BurnableUpgradeable, AccessControlEnumerableUpgradeable {
contract Sift is Initializable, ERC721Upgradeable, ERC721BurnableUpgradeable, AccessControlEnumerableUpgradeable {
using CountersUpgradeable for CountersUpgradeable.Counter;

CountersUpgradeable.Counter private _tokenIdCounter;

/// @dev mapping of hashed url to tokenId
mapping(bytes32 => uint256) public urlToTokenId;
/// @dev mapping of hashed label to tokenId (i.e. a URL, Ticker, )
mapping(bytes32 => uint256) public labelToTokenId;

/// @dev Base uri of Sift
string public baseURI;

/// @dev Total supply of Sift tokens
uint256 public totalSupply;

/// @dev Order struct
struct entityStruct {
string metadata; /// this can be JSON i.e. a string
uint256 status; /// i.e. Verified: 0, not_verified: 1, malicious: 2
}

mapping(uint256 => entityStruct) public tokenIDtoEntity;

/// @dev Role bytes
bytes32 public constant VERIFIER_ROLE = keccak256("VERIFIER_ROLE");

Expand All @@ -44,7 +51,6 @@ contract Sift is Initializable, ERC721Upgradeable, ERC721URIStorageUpgradeable,
/// @dev Uses the initializer modifier to to ensure the contract is only initialized once
function initialize(string memory baseURI_) initializer public {
__ERC721_init("Sift", "SIFT");
__ERC721URIStorage_init();
__ERC721Burnable_init();

_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
Expand All @@ -56,39 +62,57 @@ contract Sift is Initializable, ERC721Upgradeable, ERC721URIStorageUpgradeable,
/// @notice Verifies a url
/// @dev Mints an NFT with the 'VERIFIED' tokenURI
/// @dev Only addresses with VERIFIER_ROLE can call it and is checked in _safeMintAndRegister()
/// @param url Site URL
/// @param owner Address receiving the url's NFT
function verifyURL(string memory url, address owner) external {
/// @param label human-readable object label
/// @param owner Address receiving the url's NFT
/// @param metadata stringified JSON object with useful keyvalue pairs
function verifyEntity(string memory label, address owner, string memory metadata) external {
// check if the url has already been verified on the contract
// if it has a token id mapped to it, it has been verified
require(urlToTokenId[keccak256(abi.encodePacked(url))] == 0, "Consent: URL already verified");
require(labelToTokenId[keccak256(abi.encodePacked(label))] == 0, "Consent: URL already verified");

// mint token id and append to the token URI "VERIFIED"
_safeMintAndRegister(owner, "VERIFIED", url);
// mint token with the associated label and metadata for a status of 1 (which means its safe)
_safeMintAndRegister(owner, 1, label, metadata);
}

/// @notice Marks a url as malicious
/// @dev Mints an NFT with the 'MALICIOUS' tokenURI
/// @dev Only addresses with VERIFIER_ROLE can call it and is checked in _safeMintAndRegister()
/// @param url Site URL
/// @param label human-readable object label
/// @param owner Address receiving the url's NFT
function maliciousURL(string memory url, address owner) external {
/// @param metadata stringified JSON object with useful keyvalue pairs
function maliciousEntity(string memory label, address owner, string memory metadata) external {
// check if the url has already been verified on the contract
// if it has a token id mapped to it, it has been verified
require(labelToTokenId[keccak256(abi.encodePacked(label))] == 0, "Consent: URL already verified");
astrimaitis marked this conversation as resolved.
Show resolved Hide resolved

// mint token id and append to the token URI "MALICIOUS"
_safeMintAndRegister(owner, "MALICIOUS", url);
_safeMintAndRegister(owner, 2, label, metadata);
}

/// @notice Checks the status of a url
astrimaitis marked this conversation as resolved.
Show resolved Hide resolved
/// @param url Site URL
/// @dev Returns status of entities
/// @param labels human-readable object labels
/// @return result Returns the token uri of 'VERIFIED', 'MALICIOUS', or 'NOT VERIFIED'
function checkURL(string memory url) external view returns(string memory result) {
function checkEntities(string[] memory labels) external view returns(entityStruct[] memory result) {
// get the url's token using its hashed value
uint256 tokenId = urlToTokenId[keccak256(abi.encodePacked(url))];
entityStruct[] memory returnedValues = new entityStruct[](labels.length);

// if token's id is 0, it has not been verified yet
if (tokenId == 0) return "NOT VERIFIED";
for (uint i = 0; i < labels.length; i++) {
uint256 tokenId = labelToTokenId[keccak256(abi.encodePacked(labels[i]))];

// else, return token's URI
return tokenURI(tokenId);
// if token's id is 0, it has not been verified yet
if (tokenId == 0) {
returnedValues[i] = entityStruct("", 0);
}
else
{
// else, return token's entityStruct
returnedValues[i] = tokenIDtoEntity[tokenId];
}

}

return returnedValues;
}

/// @notice Sets the Sift tokens base URI
Expand All @@ -99,22 +123,29 @@ contract Sift is Initializable, ERC721Upgradeable, ERC721URIStorageUpgradeable,

/// @notice Internal function to carry out token minting and mapping updates
/// @param to Address receiving the token
/// @param uri Token uri containing status
/// @param url Site URL
function _safeMintAndRegister(address to, string memory uri, string memory url) internal onlyRole(VERIFIER_ROLE) {
/// @param verifiedStatus Status passed from the token
/// @param label Status passed from the token
/// @param metadata Token's metadata
function _safeMintAndRegister(address to, uint256 verifiedStatus, string memory label, string memory metadata) internal onlyRole(VERIFIER_ROLE) {
astrimaitis marked this conversation as resolved.
Show resolved Hide resolved
// ensure that tokenIds start from 1 so that 0 can be kept as tokens that are not verified yet
uint256 tokenId = _tokenIdCounter.current() + 1;
_tokenIdCounter.increment();
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);

// register hashed url to token mapping
urlToTokenId[keccak256(abi.encodePacked(url))] = tokenId;
labelToTokenId[keccak256(abi.encodePacked(label))] = tokenId;

/// set the metadata
tokenIDtoEntity[tokenId] = entityStruct(metadata, verifiedStatus);

/// increase total supply count
totalSupply++;
}

function returnWhiteListCount() external view returns(uint256 Val) {
astrimaitis marked this conversation as resolved.
Show resolved Hide resolved
return totalSupply;
}

/* OVERRIDES */

/// @notice Override _baseURI to return the Sift tokens base URI
Expand All @@ -124,25 +155,6 @@ contract Sift is Initializable, ERC721Upgradeable, ERC721URIStorageUpgradeable,

// The following functions are overrides required by Solidity.

function _burn(uint256 tokenId)
internal
override(ERC721Upgradeable, ERC721URIStorageUpgradeable)
{
super._burn(tokenId);

/// decrease total supply count
totalSupply--;
}

function tokenURI(uint256 tokenId)
public
view
override(ERC721Upgradeable, ERC721URIStorageUpgradeable)
returns (string memory)
{
return super.tokenURI(tokenId);
}

function supportsInterface(bytes4 interfaceId)
public
view
Expand Down
84 changes: 65 additions & 19 deletions packages/contracts/test/sift.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,20 @@ describe("Sift", () => {
await sift.deployed();
});

describe("verifyURL", function () {
describe("verifyEntity", function () {
it("Allows address with VERIFIER_ROLE to verify a url.", async function () {
// accounts 1 creates a crumb
await sift.connect(owner).verifyURL("www.uniswap.com", owner.address);
await sift
.connect(owner)
.verifyEntity("www.uniswap.com", owner.address, "website metadata");

// check token balance of the account has 1
const tokenCount = await sift.balanceOf(owner.address);
expect(tokenCount).to.eq(1);

// check token's uri
const tokenURI = await sift.tokenURI(1);
expect(tokenURI).to.eq("www.sift.com/VERIFIED");
expect(tokenURI).to.eq("www.sift.com/1");

// check total supply
const totalSupply = await sift.totalSupply();
Expand All @@ -45,36 +47,44 @@ describe("Sift", () => {
it("Does not allow address without VERIFIER_ROLE to verify a url.", async function () {
// account 1 verifies a url
await expect(
sift.connect(accounts[1]).verifyURL("www.uniswap.com", owner.address),
sift
.connect(accounts[1])
.verifyEntity("www.uniswap.com", owner.address, "website metadata"),
).to.revertedWith(
`AccessControl: account ${accounts[1].address.toLowerCase()} is missing role ${verifierRoleBytes}`,
);
});

it("Does not allow user to verify a url twice.", async function () {
// account 1 verifies a url
await sift.connect(owner).verifyURL("www.uniswap.com", owner.address);
await sift
.connect(owner)
.verifyEntity("www.uniswap.com", owner.address, "website metadata");

//account 1 tries to verify the same url

await expect(
sift.connect(owner).verifyURL("www.uniswap.com", owner.address),
sift
.connect(owner)
.verifyEntity("www.uniswap.com", owner.address, "website metadata"),
).to.revertedWith("Consent: URL already verified");
});
});

describe("maliciousURL", function () {
describe("maliciousEntity", function () {
it("Allows address with VERIFIER_ROLE to register a url as malicious.", async function () {
// accounts 1 creates a crumb
await sift.connect(owner).maliciousURL("www.uniswop.com", owner.address);
await sift
.connect(owner)
.maliciousEntity("www.uniswop.com", owner.address, "website metadata");

// check token balance of the account has 1
const tokenCount = await sift.balanceOf(owner.address);
expect(tokenCount).to.eq(1);

// check token's uri
const tokenURI = await sift.tokenURI(1);
expect(tokenURI).to.eq("www.sift.com/MALICIOUS");
expect(tokenURI).to.eq("www.sift.com/1");

// check total supply
const totalSupply = await sift.totalSupply();
Expand All @@ -86,36 +96,72 @@ describe("Sift", () => {
await expect(
sift
.connect(accounts[1])
.maliciousURL("www.uniswpp.com", owner.address),
.maliciousEntity(
"www.uniswpp.com",
owner.address,
"website metadata",
),
).to.revertedWith(
`AccessControl: account ${accounts[1].address.toLowerCase()} is missing role ${verifierRoleBytes}`,
);
});
});

describe("checkURL", function () {
describe("checkEntities", function () {
it("Returns the VERIFIED URI for a verified URL", async function () {
// accounts 1 creates a crumb
await sift.connect(owner).verifyURL("www.uniswap.com", owner.address);
await sift
.connect(owner)
.verifyEntity("www.uniswap.com", owner.address, "website metadata");

// check url
const result = await sift.checkURL("www.uniswap.com");
expect(result).to.eq("www.sift.com/VERIFIED");
const result = await sift.checkEntities(["www.uniswap.com"]);
expect(result[0]["metadata"]).to.eq("website metadata");
expect(result[0]["status"]).to.eq(1);
});

it("Returns the MALICIOUS URI for a verified URL", async function () {
// accounts 1 creates a crumb
await sift.connect(owner).maliciousURL("www.uniswop.com", owner.address);
await sift
.connect(owner)
.maliciousEntity("www.uniswop.com", owner.address, "website metadata");

// check url
const result = await sift.checkURL("www.uniswop.com");
expect(result).to.eq("www.sift.com/MALICIOUS");
const result = await sift.checkEntities(["www.uniswop.com"]);
expect(result[0]["metadata"]).to.eq("website metadata");
expect(result[0]["status"]).to.eq(2);
});

it("Returns the NOT VERIFIED string for a URL that has not been registered", async function () {
// check url
const result = await sift.checkURL("www.test.com");
expect(result).to.eq("NOT VERIFIED");
const result = await sift.checkEntities(["www.test.com"]);
expect(result[0]["status"]).to.eq(0);
});

it("Returns 3 values: 1 verfied, 1 malicious, 1 not verified", async function () {
// check url
// accounts 1 creates a crumb
await sift
.connect(owner)
.verifyEntity("www.uniswap.com", owner.address, "good metadata");
await sift
.connect(owner)
.maliciousEntity("www.uniswop.com", owner.address, "bad metadata");

// check url
const result = await sift.checkEntities([
"www.uniswap.com",
"www.uniswop.com",
"AVAX NFT",
]);
console.log("result: ", result);
expect(result[0]["metadata"]).to.eq("good metadata");
expect(result[0]["status"]).to.eq(1);

expect(result[1]["metadata"]).to.eq("bad metadata");
expect(result[1]["status"]).to.eq(2);

expect(result[2]["status"]).to.eq(0);
});
});

Expand Down