在 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);
  }
}

铸币有几种不同的途径:

我写了一个脚本来测试铸币:

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() 的第二个参数作为您询问其角色的帐户。