Solidity高权限调用方案:如何限制仅质押合约可调用NFT铸造方法
解决方案
完全不需要给用户分配临时高权限,该方案风险极高,极易出现权限漏删、重入攻击等问题导致NFT被恶意增发,采用合约代理调用的方案即可完全匹配你的业务需求,同时满足安全要求,调整步骤如下:
- 第一步:配置NFT合约权限
你现有的NFT合约CONTRACT_ROLE仅需要授予质押合约StakingExample的地址即可,不需要给任何普通用户分配该角色。
操作方法:NFT合约部署完成后,由持有DEFAULT_ADMIN_ROLE的账号(一般为部署者)执行一次授权操作:
完成授权后,只有nftContract.grantRole(keccak256("CONTRACT_ROLE"), 你的StakingExample合约地址);StakingExample合约可以调用NFT合约的mintItem方法,从根源上杜绝普通用户直接调用铸造接口的可能。 - 第二步:调整质押合约的
claimNFT方法权限
你现有代码里claimNFT方法加了onlyRole(CONTRACT_ROLE)修饰符,该配置错误,这个方法本身就是给完成质押的普通用户调用的,应该移除该修饰符,替换为自定义的资格校验逻辑即可。 - 第三步:跑通业务流程
- 用户前端调用
someStakingFunction完成质押,质押合约内标记该用户存在未领取的NFT资格 - 用户前端调用
claimNFT方法,质押合约校验用户确实有未领取的NFT后,主动调用NFT合约的mintItem方法将NFT铸造给调用用户
全程普通用户不需要持有NFT合约的任何高权限,所有铸造请求都由质押合约发起,完全符合权限控制要求。
- 用户前端调用
调整后的核心代码示例
质押合约修改后代码
// SPDX-License-Identifier: unlicensed pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; interface INFTExample { function mintItem(address _to, string memory _tokenURI) external returns (uint256); } contract StakingExample is AccessControl { using SafeERC20 for IERC20; IERC20 public immutable stakingToken; INFTExample public immutable nftContract; // 记录用户NFT领取状态 mapping(address => bool) public userHasUnclaimedNFT; constructor(address _stakingToken, address _nftContract) { stakingToken = IERC20(_stakingToken); nftContract = INFTExample(_nftContract); _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); } function someStakingFunction(uint256 _stakeAmount) external { // 原有质押逻辑 stakingToken.safeTransferFrom(msg.sender, address(this), _stakeAmount); // 标记用户有可领取NFT userHasUnclaimedNFT[msg.sender] = true; } // 移除了onlyRole(CONTRACT_ROLE)修饰符 function claimNFT(string memory _tokenURI) external { require(userHasUnclaimedNFT[msg.sender], "No available NFT to claim"); // 先标记为已领取,避免重入攻击 userHasUnclaimedNFT[msg.sender] = false; // 质押合约发起铸造调用,权限校验通过 nftContract.mintItem(msg.sender, _tokenURI); } }
临时赋权方案说明(不推荐生产使用)
如果坚持要使用临时赋权方案,需要严格控制权限生效周期,调用铸造方法前后分别做赋权和删除权限的操作,该方案Gas成本更高,且存在逻辑漏洞风险:
function claimNFT(string memory _tokenURI) external { require(userHasUnclaimedNFT[msg.sender], "No available NFT to claim"); userHasUnclaimedNFT[msg.sender] = false; // 临时给用户赋权 nftContract.grantRole(keccak256("CONTRACT_ROLE"), msg.sender); // 调用铸造方法 nftContract.mintItem(msg.sender, _tokenURI); // 立刻删除用户权限 nftContract.revokeRole(keccak256("CONTRACT_ROLE"), msg.sender); }
内容的提问来源于stack exchange,提问作者LuxuryWaffles
相关产品推荐
相关产品推荐

