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

Allow to restrict minters #4

Closed
wants to merge 12 commits into from
70 changes: 70 additions & 0 deletions tokens/contracts/access/MinterAccessControl.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract MinterAccessControl is Initializable, OwnableUpgradeable {
mapping (address => bool) private _minters;
bool public minterAccessControlEnabled;

event MinterAccessControlEnabled();
event MinterAccessControlDisabled();
event MinterGranted(address indexed account);
event MinterRevoked(address indexed account);

function __MinterAccessControl_init() internal initializer {
__Ownable_init_unchained();
__MinterAccessControl_init_unchained();
}

function __MinterAccessControl_init_unchained() internal initializer {
}

/**
* @dev Enable minter control
* When enabled, only addresses added to `grantMinter` will be allowed to mint
*/
function enableMinterAccessControl() external onlyOwner {
require(!minterAccessControlEnabled, "MinterAccessControl: Already enabled");
minterAccessControlEnabled = true;
emit MinterAccessControlEnabled();
}

/**
* @dev Disable minter control
*/
function disableMinterAccessControl() external onlyOwner {
require(minterAccessControlEnabled, "MinterAccessControl: Already disabled");
minterAccessControlEnabled = false;
emit MinterAccessControlDisabled();
}

/**
* @dev Add `_minter` to the list of allowed minters.
*/
function grantMinter(address _minter) external onlyOwner {
require(!_minters[_minter], 'MinterAccessControl: Already minter');
_minters[_minter] = true;
emit MinterGranted(_minter);
}

/**
* @dev Revoke `_minter` from the list of allowed minters.
*/
function revokeMinter(address _minter) external onlyOwner {
require(_minters[_minter], 'MinterAccessControl: Not minter');
_minters[_minter] = false;
emit MinterRevoked(_minter);
}

/**
* @dev Returns `true` if minterControl is not enabled or `account` has been granted to minters.
*/
function isValidMinter(address account) public view returns (bool) {
return !minterAccessControlEnabled || _minters[account];
}

uint256[50] private __gap;
}
4 changes: 3 additions & 1 deletion tokens/contracts/erc-1155/ERC1155Lazy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import "@rarible/royalties-upgradeable/contracts/RoyaltiesV2Upgradeable.sol";
import "@rarible/lazy-mint/contracts/erc-1155/IERC1155LazyMint.sol";
import "./Mint1155Validator.sol";
import "./ERC1155BaseURI.sol";
import "../access/MinterAccessControl.sol";

abstract contract ERC1155Lazy is IERC1155LazyMint, ERC1155BaseURI, Mint1155Validator, RoyaltiesV2Upgradeable, RoyaltiesV2Impl {
abstract contract ERC1155Lazy is IERC1155LazyMint, ERC1155BaseURI, Mint1155Validator, RoyaltiesV2Upgradeable, RoyaltiesV2Impl, MinterAccessControl {
using SafeMathUpgradeable for uint;

bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
Expand Down Expand Up @@ -59,6 +60,7 @@ abstract contract ERC1155Lazy is IERC1155LazyMint, ERC1155BaseURI, Mint1155Valid
address sender = _msgSender();

require(minter == sender || isApprovedForAll(minter, sender), "ERC1155: transfer caller is not approved");
require(isValidMinter(minter), "ERC1155: minter not granted");
require(_amount > 0, "amount incorrect");

if (supply[data.tokenId] == 0) {
Expand Down
1 change: 1 addition & 0 deletions tokens/contracts/erc-1155/ERC1155Rarible.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ contract ERC1155Rarible is ERC1155Base {
__ERC1155Burnable_init_unchained();
__RoyaltiesV2Upgradeable_init_unchained();
__ERC1155Base_init_unchained(_name, _symbol);
__MinterAccessControl_init_unchained();
_setBaseURI(baseURI);
}

Expand Down
4 changes: 3 additions & 1 deletion tokens/contracts/erc-721-minimal/ERC721LazyMinimal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import "@rarible/lazy-mint/contracts/erc-721/IERC721LazyMint.sol";
import "../Mint721Validator.sol";
import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol";
import "./ERC721URI.sol";
import "../access/MinterAccessControl.sol";

abstract contract ERC721LazyMinimal is IERC721LazyMint, ERC721UpgradeableMinimal, Mint721Validator, RoyaltiesV2Upgradeable, RoyaltiesV2Impl, ERC721URI {
abstract contract ERC721LazyMinimal is IERC721LazyMint, ERC721UpgradeableMinimal, Mint721Validator, RoyaltiesV2Upgradeable, RoyaltiesV2Impl, ERC721URI, MinterAccessControl {
using SafeMathUpgradeable for uint;

bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
Expand Down Expand Up @@ -54,6 +55,7 @@ abstract contract ERC721LazyMinimal is IERC721LazyMint, ERC721UpgradeableMinimal
require(minter == data.creators[0].account, "tokenId incorrect");
require(data.creators.length == data.signatures.length);
require(minter == sender || isApprovedForAll(minter, sender), "ERC721: transfer caller is not owner nor approved");
require(isValidMinter(minter), "ERC721: minter not granted");

bytes32 hash = LibERC721LazyMint.hash(data);
for (uint i = 0; i < data.creators.length; i++) {
Expand Down
1 change: 1 addition & 0 deletions tokens/contracts/erc-721-minimal/ERC721RaribleMinimal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ contract ERC721RaribleMinimal is ERC721BaseMinimal {
__Ownable_init_unchained();
__ERC721Burnable_init_unchained();
__Mint721Validator_init_unchained();
__MinterAccessControl_init_unchained();
__HasContractURI_init_unchained(contractURI);
__ERC721_init_unchained(_name, _symbol);
emit CreateERC721Rarible(_msgSender(), _name, _symbol);
Expand Down
4 changes: 3 additions & 1 deletion tokens/contracts/erc-721/ERC721Lazy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import "@rarible/royalties/contracts/impl/RoyaltiesV2Impl.sol";
import "@rarible/royalties-upgradeable/contracts/RoyaltiesV2Upgradeable.sol";
import "@rarible/lazy-mint/contracts/erc-721/IERC721LazyMint.sol";
import "../Mint721Validator.sol";
import "../access/MinterAccessControl.sol";

abstract contract ERC721Lazy is IERC721LazyMint, ERC721Upgradeable, Mint721Validator, RoyaltiesV2Upgradeable, RoyaltiesV2Impl {
abstract contract ERC721Lazy is IERC721LazyMint, ERC721Upgradeable, Mint721Validator, RoyaltiesV2Upgradeable, RoyaltiesV2Impl, MinterAccessControl {
using SafeMathUpgradeable for uint;
using EnumerableSetUpgradeable for EnumerableSetUpgradeable.UintSet;
using EnumerableMapUpgradeable for EnumerableMapUpgradeable.UintToAddressMap;
Expand Down Expand Up @@ -54,6 +55,7 @@ abstract contract ERC721Lazy is IERC721LazyMint, ERC721Upgradeable, Mint721Valid
require(minter == data.creators[0].account, "tokenId incorrect");
require(data.creators.length == data.signatures.length);
require(minter == sender || isApprovedForAll(minter, sender), "ERC721: transfer caller is not owner nor approved");
require(isValidMinter(minter), "ERC721: minter not granted");

bytes32 hash = LibERC721LazyMint.hash(data);
for (uint i = 0; i < data.creators.length; i++) {
Expand Down
1 change: 1 addition & 0 deletions tokens/contracts/erc-721/ERC721Rarible.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ contract ERC721Rarible is ERC721Base {
__Ownable_init_unchained();
__ERC721Burnable_init_unchained();
__Mint721Validator_init_unchained();
__MinterAccessControl_init_unchained();
__HasContractURI_init_unchained(contractURI);
__ERC721_init_unchained(_name, _symbol);
}
Expand Down
87 changes: 87 additions & 0 deletions tokens/test/erc-1155/ERC1155Rarible.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,93 @@ contract("ERC1155Rarible", accounts => {
);
});

it("mint and transfer with minter access control", async () => {
const minter = accounts[1];
let transferTo = accounts[2];

const tokenId = minter + "b00000000000000000000001";
const tokenURI = "//uri";
let supply = 5;
let mint = 2;

await token.enableMinterAccessControl({from: tokenOwner});
assert.equal(await token.minterAccessControlEnabled(), true);

await expectThrow(
token.mintAndTransfer([tokenId, tokenURI, supply, creators([minter]), [], [zeroWord]], transferTo, mint, {from: minter})
);

await token.grantMinter(minter, {from: tokenOwner});
assert.equal(await token.isValidMinter(minter), true);
assert.equal(await token.isValidMinter(transferTo), false);

await token.mintAndTransfer([tokenId, tokenURI, supply, creators([minter]), [], [zeroWord]], transferTo, mint, {from: minter});
assert.equal(await token.uri(tokenId), "ipfs:/" + tokenURI);
assert.equal(await token.balanceOf(transferTo, tokenId), mint);
assert.equal(await token.balanceOf(minter, tokenId), 0);
});

it("mint and transfer with minter access control and minter signature", async () => {
const minter = accounts[1];
let transferTo = accounts[2];

const tokenId = minter + "b00000000000000000000001";
const tokenURI = "//uri";
let supply = 5;
let mint = 2;

const signature = await getSignature(tokenId, tokenURI, supply, creators([minter]), [], minter);

let whiteListProxy = accounts[5];
await token.setDefaultApproval(whiteListProxy, true, {from: tokenOwner});

await token.enableMinterAccessControl({from: tokenOwner});
assert.equal(await token.minterAccessControlEnabled(), true);

await expectThrow(
token.mintAndTransfer([tokenId, tokenURI, supply, creators([minter]), [], [signature]], transferTo, mint, {from: whiteListProxy})
);

await token.grantMinter(minter, {from: tokenOwner});
assert.equal(await token.isValidMinter(minter), true);
assert.equal(await token.isValidMinter(whiteListProxy), false);

await token.mintAndTransfer([tokenId, tokenURI, supply, creators([minter]), [], [signature]], transferTo, mint, {from: whiteListProxy})
assert.equal(await token.uri(tokenId), "ipfs:/" + tokenURI);
assert.equal(await token.balanceOf(transferTo, tokenId), mint);
assert.equal(await token.balanceOf(minter, tokenId), 0);
});

it("mint and transfer with minter access control and wrong minter signature", async () => {
const minter = accounts[1];
let transferTo = accounts[2];

const tokenId = minter + "b00000000000000000000001";
const tokenURI = "//uri";
let supply = 5;
let mint = 2;

const signature = await getSignature(tokenId, tokenURI, supply, creators([minter]), [], transferTo);

let whiteListProxy = accounts[5];
await token.setDefaultApproval(whiteListProxy, true, {from: tokenOwner});

await token.enableMinterAccessControl({from: tokenOwner});
assert.equal(await token.minterAccessControlEnabled(), true);

await expectThrow(
token.mintAndTransfer([tokenId, tokenURI, supply, creators([minter]), [], [signature]], transferTo, mint, {from: whiteListProxy})
);

await token.grantMinter(minter, {from: tokenOwner});
assert.equal(await token.isValidMinter(minter), true);
assert.equal(await token.isValidMinter(whiteListProxy), false);

await expectThrow(
token.mintAndTransfer([tokenId, tokenURI, supply, creators([minter]), [], [signature]], transferTo, mint, {from: whiteListProxy})
);
});

it("standard transfer from owner", async () => {
let minter = accounts[1];
const tokenId = minter + "b00000000000000000000001";
Expand Down
59 changes: 59 additions & 0 deletions tokens/test/erc-1155/MinterAccessControl1155.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const Testing = artifacts.require("ERC1155Rarible.sol");

const { expectThrow } = require('@daonomic/tests-common');
const { deployProxy, upgradeProxy } = require('@openzeppelin/truffle-upgrades');

function creators(list) {
const value = 10000 / list.length
return list.map(account => ({ account, value }))
}

contract("MinterAccessControl1155", accounts => {
let token;
let tokenOwner = accounts[9];
const zeroWord = "0x0000000000000000000000000000000000000000000000000000000000000000";
const name = 'FreeMintable';
const ZERO = "0x0000000000000000000000000000000000000000";

beforeEach(async () => {
token = await deployProxy(Testing, [name, "TST", "ipfs:/", "ipfs:/"], { initializer: "__ERC1155Rarible_init" });
await token.transferOwnership(tokenOwner);
});

it("conserve minter access control after upgrade", async () => {
const minter = accounts[1];
let transferTo = accounts[2];

const tokenId = minter + "b00000000000000000000001";
const tokenURI = "//uri";
let supply = 5;
let mint = 2;


console.log(`owner: ${await token.owner()}, expected: ${tokenOwner}`);

await token.enableMinterAccessControl({from: tokenOwner});
assert.equal(await token.minterAccessControlEnabled(), true);

await expectThrow(
token.mintAndTransfer([tokenId, tokenURI, supply, creators([minter]), [], [zeroWord]], transferTo, mint, {from: minter})
);

// upgrade contract
const newInstance = await upgradeProxy(token.address, Testing);
assert.equal(await newInstance.minterAccessControlEnabled(), true);

await expectThrow(
newInstance.mintAndTransfer([tokenId, tokenURI, supply, creators([minter]), [], [zeroWord]], transferTo, mint, {from: minter})
);

await newInstance.grantMinter(minter, {from: tokenOwner});
assert.equal(await newInstance.isValidMinter(minter), true);
assert.equal(await newInstance.isValidMinter(transferTo), false);

await newInstance.mintAndTransfer([tokenId, tokenURI, supply, creators([minter]), [], [zeroWord]], transferTo, mint, {from: minter});
assert.equal(await newInstance.uri(tokenId), "ipfs:/" + tokenURI);
assert.equal(await newInstance.balanceOf(transferTo, tokenId), mint);
assert.equal(await newInstance.balanceOf(minter, tokenId), 0);
});
});
53 changes: 53 additions & 0 deletions tokens/test/erc-721/MinterAccessControl721.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const Testing = artifacts.require("ERC721RaribleMinimal.sol");

const { expectThrow } = require('@daonomic/tests-common');
const { deployProxy, upgradeProxy } = require('@openzeppelin/truffle-upgrades');

function creators(list) {
const value = 10000 / list.length
return list.map(account => ({ account, value }))
}

contract("MinterAccessControl721", accounts => {
let token;
let tokenOwner = accounts[9];
const name = 'FreeMintableRarible';
const chainId = 1;
const zeroWord = "0x0000000000000000000000000000000000000000000000000000000000000000";
const ZERO = "0x0000000000000000000000000000000000000000";

beforeEach(async () => {
token = await deployProxy(Testing, [name, "RARI", "https://ipfs.rarible.com", "https://ipfs.rarible.com"], { initializer: "__ERC721Rarible_init" });
await token.transferOwnership(tokenOwner);
});

it("conserve minter access control after upgrade", async () => {
const minter = accounts[1];
let transferTo = accounts[2];

const tokenId = minter + "b00000000000000000000001";
const tokenURI = "//uri";

await token.enableMinterAccessControl({from: tokenOwner});
assert.equal(await token.minterAccessControlEnabled(), true);

await expectThrow(
token.mintAndTransfer([tokenId, tokenURI, creators([minter]), [], [zeroWord]], transferTo, {from: minter})
);

// upgrade contract
const newInstance = await upgradeProxy(token.address, Testing);
assert.equal(await newInstance.minterAccessControlEnabled(), true);

await expectThrow(
newInstance.mintAndTransfer([tokenId, tokenURI, creators([minter]), [], [zeroWord]], transferTo, {from: minter})
);

await newInstance.grantMinter(minter, {from: tokenOwner})
assert.equal(await newInstance.isValidMinter(minter), true);
assert.equal(await newInstance.isValidMinter(transferTo), false);

await token.mintAndTransfer([tokenId, tokenURI, creators([minter]), [], [zeroWord]], transferTo, {from: minter})
assert.equal(await token.ownerOf(tokenId), transferTo);
});
});
Loading