【问题标题】:Checking and granting role in Solidity在 Solidity 中检查和授予角色
【发布时间】:2021-12-07 08:47:01
【问题描述】:

我正在尝试创建一个用于铸造 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_WHITELISTEDtrue,则交易必须有足够的预售价值,并且必须有ROLE_PRESALE
  • 如果ONLY_WHITELISTEDfalse,任何人都可以造币

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

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

如果我尝试从另一个地址(没有角色)运行 test mint 脚本,它会按预期失败,这表明角色正在工作,但我使用 hasRole 错误?

【问题讨论】:

    标签: ethereum solidity smartcontracts


    【解决方案1】:
    const minterHex = web3.utils.fromAscii('ROLE_MINTER')
    

    这个 JS sn-p 返回ROLE_MINTER 字符串的十六进制表示0x524f4c455f4d494e544552

    bytes32 public constant ROLE_MINTER = keccak256("ROLE_MINTER");
    

    但是这个 Solidity sn-p 返回 ROLE_MINTER 字符串的 keccak256 哈希0xaeaef46186eb59f884e36929b6d682a6ae35e1e43d8f05f058dcefb92b601461

    因此,当您查询合约时,如果 OWNER_ADDRESS 具有角色 0x524f4c455f4d494e544552,它会返回 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() 的第二个参数作为您询问其角色的帐户。

    【讨论】:

      猜你喜欢
      • 2017-09-08
      • 2019-05-21
      • 2019-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-27
      • 2020-07-09
      • 2021-04-07
      相关资源
      最近更新 更多