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

Minter Access Control #105

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d95bcc1
Add MintControl contract
Hadrienlc Nov 9, 2021
774045b
Add MintControl to ERC721Rarible
Hadrienlc Nov 9, 2021
adcede4
Add MintControl to ERC721RaribleMinimal
Hadrienlc Nov 9, 2021
9f5ba6a
Add MintControl to ERC1155Rarible
Hadrienlc Nov 9, 2021
43e59ba
Add MintControl tests
Hadrienlc Nov 9, 2021
78f93c3
fix: Rename MintControl to MinterAccessControl
Hadrienlc Nov 12, 2021
9e6f220
fix: Update tests
Hadrienlc Nov 12, 2021
e54c340
Add custom tests with upgrade for MinterAccessControl
Hadrienlc Nov 12, 2021
71378cb
fix: Use past for events
Hadrienlc Nov 12, 2021
325a4a0
fix: Moved MinterAccessControl to Lazy classes
Hadrienlc Nov 12, 2021
88dd056
fix: validate minter and not sender in MinterAccessControl
Hadrienlc Nov 16, 2021
3a68904
fix: Add __gap in MinterAccessControl
Hadrienlc Nov 17, 2021
6305db8
Update MinterAccessControl to use Enumerable AddressSet
Hadrienlc Nov 19, 2021
099bd1e
Merge remote-tracking branch 'origin/master' into feature/mint-control
NicolasMahe Nov 30, 2021
6a5e213
use isPrivate in MinterAccessControl
Hadrienlc Nov 26, 2021
eb3607e
fix: minter access control typos and tests
Hadrienlc Dec 2, 2021
1e8f323
set MinterAccessControl abstract
Hadrienlc Dec 6, 2021
e46674d
Merge pull request #6 from liteflow-labs/feature/mint-control-enumerable
NicolasMahe Dec 6, 2021
4c01bbe
Merge branch 'rarible:master' into feature/mint-control
Hadrienlc Dec 16, 2021
4d5f1fb
Renamed methods and events in MinterAccessControl
Hadrienlc Dec 16, 2021
1d196a6
Add tests for add/remove minter
Hadrienlc Dec 16, 2021
0cc1751
Removed test on tokenURI
Hadrienlc Dec 16, 2021
f76cf69
Merge branch 'rarible:master' into feature/mint-control
Hadrienlc Jan 10, 2022
af18208
update check for V2 test contract
Hadrienlc Jan 10, 2022
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
51 changes: 51 additions & 0 deletions tokens/contracts/access/MinterAccessControl.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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";
import "@openzeppelin/contracts-upgradeable/utils/EnumerableSetUpgradeable.sol";

abstract contract MinterAccessControl is Initializable, OwnableUpgradeable {
using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;

EnumerableSetUpgradeable.AddressSet private _minters;

event MinterAdded(address indexed operator, address indexed minter);
event MinterRemoved(address indexed operator, address indexed minter);

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

function __MinterAccessControl_init_unchained() internal initializer {
}

/**
* @dev Add `_minter` to the list of allowed minters.
*/
function addMinter(address _minter) external onlyOwner {
require(!_minters.contains(_minter), 'MinterAccessControl: Already minter');
_minters.add(_minter);
emit MinterAdded(_msgSender(), _minter);
}

/**
* @dev Revoke `_minter` from the list of allowed minters.
*/
function removeMinter(address _minter) external onlyOwner {
require(_minters.contains(_minter), 'MinterAccessControl: Not minter');
_minters.remove(_minter);
emit MinterRemoved(_msgSender(), _minter);
}

/**
* @dev Returns `true` if `account` has been granted to minters.
*/
function isMinter(address account) public view returns (bool) {
return _minters.contains(account);
}

uint256[50] private __gap;
}
6 changes: 4 additions & 2 deletions tokens/contracts/erc-1155/ERC1155Rarible.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ pragma solidity 0.7.6;
pragma abicoder v2;

import "./ERC1155Base.sol";
import "../access/MinterAccessControl.sol";

contract ERC1155Rarible is ERC1155Base {
contract ERC1155Rarible is ERC1155Base, MinterAccessControl {
/// @dev true if collection is private, false if public
bool isPrivate;

Expand Down Expand Up @@ -40,6 +41,7 @@ contract ERC1155Rarible is ERC1155Base {
__ERC1155Burnable_init_unchained();
__RoyaltiesV2Upgradeable_init_unchained();
__ERC1155Base_init_unchained(_name, _symbol);
__MinterAccessControl_init_unchained();
_setBaseURI(baseURI);

//setting default approver for transferProxies
Expand All @@ -49,7 +51,7 @@ contract ERC1155Rarible is ERC1155Base {

function mintAndTransfer(LibERC1155LazyMint.Mint1155Data memory data, address to, uint256 _amount) public override {
if (isPrivate){
require(owner() == data.creators[0].account, "minter is not the owner");
require(owner() == data.creators[0].account || isMinter(data.creators[0].account), "not owner or minter");
}
super.mintAndTransfer(data, to, _amount);
}
Expand Down
6 changes: 4 additions & 2 deletions tokens/contracts/erc-721-minimal/ERC721RaribleMinimal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ pragma solidity 0.7.6;
pragma abicoder v2;

import "./ERC721BaseMinimal.sol";
import "../access/MinterAccessControl.sol";

contract ERC721RaribleMinimal is ERC721BaseMinimal {
contract ERC721RaribleMinimal is ERC721BaseMinimal, MinterAccessControl {
/// @dev true if collection is private, false if public
bool isPrivate;

Expand Down Expand Up @@ -39,6 +40,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);

Expand All @@ -49,7 +51,7 @@ contract ERC721RaribleMinimal is ERC721BaseMinimal {

function mintAndTransfer(LibERC721LazyMint.Mint721Data memory data, address to) public override virtual {
if (isPrivate){
require(owner() == data.creators[0].account, "minter is not the owner");
require(owner() == data.creators[0].account || isMinter(data.creators[0].account), "not owner or minter");
}
super.mintAndTransfer(data, to);
}
Expand Down
71 changes: 71 additions & 0 deletions tokens/test/MinterAccessControl.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const TestingV1 = artifacts.require("MinterAccessControlTestV1.sol");
const TestingV2 = artifacts.require("MinterAccessControlTestV2.sol");

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

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

contract("MinterAccessControl", accounts => {
let token;
let tokenOwner = accounts[9];

beforeEach(async () => {
token = await deployProxy(TestingV1, [], { initializer: 'initialize' });
await token.transferOwnership(tokenOwner);
});

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

await token.addMinter(minter, {from: tokenOwner})
assert.equal(await token.isMinter(minter), true);

// upgrade contract
const newInstance = await upgradeProxy(token.address, TestingV2);
assert.equal(await newInstance.version(), await newInstance.V2());

assert.equal(await newInstance.isMinter(minter), true);
});

it("should add a minter and emit event", async () => {
const minter = accounts[2];

let operator;
let addedMinter;
const receipt = await token.addMinter(minter, {from: tokenOwner})
truffleAssert.eventEmitted(receipt, 'MinterAdded', (ev) => {
operator = ev.operator;
addedMinter = ev.minter;
return true;
});

assert.equal(operator, tokenOwner);
assert.equal(addedMinter, minter);
assert.equal(await token.isMinter(minter), true);
})

it("should remove a minter and emit event", async () => {
const minter = accounts[2];

await token.addMinter(minter, {from: tokenOwner})
assert.equal(await token.isMinter(minter), true);

let operator;
let removedMinter;
const receipt = await token.removeMinter(minter, {from: tokenOwner})
truffleAssert.eventEmitted(receipt, 'MinterRemoved', (ev) => {
operator = ev.operator;
removedMinter = ev.minter;
return true;
});

assert.equal(operator, tokenOwner);
assert.equal(removedMinter, minter);
assert.equal(await token.isMinter(minter), false);
})
});
15 changes: 15 additions & 0 deletions tokens/test/contracts/access/MinterAccessControlTestV1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "../../../contracts/access/MinterAccessControl.sol";

contract MinterAccessControlTestV1 is MinterAccessControl {

function initialize() external initializer {
__Ownable_init_unchained();
__MinterAccessControl_init_unchained();
}

uint256[50] private __gap;
}
20 changes: 20 additions & 0 deletions tokens/test/contracts/access/MinterAccessControlTestV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "../../../contracts/access/MinterAccessControl.sol";

contract MinterAccessControlTestV2 is MinterAccessControl {
bytes4 constant public V2 = bytes4(keccak256("V2"));

function initialize() external initializer {
__Ownable_init_unchained();
__MinterAccessControl_init_unchained();
}

function version() public view returns (bytes4) {
return V2;
}

uint256[50] private __gap;
}
72 changes: 72 additions & 0 deletions tokens/test/erc-1155/ERC1155RaribleUser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,78 @@ contract("ERC1155RaribleUser", accounts => {
await checkCreators(tokenId, [minter]);
});

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 expectThrow(
token.mintAndTransfer([tokenId, tokenURI, supply, creators([minter]), [], [zeroWord]], transferTo, mint, {from: minter})
);

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

await token.mintAndTransfer([tokenId, tokenURI, supply, creators([minter]), [], [zeroWord]], transferTo, mint, {from: minter});
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);

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

await token.setApprovalForAll(whiteListProxy, true, {from: minter})
await token.addMinter(minter, {from: tokenOwner});
assert.equal(await token.isMinter(minter), true);
assert.equal(await token.isMinter(whiteListProxy), false);

await token.mintAndTransfer([tokenId, tokenURI, supply, creators([minter]), [], [signature]], transferTo, mint, {from: whiteListProxy})
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);

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

await token.setApprovalForAll(whiteListProxy, true, {from: minter})
await token.addMinter(minter, {from: tokenOwner});
assert.equal(await token.isMinter(minter), true);
assert.equal(await token.isMinter(whiteListProxy), false);

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

async function getSignature(tokenId, tokenURI, supply, creators, fees, account) {
return sign(account, tokenId, tokenURI, supply, creators, fees, token.address);
}
Expand Down
64 changes: 64 additions & 0 deletions tokens/test/erc-721-minimal/RaribleUser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,70 @@ contract("ERC721RaribleUser minimal", 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";

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

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

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

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";

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

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

await token.setApprovalForAll(whiteListProxy, true, {from: minter})
await token.addMinter(minter, {from: tokenOwner})
assert.equal(await token.isMinter(minter), true);
assert.equal(await token.isMinter(whiteListProxy), false);

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

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";

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

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

await token.setApprovalForAll(whiteListProxy, true, {from: minter})
await token.addMinter(minter, {from: tokenOwner})
assert.equal(await token.isMinter(minter), true);
assert.equal(await token.isMinter(whiteListProxy), false);

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

function getSignature(tokenId, tokenURI, fees, creators, account) {
return sign(account, tokenId, tokenURI, fees, creators, token.address);
}
Expand Down