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(taikoon): geoblocking & address blacklist #16964

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/taikoon/contracts/IMinimalBlacklist.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/// @title Minimal Blacklist Interface
/// @dev Mainnet blacklist: 0x97044531D0fD5B84438499A49629488105Dc58e6
interface IMinimalBlacklist {
function isBlacklisted(address _account) external view returns (bool);
}
34 changes: 30 additions & 4 deletions packages/taikoon/contracts/MerkleWhitelist.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Ownable2StepUpgradeable } from
import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import { ContextUpgradeable } from
"@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import { IMinimalBlacklist } from "./IMinimalBlacklist.sol";

/// @title MerkleWhitelist
/// @dev Merkle Tree Whitelist
Expand All @@ -19,30 +20,47 @@ contract MerkleWhitelist is ContextUpgradeable, UUPSUpgradeable, Ownable2StepUpg
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;

/// @notice Blackist address
IMinimalBlacklist public blacklist;
/// @notice Gap for upgrade safety
uint256[48] 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;
}

/// @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 +75,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
7 changes: 5 additions & 2 deletions packages/taikoon/contracts/TaikoonToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ERC721EnumerableUpgradeable } from
"@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";

import { MerkleWhitelist } from "./MerkleWhitelist.sol";
import { IMinimalBlacklist } from "./IMinimalBlacklist.sol";

/// @title TaikoonToken
/// @dev The Taikoons ERC-721 token
Expand All @@ -17,6 +18,7 @@ contract TaikoonToken is ERC721EnumerableUpgradeable, MerkleWhitelist {
// Base URI required to interact with IPFS
string private _baseURIExtended;

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

error MAX_MINTS_EXCEEDED();
Expand All @@ -30,13 +32,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
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
14 changes: 14 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 "../../contracts/IMinimalBlacklist.sol";
import { MockBlacklist } from "../../test/Blacklist.sol";

contract UtilsScript is Script {
using stdJson for string;
Expand Down Expand Up @@ -57,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 "../contracts/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;
}
}
16 changes: 15 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,11 @@ contract MerkleWhitelistTest is Test {

vm.stopBroadcast();
}

function test_revert_canMint_blacklisted() public {
address blacklisted =
vm.addr(0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6);
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