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

feat(nfts): blacklist integration #17217

Merged
merged 13 commits into from
May 20, 2024
Merged
38 changes: 33 additions & 5 deletions packages/taikoon/contracts/MerkleWhitelist.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,61 @@ import { Ownable2StepUpgradeable } from
import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import { ContextUpgradeable } from
"@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import { IMinimalBlacklist } from "@taiko/blacklist/IMinimalBlacklist.sol";

/// @title MerkleWhitelist
/// @dev Merkle Tree Whitelist
/// @custom:security-contact [email protected]
contract MerkleWhitelist is ContextUpgradeable, UUPSUpgradeable, Ownable2StepUpgradeable {
event RootUpdated(bytes32 _root);
event MintConsumed(address _minter, uint256 _mintAmount);
event BlacklistUpdated(address _blacklist);

error MINTS_EXCEEDED();
error INVALID_PROOF();
error INVALID_TOKEN_AMOUNT();
error ADDRESS_BLACKLISTED();

/// @notice Merkle Tree Root
bytes32 public root;
/// @notice Tracker for minted leaves
mapping(bytes32 leaf => bool hasMinted) public minted;

uint256[48] private __gap;
/// @notice Blackist address
IMinimalBlacklist public blacklist;
/// @notice Gap for upgrade safety
uint256[47] private __gap;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

/// @notice Update the blacklist address
/// @param _blacklist The new blacklist address
function updateBlacklist(IMinimalBlacklist _blacklist) external onlyOwner {
blacklist = _blacklist;
emit BlacklistUpdated(address(_blacklist));
}

/// @notice Contract initializer
/// @param _root Merkle Tree root
function initialize(address _owner, bytes32 _root) external initializer {
__MerkleWhitelist_init(_owner, _root);
function initialize(
address _owner,
bytes32 _root,
IMinimalBlacklist _blacklist
)
external
initializer
{
__MerkleWhitelist_init(_owner, _root, _blacklist);
}

/// @notice Check if a wallet can free mint
/// @param _minter Address of the minter
/// @param _maxMints Max amount of free mints
/// @return Whether the wallet can mint
function canMint(address _minter, uint256 _maxMints) public view returns (bool) {
if (blacklist.isBlacklisted(_minter)) revert ADDRESS_BLACKLISTED();
bytes32 _leaf = leaf(_minter, _maxMints);
return !minted[_leaf];
}
Expand All @@ -57,10 +77,18 @@ contract MerkleWhitelist is ContextUpgradeable, UUPSUpgradeable, Ownable2StepUpg

/// @notice Internal initializer
/// @param _root Merkle Tree root
function __MerkleWhitelist_init(address _owner, bytes32 _root) internal initializer {
function __MerkleWhitelist_init(
address _owner,
bytes32 _root,
IMinimalBlacklist _blacklist
)
internal
initializer
{
_transferOwnership(_owner == address(0) ? msg.sender : _owner);
__Context_init();
root = _root;
blacklist = _blacklist;
}

/// @notice Update the merkle tree's root
Expand Down
12 changes: 7 additions & 5 deletions packages/taikoon/contracts/TaikoonToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ import { ERC721EnumerableUpgradeable } from
"@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";

import { MerkleWhitelist } from "./MerkleWhitelist.sol";
import { IMinimalBlacklist } from "@taiko/blacklist/IMinimalBlacklist.sol";

/// @title TaikoonToken
/// @dev The Taikoons ERC-721 token
/// @custom:security-contact [email protected]
contract TaikoonToken is ERC721EnumerableUpgradeable, MerkleWhitelist {
/// @notice The current supply
uint256 private _totalSupply;
// Base URI required to interact with IPFS
/// @notice Base URI required to interact with IPFS
string private _baseURIExtended;

uint256[48] private __gap;
/// @notice Gap for upgrade safety
uint256[47] private __gap;

error MAX_MINTS_EXCEEDED();
error MAX_SUPPLY_REACHED();
Expand All @@ -30,13 +31,14 @@ contract TaikoonToken is ERC721EnumerableUpgradeable, MerkleWhitelist {
function initialize(
address _owner,
string memory _rootURI,
bytes32 _merkleRoot
bytes32 _merkleRoot,
IMinimalBlacklist _blacklistAddress
)
external
initializer
{
__ERC721_init("Taikoon", "TKOON");
__MerkleWhitelist_init(_owner, _merkleRoot);
__MerkleWhitelist_init(_owner, _merkleRoot, _blacklistAddress);
_baseURIExtended = _rootURI;
}

Expand Down
1 change: 1 addition & 0 deletions packages/taikoon/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ remappings = [
"p256-verifier/=node_modules/p256-verifier/",
"murky/=node_modules/murky/src/",
"solidity-stringutils/=node_modules/solidity-stringutils/",
"@taiko/blacklist/=node_modules/@taiko/supplementary-contracts/contracts/blacklist/",
]

# Do not change the block_gas_limit value, TaikoL2.t.sol depends on it.
Expand Down
3 changes: 2 additions & 1 deletion packages/taikoon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"p256-verifier": "github:taikoxyz/p256-verifier#v0.1.0",
"sharp": "^0.33.3",
"solady": "github:Vectorized/solady#v0.0.167",
"solidity-stringutils": "github:Arachnid/solidity-stringutils"
"solidity-stringutils": "github:Arachnid/solidity-stringutils",
"@taiko/supplementary-contracts": "workspace:*"
}
}
7 changes: 6 additions & 1 deletion packages/taikoon/script/sol/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ contract DeployScript is Script {
// deploy token with empty root
address impl = address(new TaikoonToken());
address proxy = address(
new ERC1967Proxy(impl, abi.encodeCall(TaikoonToken.initialize, (owner, baseURI, root)))
new ERC1967Proxy(
impl,
abi.encodeCall(
TaikoonToken.initialize, (owner, baseURI, root, utils.getBlacklist())
)
)
);

TaikoonToken token = TaikoonToken(proxy);
Expand Down
17 changes: 17 additions & 0 deletions packages/taikoon/script/sol/Utils.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pragma solidity 0.8.24;

import { Script, console } from "forge-std/src/Script.sol";
import "forge-std/src/StdJson.sol";
import { IMinimalBlacklist } from "@taiko/blacklist/IMinimalBlacklist.sol";
import { MockBlacklist } from "../../test/Blacklist.sol";

contract UtilsScript is Script {
using stdJson for string;
Expand All @@ -27,6 +29,9 @@ contract UtilsScript is Script {
} else if (chainId == 167_001) {
lowercaseNetworkKey = "devnet";
uppercaseNetworkKey = "DEVNET";
} else if (chainId == 11_155_111) {
lowercaseNetworkKey = "sepolia";
uppercaseNetworkKey = "SEPOLIA";
} else if (chainId == 167_008) {
lowercaseNetworkKey = "katla";
uppercaseNetworkKey = "KATLA";
Expand Down Expand Up @@ -54,5 +59,17 @@ contract UtilsScript is Script {
return vm.envString("IPFS_BASE_URI");
}

function getBlacklist() public returns (IMinimalBlacklist blacklistAddress) {
if (block.chainid == 1) {
// mainnet blacklist address
blacklistAddress = IMinimalBlacklist(0x97044531D0fD5B84438499A49629488105Dc58e6);
} else {
// deploy a mock blacklist otherwise
blacklistAddress = IMinimalBlacklist(new MockBlacklist());
}

return blacklistAddress;
}

function run() public { }
}
27 changes: 27 additions & 0 deletions packages/taikoon/test/Blacklist.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { IMinimalBlacklist } from "@taiko/blacklist/IMinimalBlacklist.sol";

// Blacklist contract mock
contract MockBlacklist is IMinimalBlacklist {
address[] public blacklist;

constructor() {
// hardhat accounts, #5 to #9
blacklist.push(0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc);
blacklist.push(0x976EA74026E726554dB657fA54763abd0C3a0aa9);
blacklist.push(0x14dC79964da2C08b23698B3D3cc7Ca32193d9955);
blacklist.push(0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f);
blacklist.push(0xa0Ee7A142d267C1f36714E4a8F75612F20a79720);
}

function isBlacklisted(address _address) public view returns (bool) {
for (uint256 i = 0; i < blacklist.length; i++) {
if (blacklist[i] == _address) {
return true;
}
}
return false;
}
}
15 changes: 14 additions & 1 deletion packages/taikoon/test/MerkleWhitelist.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "forge-std/src/StdJson.sol";
import { Merkle } from "murky/Merkle.sol";
import { MerkleWhitelist } from "../contracts/MerkleWhitelist.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { UtilsScript } from "../script/sol/Utils.s.sol";

/// @custom:oz-upgrades-from MerkleWhitelist
contract MerkleWhitelistForTest is MerkleWhitelist {
Expand All @@ -20,6 +21,7 @@ contract MerkleWhitelistForTest is MerkleWhitelist {

contract MerkleWhitelistTest is Test {
Merkle tree;
UtilsScript public utils;

using stdJson for string;

Expand All @@ -38,6 +40,8 @@ contract MerkleWhitelistTest is Test {
address[3] minters = [address(0x1), address(0x2), address(0x3)];

function setUp() public {
utils = new UtilsScript();
utils.setUp();
vm.startBroadcast(owner);

tree = new Merkle();
Expand All @@ -51,7 +55,10 @@ contract MerkleWhitelistTest is Test {

address impl = address(new MerkleWhitelistForTest());
address proxy = address(
new ERC1967Proxy(impl, abi.encodeCall(MerkleWhitelist.initialize, (address(0), root)))
new ERC1967Proxy(
impl,
abi.encodeCall(MerkleWhitelist.initialize, (address(0), root, utils.getBlacklist()))
)
);

whitelist = MerkleWhitelistForTest(proxy);
Expand Down Expand Up @@ -113,4 +120,10 @@ contract MerkleWhitelistTest is Test {

vm.stopBroadcast();
}

function test_revert_canMint_blacklisted() public {
address blacklisted = 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc;
vm.expectRevert();
whitelist.canMint(blacklisted, MAX_MINTS);
}
}
20 changes: 19 additions & 1 deletion packages/taikoon/test/TaikoonToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import { Test } from "forge-std/src/Test.sol";
import { TaikoonToken } from "../contracts/TaikoonToken.sol";
import { Merkle } from "murky/Merkle.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { UtilsScript } from "../script/sol/Utils.s.sol";

contract TaikoonTokenTest is Test {
UtilsScript public utils;

TaikoonToken public token;

address public owner = vm.addr(0x5);
Expand All @@ -20,6 +23,8 @@ contract TaikoonTokenTest is Test {
Merkle tree = new Merkle();

function setUp() public {
utils = new UtilsScript();
utils.setUp();
// create whitelist merkle tree
vm.startBroadcast(owner);
bytes32 root = tree.getRoot(leaves);
Expand All @@ -28,7 +33,10 @@ contract TaikoonTokenTest is Test {
address impl = address(new TaikoonToken());
address proxy = address(
new ERC1967Proxy(
impl, abi.encodeCall(TaikoonToken.initialize, (address(0), "ipfs://", root))
impl,
abi.encodeCall(
TaikoonToken.initialize, (address(0), "ipfs://", root, utils.getBlacklist())
)
)
);

Expand Down Expand Up @@ -97,4 +105,14 @@ contract TaikoonTokenTest is Test {
assertEq(token.balanceOf(owner), 5);
assertEq(tokenIds.length, 5);
}

function test_revert_mint_blacklisted() public {
address blacklisted =
vm.addr(0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6);
vm.startBroadcast(blacklisted);
bytes32[] memory fakeProof = tree.getProof(leaves, 0);
vm.expectRevert();
token.mint(fakeProof, MAX_MINTS);
vm.stopBroadcast();
}
}
10 changes: 9 additions & 1 deletion packages/taikoon/test/Upgradeable.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ import { TaikoonToken } from "../contracts/TaikoonToken.sol";
import { Merkle } from "murky/Merkle.sol";
import { MerkleMintersScript } from "../script/sol/MerkleMinters.s.sol";
import "forge-std/src/StdJson.sol";
import { UtilsScript } from "../script/sol/Utils.s.sol";

import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract UpgradeableTest is Test {
using stdJson for string;

UtilsScript public utils;

TaikoonToken public token;

address public owner = vm.addr(0x5);
Expand All @@ -26,6 +29,8 @@ contract UpgradeableTest is Test {
Merkle tree = new Merkle();

function setUp() public {
utils = new UtilsScript();
utils.setUp();
// create whitelist merkle tree
vm.startPrank(owner);
bytes32 root = tree.getRoot(leaves);
Expand All @@ -34,7 +39,10 @@ contract UpgradeableTest is Test {
address impl = address(new TaikoonToken());
address proxy = address(
new ERC1967Proxy(
impl, abi.encodeCall(TaikoonToken.initialize, (address(0), "ipfs://", root))
impl,
abi.encodeCall(
TaikoonToken.initialize, (address(0), "ipfs://", root, utils.getBlacklist())
)
)
);

Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.