在 Solidity 中检查和授予角色
Checking and granting role in Solidity
我正在尝试创建一个用于铸造 ERC721 代币的工厂合约,根据它是否在预售期间以两种不同的价格。
我正在使用 OpenZeppelin 的 Access 库,并且我的合同设置了两个角色(加上默认的管理员角色)。为简洁起见,排除了一些行:
import "@openzeppelin/contracts/access/AccessControl.sol";
import "./Example.sol";
contract ExampleFactory is AccessControl {
// ...
bool public ONLY_WHITELISTED = true;
uint256 public PRESALE_COST = 6700000 gwei;
uint256 public SALE_COST = 13400000 gwei;
uint256 MAX_PRESALE_MINT = 2;
uint256 MAX_LIVE_MINT = 10;
uint256 TOTAL_SUPPLY = 100;
// ...
bytes32 public constant ROLE_MINTER = keccak256("ROLE_MINTER");
bytes32 public constant ROLE_PRESALE = keccak256("ROLE_PRESALE");
// ...
constructor(address _nftAddress) {
nftAddress = _nftAddress;
// Grant the contract deployer the default admin role: it will be able
// to grant and revoke any roles
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(ROLE_MINTER, msg.sender);
_setupRole(ROLE_PRESALE, msg.sender);
}
function mint(uint256 _mintAmount, address _toAddress) public payable {
// If the user doesn't have the minter role then require payment
if (hasRole(ROLE_MINTER, msg.sender) == false) {
if (ONLY_WHITELISTED == true) {
// If still in whitelist mode then require presale role & enough value
require(hasRole(ROLE_PRESALE, msg.sender), "address is not whitelisted");
require(msg.value >= PRESALE_COST * _mintAmount, "tx value too low for quantity");
} else {
require(msg.value >= SALE_COST * _mintAmount, "tx value too low for quantity");
}
}
// Check there are enough tokens left to mint
require(canMint(_mintAmount), "remaining supply too low");
Example token = Example(nftAddress);
for (uint256 i = 0; i < _mintAmount; i++) {
token.mintTo(_toAddress);
}
}
function canMint(uint256 _mintAmount) public view returns (bool) {
if (hasRole(ROLE_MINTER, msg.sender) == false) {
if (ONLY_WHITELISTED == true) {
require((_mintAmount <= MAX_PRESALE_MINT), "max 2 tokens can be minted during presale");
} else {
require((_mintAmount <= MAX_LIVE_MINT), "max 10 tokens can be minted during sale");
}
}
Example token = Example(nftAddress);
uint256 issuedSupply = token.totalSupply();
return issuedSupply < (TOTAL_SUPPLY - _mintAmount);
}
}
铸币有几种不同的途径:
- 如果用户有
ROLE_MINTER
,他们可以在没有付款或限制的情况下铸造
- 如果
ONLY_WHITELISTED
是true
,交易必须有足够的预售价格,他们必须有ROLE_PRESALE
- 如果
ONLY_WHITELISTED
是false
,任何人都可以铸造
我写了一个脚本来测试铸币:
const factoryContract = new web3Instance.eth.Contract(
FACTORY_ABI,
FACTORY_CONTRACT_ADDRESS,
{ gasLimit: '1000000' }
);
console.log('Testing mint x3 from minter role')
try {
const result = await factoryContract.methods
.mint(3, OWNER_ADDRESS)
.send({ from: OWNER_ADDRESS });
console.log(' ✅ Minted 3x. Transaction: ' + result.transactionHash);
} catch (err) {
console.log(' Mint failed')
console.log(err)
}
运行 这成功地为工厂所有者铸造了 3 个代币。此调用没有附加任何值,并且它的铸造量超过最大值,因此为了使其成功,它必须遵循 ROLE_MINTER
路径。
但是,如果我从同一个地址调用 hasRole
,结果是 false
,这没有意义。
const minterHex = web3.utils.fromAscii('ROLE_MINTER')
const result = await factoryContract.methods.hasRole(minterHex, OWNER_ADDRESS).call({ from: OWNER_ADDRESS });
// result = false
如果我尝试从另一个地址(没有角色)运行 测试 mint 脚本,它会按预期失败,这表明角色正在工作,但我使用 hasRole
错误?
const minterHex = web3.utils.fromAscii('ROLE_MINTER')
此 JS 片段 returns 十六进制表示法 ROLE_MINTER
字符串:0x524f4c455f4d494e544552
bytes32 public constant ROLE_MINTER = keccak256("ROLE_MINTER");
但是这个 Solidity 片段 returns keccak256
哈希 ROLE_MINTER
字符串:0xaeaef46186eb59f884e36929b6d682a6ae35e1e43d8f05f058dcefb92b601461
因此,当您查询合同时,如果 OWNER_ADDRESS
具有角色 0x524f4c455f4d494e544552
,则它 returns false
因为此地址没有此角色。
您可以使用 web3.utils.soliditySha3()
函数 (docs) 计算哈希值。
const minterHash = web3.utils.soliditySha3('ROLE_MINTER');
const result = await factoryContract.methods.hasRole(minterHash, OWNER_ADDRESS).call();
另请注意,OpenZeppelin hasRole() 函数不会检查 msg.sender
,因此您无需在 call()
函数中指定调用者。只是 hasRole()
的第二个参数作为您询问其角色的帐户。
我正在尝试创建一个用于铸造 ERC721 代币的工厂合约,根据它是否在预售期间以两种不同的价格。
我正在使用 OpenZeppelin 的 Access 库,并且我的合同设置了两个角色(加上默认的管理员角色)。为简洁起见,排除了一些行:
import "@openzeppelin/contracts/access/AccessControl.sol";
import "./Example.sol";
contract ExampleFactory is AccessControl {
// ...
bool public ONLY_WHITELISTED = true;
uint256 public PRESALE_COST = 6700000 gwei;
uint256 public SALE_COST = 13400000 gwei;
uint256 MAX_PRESALE_MINT = 2;
uint256 MAX_LIVE_MINT = 10;
uint256 TOTAL_SUPPLY = 100;
// ...
bytes32 public constant ROLE_MINTER = keccak256("ROLE_MINTER");
bytes32 public constant ROLE_PRESALE = keccak256("ROLE_PRESALE");
// ...
constructor(address _nftAddress) {
nftAddress = _nftAddress;
// Grant the contract deployer the default admin role: it will be able
// to grant and revoke any roles
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(ROLE_MINTER, msg.sender);
_setupRole(ROLE_PRESALE, msg.sender);
}
function mint(uint256 _mintAmount, address _toAddress) public payable {
// If the user doesn't have the minter role then require payment
if (hasRole(ROLE_MINTER, msg.sender) == false) {
if (ONLY_WHITELISTED == true) {
// If still in whitelist mode then require presale role & enough value
require(hasRole(ROLE_PRESALE, msg.sender), "address is not whitelisted");
require(msg.value >= PRESALE_COST * _mintAmount, "tx value too low for quantity");
} else {
require(msg.value >= SALE_COST * _mintAmount, "tx value too low for quantity");
}
}
// Check there are enough tokens left to mint
require(canMint(_mintAmount), "remaining supply too low");
Example token = Example(nftAddress);
for (uint256 i = 0; i < _mintAmount; i++) {
token.mintTo(_toAddress);
}
}
function canMint(uint256 _mintAmount) public view returns (bool) {
if (hasRole(ROLE_MINTER, msg.sender) == false) {
if (ONLY_WHITELISTED == true) {
require((_mintAmount <= MAX_PRESALE_MINT), "max 2 tokens can be minted during presale");
} else {
require((_mintAmount <= MAX_LIVE_MINT), "max 10 tokens can be minted during sale");
}
}
Example token = Example(nftAddress);
uint256 issuedSupply = token.totalSupply();
return issuedSupply < (TOTAL_SUPPLY - _mintAmount);
}
}
铸币有几种不同的途径:
- 如果用户有
ROLE_MINTER
,他们可以在没有付款或限制的情况下铸造 - 如果
ONLY_WHITELISTED
是true
,交易必须有足够的预售价格,他们必须有ROLE_PRESALE
- 如果
ONLY_WHITELISTED
是false
,任何人都可以铸造
我写了一个脚本来测试铸币:
const factoryContract = new web3Instance.eth.Contract(
FACTORY_ABI,
FACTORY_CONTRACT_ADDRESS,
{ gasLimit: '1000000' }
);
console.log('Testing mint x3 from minter role')
try {
const result = await factoryContract.methods
.mint(3, OWNER_ADDRESS)
.send({ from: OWNER_ADDRESS });
console.log(' ✅ Minted 3x. Transaction: ' + result.transactionHash);
} catch (err) {
console.log(' Mint failed')
console.log(err)
}
运行 这成功地为工厂所有者铸造了 3 个代币。此调用没有附加任何值,并且它的铸造量超过最大值,因此为了使其成功,它必须遵循 ROLE_MINTER
路径。
但是,如果我从同一个地址调用 hasRole
,结果是 false
,这没有意义。
const minterHex = web3.utils.fromAscii('ROLE_MINTER')
const result = await factoryContract.methods.hasRole(minterHex, OWNER_ADDRESS).call({ from: OWNER_ADDRESS });
// result = false
如果我尝试从另一个地址(没有角色)运行 测试 mint 脚本,它会按预期失败,这表明角色正在工作,但我使用 hasRole
错误?
const minterHex = web3.utils.fromAscii('ROLE_MINTER')
此 JS 片段 returns 十六进制表示法 ROLE_MINTER
字符串:0x524f4c455f4d494e544552
bytes32 public constant ROLE_MINTER = keccak256("ROLE_MINTER");
但是这个 Solidity 片段 returns keccak256
哈希 ROLE_MINTER
字符串:0xaeaef46186eb59f884e36929b6d682a6ae35e1e43d8f05f058dcefb92b601461
因此,当您查询合同时,如果 OWNER_ADDRESS
具有角色 0x524f4c455f4d494e544552
,则它 returns false
因为此地址没有此角色。
您可以使用 web3.utils.soliditySha3()
函数 (docs) 计算哈希值。
const minterHash = web3.utils.soliditySha3('ROLE_MINTER');
const result = await factoryContract.methods.hasRole(minterHash, OWNER_ADDRESS).call();
另请注意,OpenZeppelin hasRole() 函数不会检查 msg.sender
,因此您无需在 call()
函数中指定调用者。只是 hasRole()
的第二个参数作为您询问其角色的帐户。