按审计要求, 修改202403活动相关合约

This commit is contained in:
CounterFire2023 2024-03-03 13:48:10 +08:00
parent 663eea2ac8
commit cf3bcd96c7
10 changed files with 13254 additions and 11763 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -3259,7 +3259,7 @@
}
},
"schemaVersion": "3.4.16",
"updatedAt": "2024-01-12T05:44:22.587Z",
"updatedAt": "2024-02-27T05:13:36.919Z",
"networkType": "ethereum",
"devdoc": {
"kind": "dev",

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
import "../core/HasSignature.sol";
import "../utils/TimeChecker.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "../core/HasSignature.sol";
/**
* Contract for activity NFT claim.
@ -14,13 +13,14 @@ interface IClaimAbleNFT {
) external returns (uint256);
}
contract NFTClaimer is HasSignature, TimeChecker, ReentrancyGuard{
contract NFTClaimer is HasSignature, ReentrancyGuard{
uint256 private immutable _CACHED_CHAIN_ID;
address private immutable _CACHED_THIS;
address public immutable nftAddress;
address public signer;
uint256 public startTime;
uint256 public endTime;
mapping(address => bool) public tokenSupported;
mapping(address => uint256) public claimHistory;
event NFTClaimed(
@ -29,20 +29,17 @@ contract NFTClaimer is HasSignature, TimeChecker, ReentrancyGuard{
uint256 tokenId,
uint256 nonce
);
event NFTSupportUpdated(address indexed nftAddress, bool support);
event SignerUpdated(address indexed signer);
event StartTimeUpdated(uint256 indexed startTime);
event EndTimeUpdated(uint256 indexed endTime);
constructor() {
constructor(address _nftAddress) {
_CACHED_CHAIN_ID = block.chainid;
_CACHED_THIS = address(this);
nftAddress = _nftAddress;
}
function updateTokenSupport(address nftToken, bool support) external onlyOwner {
tokenSupported[nftToken] = support;
emit NFTSupportUpdated(nftToken, support);
}
function updateStartTime(uint256 _startTime) external onlyOwner {
startTime = _startTime;
@ -57,37 +54,31 @@ contract NFTClaimer is HasSignature, TimeChecker, ReentrancyGuard{
/**
* @dev update signer
* @param account new signer address
* @param _account new signer address
*/
function updateSigner(address account) external onlyOwner {
require(account != address(0), "NFTClaimer: address can not be zero");
signer = account;
emit SignerUpdated(account);
function updateSigner(address _account) external onlyOwner {
require(_account != address(0), "NFTClaimer: address can not be zero");
signer = _account;
emit SignerUpdated(_account);
}
/**
* @dev claim NFT
* Get whitelist signature from a third-party service, then call this method to claim NFT
* @param nftAddress NFT address
* @param signTime sign time
* @param saltNonce nonce
* @param signature signature
*/
function claim(
address nftAddress,
uint256 signTime,
uint256 saltNonce,
bytes calldata signature
) external signatureValid(signature) timeValid(signTime) nonReentrant {
) external nonReentrant {
require(block.timestamp >= startTime, "NFTClaimer: not started");
require(block.timestamp <= endTime, "NFTClaimer: already ended");
require(tokenSupported[nftAddress], "NFTClaimer: unsupported NFT");
address to = _msgSender();
require(claimHistory[to] == 0, "NFTClaimer: already claimed");
bytes32 criteriaMessageHash = getMessageHash(
to,
nftAddress,
signTime,
saltNonce
);
checkSigner(signer, criteriaMessageHash, signature);
@ -100,13 +91,11 @@ contract NFTClaimer is HasSignature, TimeChecker, ReentrancyGuard{
function getMessageHash(
address _to,
address _address,
uint256 _signTime,
uint256 _saltNonce
) public view returns (bytes32) {
bytes memory encoded = abi.encodePacked(
_to,
_address,
_signTime,
_CACHED_CHAIN_ID,
_CACHED_THIS,
_saltNonce

View File

@ -11,8 +11,6 @@ contract HasSignature is Ownable {
bytes32 hash,
bytes memory signature
) public pure {
require(signer != address(0), "[BE] invalid signer");
require(signature.length == 65, "[BE] invalid signature length");
bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(hash);
address recovered = ECDSA.recover(ethSignedMessageHash, signature);

View File

@ -2,72 +2,37 @@
pragma solidity 0.8.10;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract BEBadgeV2 is AccessControl, ERC721Enumerable, ERC721Burnable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
contract BEBadgeV2 is Ownable, ERC721Enumerable, ERC721Burnable {
string private _baseTokenURI = "";
uint256 public immutable supplyLimit;
uint256 private _tokenIndex;
uint256 public maxBatchSize = 500;
mapping(address => bool) public minters;
// ============ Events ============
event MetaAddressUpdated(address indexed metaAddress);
event BatchLimitUpdated(uint256 indexed maxBatchSize);
event MinterUpdated(address indexed minter, bool support);
constructor(
string memory _name,
string memory _symbol,
uint256 _supplyLimt
) ERC721(_name, _symbol) {
_setRoleAdmin(MINTER_ROLE, DEFAULT_ADMIN_ROLE);
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(MINTER_ROLE, msg.sender);
supplyLimit = _supplyLimt;
}
/**
* @dev Batch mint tokens and transfer to specified address.
*
* Requirements:
* - Caller must have `MINTER_ROLE`.
* - The total supply limit should not be exceeded if supplyLimit is greater than zero.
* - The number of tokenIds offered for minting should not exceed maxBatchSize.
*/
function batchMint(
address to,
uint256 count
) external onlyRole(MINTER_ROLE) returns (uint256[] memory) {
require(count > 0, "count is too small");
require(count <= maxBatchSize, "Exceeds the maximum batch size");
require(
(supplyLimit == 0) || (totalSupply() + count <= supplyLimit),
"Exceeds the total supply"
);
uint256[] memory tokenIds = new uint256[](count);
for (uint256 i = 0; i < count; i++) {
_tokenIndex += 1;
_safeMint(to, _tokenIndex);
tokenIds[i] = _tokenIndex;
}
return tokenIds;
}
/**
* @dev Safely mints a new token and assigns it to the specified address.
* Only the account with the MINTER_ROLE can call this function.
*
* @param to The address to which the newly minted token will be assigned.
* tokenId begin with 1.
* @param _to The address to which the newly minted token will be assigned.
*/
function safeMint(address to) external onlyRole(MINTER_ROLE) returns (uint256){
require(
(supplyLimit == 0) || (totalSupply() < supplyLimit),
"Exceeds the total supply"
);
function safeMint(address _to) external onlyMinter returns (uint256){
require(_tokenIndex < supplyLimit, "Exceeds the total supply");
uint256 tokenId = ++_tokenIndex;
_safeMint(to, tokenId);
_safeMint(_to, tokenId);
return tokenId;
}
@ -76,7 +41,7 @@ contract BEBadgeV2 is AccessControl, ERC721Enumerable, ERC721Burnable {
*/
function updateBaseURI(
string calldata baseTokenURI
) external onlyRole(DEFAULT_ADMIN_ROLE) {
) external onlyOwner() {
_baseTokenURI = baseTokenURI;
}
@ -84,20 +49,6 @@ contract BEBadgeV2 is AccessControl, ERC721Enumerable, ERC721Burnable {
return _baseTokenURI;
}
/**
* @dev Updates the maximum batch size for a batch operation.
* @param valNew The new maximum batch size.
* Requirements:
* - The caller must have the DEFAULT_ADMIN_ROLE.
* - The new batch size must be greater than 0.
*/
function updateBatchLimit(
uint256 valNew
) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(valNew > 0, "Batch size is too short");
maxBatchSize = valNew;
emit BatchLimitUpdated(valNew);
}
/**
* @dev See {IERC165-_beforeTokenTransfer}.
@ -120,9 +71,25 @@ contract BEBadgeV2 is AccessControl, ERC721Enumerable, ERC721Burnable {
public
view
virtual
override(ERC721, AccessControl, ERC721Enumerable)
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
/**
* @dev Updates the minters mapping to allow or disallow a specific address to mint tokens.
* Only the contract owner can call this function.
* @param _address The address to update the minting permission for.
* @param _support A boolean indicating whether the address should be allowed to mint tokens or not.
*/
function updateMinters(address _address, bool _support) external onlyOwner {
minters[_address] = _support;
emit MinterUpdated(_address, _support);
}
modifier onlyMinter() {
require(minters[_msgSender()], "Address does not have the minter permission");
_;
}
}

View File

@ -0,0 +1,52 @@
const base = require("../scripts/base");
const NFTClaimer = artifacts.require("activity/NFTClaimer");
const NFT = artifacts.require("tokens/erc721/BEBadgeV2");
module.exports = async function (deployer, network, accounts) {
const config = require(`../config/config_${network}`);
let cfgs = base.loadData({ network });
// await deployer.deploy(NFT, 'BEBadgeV2', 'BEBadgeV2', 500);
// const nftInstance = await NFT.deployed();
// if (nftInstance) {
// console.log("NFT successfully deployed.");
// console.log("address: " + nftInstance.address);
// }
// base.updateArray({
// name: "BEBadgeV2",
// type: "erc721",
// json: "assets/contracts/BEBadgeV2.json",
// address: nftInstance.address,
// network,
// });
// await deployer.deploy(NFTClaimer);
// const claimInstance = await NFTClaimer.deployed();
// if (claimInstance) {
// console.log("ClaimToken successfully deployed.");
// console.log("address: " + claimInstance.address);
// }
// base.updateArray({
// name: "NFTClaimer",
// type: "logic",
// json: "assets/contracts/NFTClaimer.json",
// address: claimInstance.address,
// network,
// });
const nftAddress = cfgs.find((c) => c.name === "BEBadgeV2").address
const nftInstance = await NFT.at(nftAddress);
const claimerAddress = cfgs.find((c) => c.name === "NFTClaimer").address
const claimInstance = await NFTClaimer.at(claimerAddress);
await claimInstance.updateTokenSupport(nftInstance.address, true);
console.log("updateTokenSupport successfully deployed.");
await claimInstance.updateSigner(config.admins.admin);
console.log("updateSigner successfully deployed.");
await claimInstance.updateStartTime((Date.now() / 1000 | 0) - 3600 * 24);
await claimInstance.updateEndTime((Date.now() / 1000 | 0) + 3600 * 24 * 10);
console.log("update start time and end time successfully deployed.");
await nftInstance.grantRole(await nftInstance.MINTER_ROLE(), claimInstance.address);
console.log("grantRole successfully deployed.");
};

14
out/sepolia_dev.json Normal file
View File

@ -0,0 +1,14 @@
[
{
"name": "BEBadgeV2",
"type": "erc721",
"json": "assets/contracts/BEBadgeV2.json",
"address": "0x09F0dFFA584B1277D7c4E44265a6b5D03303Fc99"
},
{
"name": "NFTClaimer",
"type": "logic",
"json": "assets/contracts/NFTClaimer.json",
"address": "0xe4112A9490765463471a9a73050328153C509B0b"
}
]

View File

@ -20,29 +20,24 @@ contract("NFTClaimer", (accounts) => {
beforeEach(async () => {
nft = await NFT.new("NFT", "NFT", 5);
tokenClaimer = await NFTClaimer.new();
await tokenClaimer.updateTokenSupport(nft.address, true);
tokenClaimer = await NFTClaimer.new(nft.address);
await tokenClaimer.updateSigner(singer);
await tokenClaimer.updateStartTime(start);
await tokenClaimer.updateEndTime(end);
});
it("should cliam token", async () => {
const minterRole = await nft.MINTER_ROLE();
await nft.grantRole(minterRole, tokenClaimer.address);
await nft.updateMinters(tokenClaimer.address, true);
const token = nft.address;
const chainId = await web3.eth.getChainId();
const address = tokenClaimer.address;
const startTime = Date.now() / 1000 | 0;
const saltNonce = (Math.random() * 1000) | 0;
let signStr = web3.utils.soliditySha3.apply(this,
[user, token, startTime, chainId, address, saltNonce]);
[user, token, chainId, address, saltNonce]);
let signature = await web3.eth.sign(signStr, singer);
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
const receipt = await tokenClaimer.claim(
token,
startTime,
saltNonce,
signature,
{ from: user }
@ -51,31 +46,46 @@ contract("NFTClaimer", (accounts) => {
const balanceOfUser = await nft.balanceOf(user);
assert.equal(balanceOfUser, 1, "Incorrect user balance");
});
it("should revert cliam token for error signature", async () => {
const minterRole = await nft.MINTER_ROLE();
await nft.grantRole(minterRole, tokenClaimer.address);
it("should revert cliam token for no minter permission", async () => {
const token = nft.address;
const chainId = await web3.eth.getChainId();
const address = tokenClaimer.address;
const startTime = Date.now() / 1000 | 0;
const saltNonce = (Math.random() * 1000) | 0;
let signStr = web3.utils.soliditySha3.apply(this,
[user, token, startTime, chainId, address, saltNonce]);
[user, token, chainId, address, saltNonce]);
let signature = await web3.eth.sign(signStr, singer);
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
const err = `Address does not have the minter permission`
await expectRevert(
tokenClaimer.claim(
saltNonce,
signature,
{ from: user }
),
err
);
});
it("should revert cliam token for error signature", async () => {
await nft.updateMinters(tokenClaimer.address, true);
const token = nft.address;
const chainId = await web3.eth.getChainId();
const address = tokenClaimer.address;
const saltNonce = (Math.random() * 1000) | 0;
let signStr = web3.utils.soliditySha3.apply(this,
[user, token, chainId, address, saltNonce]);
let signature = await web3.eth.sign(signStr, singer);
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
const receipt = await tokenClaimer.claim(
token,
startTime,
saltNonce,
signature,
{ from: user }
);
const err = `[BE] signature used. please send another transaction with new signature`
const err = `NFTClaimer: already claimed.`
await expectRevert(
tokenClaimer.claim(
token,
startTime,
saltNonce,
signature,
{ from: user }
@ -88,37 +98,30 @@ contract("NFTClaimer", (accounts) => {
});
it("should revert cliam token for already claimed", async () => {
const minterRole = await nft.MINTER_ROLE();
await nft.grantRole(minterRole, tokenClaimer.address);
await nft.updateMinters(tokenClaimer.address, true);
const token = nft.address;
const chainId = await web3.eth.getChainId();
const address = tokenClaimer.address;
const startTime = Date.now() / 1000 | 0;
const saltNonce = (Math.random() * 1000) | 0;
let signStr = web3.utils.soliditySha3.apply(this,
[user, token, startTime, chainId, address, saltNonce]);
[user, token, chainId, address, saltNonce]);
let signature = await web3.eth.sign(signStr, singer);
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
const receipt = await tokenClaimer.claim(
token,
startTime,
saltNonce,
signature,
{ from: user }
);
const startTime2 = Date.now() / 1000 | 0;
const saltNonce2 = (Math.random() * 1000) | 0;
let signStr2 = web3.utils.soliditySha3.apply(this,
[user, token, startTime2, chainId, address, saltNonce2]);
[user, token, chainId, address, saltNonce2]);
let signature2 = await web3.eth.sign(signStr2, singer);
signature2 = signature2.replace(/00$/, "1b").replace(/01$/, "1c");
const err = `NFTClaimer: already claimed`
await expectRevert(
tokenClaimer.claim(
token,
startTime2,
saltNonce2,
signature2,
{ from: user }
@ -127,24 +130,20 @@ contract("NFTClaimer", (accounts) => {
);
});
it("should revert cliam token for activity not begin", async () => {
const minterRole = await nft.MINTER_ROLE();
await nft.updateMinters(tokenClaimer.address, true);
await tokenClaimer.updateStartTime(Date.now() / 1000 | 0 + 3600);
await nft.grantRole(minterRole, tokenClaimer.address);
const token = nft.address;
const chainId = await web3.eth.getChainId();
const address = tokenClaimer.address;
const startTime = Date.now() / 1000 | 0;
const saltNonce = (Math.random() * 1000) | 0;
let signStr = web3.utils.soliditySha3.apply(this,
[user, token, startTime, chainId, address, saltNonce]);
[user, token, chainId, address, saltNonce]);
let signature = await web3.eth.sign(signStr, singer);
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
const err = `NFTClaimer: not started`
await expectRevert(
tokenClaimer.claim(
token,
startTime,
saltNonce,
signature,
{ from: user }
@ -153,24 +152,20 @@ contract("NFTClaimer", (accounts) => {
);
});
it("should revert cliam token for activity already end", async () => {
const minterRole = await nft.MINTER_ROLE();
await nft.updateMinters(tokenClaimer.address, true);
await tokenClaimer.updateEndTime((Date.now() / 1000 | 0) - 3600);
await nft.grantRole(minterRole, tokenClaimer.address);
const token = nft.address;
const chainId = await web3.eth.getChainId();
const address = tokenClaimer.address;
const startTime = Date.now() / 1000 | 0;
const saltNonce = (Math.random() * 1000) | 0;
let signStr = web3.utils.soliditySha3.apply(this,
[user, token, startTime, chainId, address, saltNonce]);
[user, token, chainId, address, saltNonce]);
let signature = await web3.eth.sign(signStr, singer);
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
const err = `NFTClaimer: already ended`
await expectRevert(
tokenClaimer.claim(
token,
startTime,
saltNonce,
signature,
{ from: user }