增加金砖NFT和金砖的合成,分解
This commit is contained in:
parent
2de2605e9b
commit
7f4afbe3c1
19046
build/contracts/GoldBricksFactory.json
Normal file
19046
build/contracts/GoldBricksFactory.json
Normal file
File diff suppressed because one or more lines are too long
@ -5,4 +5,5 @@ import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
||||
|
||||
interface IAsset is IERC721 {
|
||||
function batchMint(address to, uint256[] memory tokenIds) external;
|
||||
function burn(uint256 tokenId) external;
|
||||
}
|
||||
|
114
contracts/logic/GoldBricksFactory.sol
Normal file
114
contracts/logic/GoldBricksFactory.sol
Normal file
@ -0,0 +1,114 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.10;
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import "../interfaces/IAsset.sol";
|
||||
import "../utils/TimeChecker.sol";
|
||||
import "../core/HasSignature.sol";
|
||||
|
||||
contract GoldBricksFactory is Ownable, TimeChecker, HasSignature {
|
||||
mapping(address => bool) public tokenSupported;
|
||||
|
||||
address public executor;
|
||||
|
||||
event TokenMinted(
|
||||
address indexed nftAddress,
|
||||
address indexed to,
|
||||
uint256 indexed nonce,
|
||||
uint256 startTime,
|
||||
uint256[] ids
|
||||
);
|
||||
|
||||
event TokenDecomposed(
|
||||
address indexed nftAddress,
|
||||
address indexed to,
|
||||
uint256 indexed nonce,
|
||||
uint256 startTime,
|
||||
uint256[] ids
|
||||
);
|
||||
|
||||
function mintNft(
|
||||
address nftAddress,
|
||||
uint256[] memory tokenIds,
|
||||
uint256 startTime,
|
||||
uint256 saltNonce,
|
||||
bytes calldata signature
|
||||
) external signatureValid(signature) timeValid(startTime) {
|
||||
require(tokenSupported[nftAddress], "GoldBricksFactory: Unsupported NFT");
|
||||
address to = _msgSender();
|
||||
bytes32 criteriaMessageHash = getMessageHash(
|
||||
to,
|
||||
nftAddress,
|
||||
address(this),
|
||||
startTime,
|
||||
saltNonce,
|
||||
tokenIds
|
||||
);
|
||||
checkSigner(executor, criteriaMessageHash, signature);
|
||||
IAsset(nftAddress).batchMint(to, tokenIds);
|
||||
_useSignature(signature);
|
||||
emit TokenMinted(nftAddress, to, saltNonce, startTime, tokenIds);
|
||||
}
|
||||
|
||||
function decomposeNft(
|
||||
address nftAddress,
|
||||
uint256[] memory tokenIds,
|
||||
uint256 startTime,
|
||||
uint256 saltNonce,
|
||||
bytes calldata signature
|
||||
) external signatureValid(signature) timeValid(startTime){
|
||||
require(tokenSupported[nftAddress], "GoldBricksFactory: Unsupported NFT");
|
||||
address to = _msgSender();
|
||||
bytes32 criteriaMessageHash = getMessageHash(
|
||||
to,
|
||||
nftAddress,
|
||||
address(this),
|
||||
startTime,
|
||||
saltNonce,
|
||||
tokenIds
|
||||
);
|
||||
checkSigner(executor, criteriaMessageHash, signature);
|
||||
for (uint256 i = 0; i < tokenIds.length; i++) {
|
||||
IAsset(nftAddress).burn(tokenIds[i]);
|
||||
}
|
||||
_useSignature(signature);
|
||||
emit TokenDecomposed(nftAddress, to, saltNonce, startTime, tokenIds);
|
||||
}
|
||||
|
||||
function addTokenSupport(address nftToken) external onlyOwner {
|
||||
tokenSupported[nftToken] = true;
|
||||
}
|
||||
|
||||
function removeTokenSupport(address nftToken) external onlyOwner {
|
||||
tokenSupported[nftToken] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev update executor
|
||||
*/
|
||||
function updateExecutor(address account) external onlyOwner {
|
||||
require(account != address(0), "address can not be zero");
|
||||
executor = account;
|
||||
}
|
||||
|
||||
function getMessageHash(
|
||||
address _to,
|
||||
address _nftAddress,
|
||||
address _contractAddress,
|
||||
uint256 _startTime,
|
||||
uint256 _saltNonce,
|
||||
uint256[] memory _ids
|
||||
) public pure returns (bytes32) {
|
||||
bytes memory encoded = abi.encodePacked(
|
||||
_to,
|
||||
_nftAddress,
|
||||
_contractAddress,
|
||||
_startTime,
|
||||
_saltNonce
|
||||
);
|
||||
uint256 len = _ids.length;
|
||||
for (uint256 i = 0; i < len; ++i) {
|
||||
encoded = bytes.concat(encoded, abi.encodePacked(_ids[i]));
|
||||
}
|
||||
return keccak256(encoded);
|
||||
}
|
||||
}
|
34
migrations/13_deploy_goldbricks.js
Normal file
34
migrations/13_deploy_goldbricks.js
Normal file
@ -0,0 +1,34 @@
|
||||
const BricksFactory = artifacts.require('logic/GoldBricksFactory');
|
||||
const NFT = artifacts.require("tokens/erc721/NFT");
|
||||
const base = require("../scripts/base");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const config = require(`../config/config_${network}`);
|
||||
let cfgs = base.loadData({ network });
|
||||
const brickAddress = cfgs.find((c) => c.name === "GoldBrick").address
|
||||
await deployer.deploy(BricksFactory);
|
||||
const factoryInstance = await BricksFactory.deployed();
|
||||
if(!factoryInstance) {
|
||||
return console.log("BricksFactory deploy failed.")
|
||||
}
|
||||
console.log("BricksFactory successfully deployed.")
|
||||
base.updateArray({
|
||||
name: "GoldBricksFactory",
|
||||
type: "logic",
|
||||
json: "assets/contracts/GoldBricksFactory.json",
|
||||
address: factoryInstance.address,
|
||||
network,
|
||||
});
|
||||
|
||||
try {
|
||||
const brickInstance = await NFT.at(brickAddress);
|
||||
await brickInstance.setMintRole(factoryInstance.address);
|
||||
console.log(`success set mint role for: ${brickInstance.address}`)
|
||||
await factoryInstance.updateExecutor(config.admins.admin);
|
||||
console.log(`success update executor for: ${factoryInstance.address}`)
|
||||
await factoryInstance.addTokenSupport(brickAddress);
|
||||
console.log(`success add token support for: ${factoryInstance.address}`)
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ module.exports = async function (deployer, network, accounts) {
|
||||
{ name: "HERO", desc: "CRYPTO ELITE'S HERO", limit: 0 },
|
||||
{ name: "PLANET", desc: "CF PLANET", limit: 0 },
|
||||
{ name: "TestHERO", desc: "CRYPTO ELITE'S HERO", limit: 0 },
|
||||
{ name: "GoldBrick", desc: "CRYPTO ELITE'S GOLDBRICK", limit: 0 },
|
||||
];
|
||||
for (let i = 0, l = tokens.length; i < l; i++) {
|
||||
const { name, desc, limit } = tokens[i];
|
||||
|
@ -3,126 +3,138 @@
|
||||
"name": "BEBadge",
|
||||
"type": "erc721",
|
||||
"json": "assets/contracts/BEBadge.json",
|
||||
"address": "0xCfEB869F69431e42cdB54A4F4f105C19C080A601"
|
||||
"address": "0xF30031788e1b8641B4531edf552753ce1BfdD5C4"
|
||||
},
|
||||
{
|
||||
"name": "BEMultiSigWallet",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/BEMultiSigWallet.json",
|
||||
"address": "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550"
|
||||
"address": "0xFb809153124ab0e7c3E4213435b6675e63743d3F"
|
||||
},
|
||||
{
|
||||
"name": "NftDistributor",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/NftDistributor.json",
|
||||
"address": "0xe982E462b094850F12AF94d21D470e21bE9D0E9C"
|
||||
"address": "0xf520767FDBE4443F89F37714EE1BeEC0BDF05B79"
|
||||
},
|
||||
{
|
||||
"name": "CEC",
|
||||
"type": "erc20",
|
||||
"json": "assets/contracts/FT.json",
|
||||
"address": "0x67B5656d60a809915323Bf2C40A8bEF15A152e3e"
|
||||
"address": "0x012A2C3C6f7F198A52E2820aEB48208d453EEa17"
|
||||
},
|
||||
{
|
||||
"name": "CEG",
|
||||
"type": "erc20",
|
||||
"json": "assets/contracts/FT.json",
|
||||
"address": "0x2612Af3A521c2df9EAF28422Ca335b04AdF3ac66"
|
||||
"address": "0x931985167bD678bA850156fc717443c34C856424"
|
||||
},
|
||||
{
|
||||
"name": "BEUSDC",
|
||||
"type": "erc20",
|
||||
"json": "assets/contracts/FT.json",
|
||||
"address": "0xA57B8a5584442B467b4689F1144D269d096A3daF"
|
||||
"address": "0x4a931bfa7331009b0973c615b6D0B4FCCd5A89bb"
|
||||
},
|
||||
{
|
||||
"name": "BEUSDT",
|
||||
"type": "erc20",
|
||||
"json": "assets/contracts/FT.json",
|
||||
"address": "0x26b4AFb60d6C903165150C6F0AA14F8016bE4aec"
|
||||
"address": "0xb48c8Fa8Bc892f2F48A5Ea3C5dD3335aFECB9b0c"
|
||||
},
|
||||
{
|
||||
"name": "HERO",
|
||||
"type": "erc721",
|
||||
"json": "assets/contracts/NFT.json",
|
||||
"address": "0xDb56f2e9369E0D7bD191099125a3f6C370F8ed15"
|
||||
"address": "0xF6F2BD751fA1EE87648709B8229b5B22e5Ab4E3b"
|
||||
},
|
||||
{
|
||||
"name": "PLANET",
|
||||
"type": "erc721",
|
||||
"json": "assets/contracts/NFT.json",
|
||||
"address": "0xA94B7f0465E98609391C623d0560C5720a3f2D33"
|
||||
"address": "0x20b28EebFa7Be6a4a4e0B8719845ce9e2C0345cb"
|
||||
},
|
||||
{
|
||||
"name": "TestHERO",
|
||||
"type": "erc721",
|
||||
"json": "assets/contracts/NFT.json",
|
||||
"address": "0x6eD79Aa1c71FD7BdBC515EfdA3Bd4e26394435cC"
|
||||
"address": "0x422e8DE05878Ab7Acd10D3d50A76532DE7A8B39E"
|
||||
},
|
||||
{
|
||||
"name": "GoldBrick",
|
||||
"type": "erc721",
|
||||
"json": "assets/contracts/NFT.json",
|
||||
"address": "0x19BCEBD43D23F75E7554669DF8db6dFA4c73bfa0"
|
||||
},
|
||||
{
|
||||
"name": "UserMinterFactory",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/UserMinterFactory.json",
|
||||
"address": "0xFC628dd79137395F3C9744e33b1c5DE554D94882"
|
||||
"address": "0xba1B19248020B0dfcDc9C53eAE31722B478334aE"
|
||||
},
|
||||
{
|
||||
"name": "BENftMarket",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/BENftMarket.json",
|
||||
"address": "0x21a59654176f2689d12E828B77a783072CD26680"
|
||||
"address": "0x30ac1D5546Af7382986f8357919f6BDeDa09442F"
|
||||
},
|
||||
{
|
||||
"name": "BENftMall",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/BENftMall.json",
|
||||
"address": "0xD86C8F0327494034F60e25074420BcCF560D5610"
|
||||
"address": "0xea8beafa3ABF2eC7e2404550EcaECfcD999C0d5B"
|
||||
},
|
||||
{
|
||||
"name": "BETokenMall",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/BETokenMall.json",
|
||||
"address": "0x4bf749ec68270027C5910220CEAB30Cc284c7BA2"
|
||||
"address": "0x49a7da6e47679c88563D718B2B42d199DA30b86f"
|
||||
},
|
||||
{
|
||||
"name": "GameItemMarket",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/GameItemMarket.json",
|
||||
"address": "0x86072CbFF48dA3C1F01824a6761A03F105BCC697"
|
||||
"address": "0x2B4162a630c1C51AC40e2d73dFeB5864606d888e"
|
||||
},
|
||||
{
|
||||
"name": "GameItemMall",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/GameItemMall.json",
|
||||
"address": "0xFF6049B87215476aBf744eaA3a476cBAd46fB1cA"
|
||||
"address": "0x2E5c38D7bD4d89561d3d914812C61f2DbaC7DEF6"
|
||||
},
|
||||
{
|
||||
"name": "Gacha",
|
||||
"type": "erc721",
|
||||
"json": "assets/contracts/BEBadge.json",
|
||||
"address": "0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A"
|
||||
"address": "0x4fd02791a3A4cbE14315634C3AdD9aDBef5d73d8"
|
||||
},
|
||||
{
|
||||
"name": "ClaimGachaFactory",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/ClaimBoxFactory.json",
|
||||
"address": "0x970e8f18ebfEa0B08810f33a5A40438b9530FBCF"
|
||||
"address": "0x3DEe4593fBc3DC789DF5828E5E10f7b4E3cb5C01"
|
||||
},
|
||||
{
|
||||
"name": "JSONMetadata",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/JSONMetadata.json",
|
||||
"address": "0x5b9b42d6e4B2e4Bf8d42Eba32D46918e10899B66"
|
||||
"address": "0x07C4b385eee49d0bC222F6D09D94A6D3A7B15060"
|
||||
},
|
||||
{
|
||||
"name": "ERC721Staking",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/ERC721Staking.json",
|
||||
"address": "0xB9bdBAEc07751F6d54d19A6B9995708873F3DE18"
|
||||
"address": "0xb4AE08EB231A021E74C237874545884975ae0A96"
|
||||
},
|
||||
{
|
||||
"name": "EvolveProxy",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/EvolveProxy.json",
|
||||
"address": "0x2c5f3c004878923f55A2a255F89Fe29393177509"
|
||||
"address": "0x4F475aD35D69b274e4EC58D76eA4Ce7081336049"
|
||||
},
|
||||
{
|
||||
"name": "GoldBricksFactory",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/GoldBricksFactory.json",
|
||||
"address": "0x6F6BfF298e182882091B8D0FF40208d09aE96B62"
|
||||
}
|
||||
]
|
136
test/goldbricks.test.js
Normal file
136
test/goldbricks.test.js
Normal file
@ -0,0 +1,136 @@
|
||||
const BricksFactory = artifacts.require("GoldBricksFactory");
|
||||
const NFT = artifacts.require("NFT");
|
||||
const { BN,expectEvent, expectRevert } = require("@openzeppelin/test-helpers");
|
||||
const { expect } = require("chai");
|
||||
|
||||
contract("BricksFactory", (accounts) => {
|
||||
const admin = accounts[0];
|
||||
const user = accounts[1];
|
||||
const executor = accounts[0];
|
||||
let brick;
|
||||
let brickFactory;
|
||||
|
||||
let ids = [new BN(1), new BN(2), new BN(3)];
|
||||
|
||||
beforeEach(async () => {
|
||||
brick = await NFT.new("CRYPTO ELITE'S GOLDBRICK", "GoldBrick", 0);
|
||||
brickFactory = await BricksFactory.new();
|
||||
await brick.setMintRole(brickFactory.address);
|
||||
await brickFactory.updateExecutor(executor);
|
||||
await brickFactory.addTokenSupport(brick.address);
|
||||
await brick.setApprovalForAll(brickFactory.address, true);
|
||||
await brick.setMintRole(admin);
|
||||
});
|
||||
|
||||
it("should assemble gold bricks correctly", async () => {
|
||||
const startTime = new BN(Date.now() / 1000 | 0);
|
||||
const saltNonce = new BN((Math.random() * 1000) | 0);
|
||||
let signStr = web3.utils.soliditySha3.apply(this,
|
||||
[user, brick.address, brickFactory.address,
|
||||
startTime, saltNonce, ...ids]);
|
||||
let signature = await web3.eth.sign(signStr, executor);
|
||||
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
|
||||
await brick.setApprovalForAll(brickFactory.address, true, { from: user });
|
||||
const receipt = await brickFactory.mintNft(
|
||||
brick.address,
|
||||
ids,
|
||||
startTime,
|
||||
saltNonce,
|
||||
signature,
|
||||
{ from: user }
|
||||
);
|
||||
const e = expectEvent(receipt, "TokenMinted", {});
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
expect(e.args.ids[i]).to.be.bignumber.equal(ids[i]);
|
||||
}
|
||||
|
||||
// Get the user's hero balance
|
||||
const userBrickBalance = await brick.balanceOf(user);
|
||||
expect(userBrickBalance).to.be.bignumber.equal(new BN(3));
|
||||
});
|
||||
|
||||
it("should revert for expired", async () => {
|
||||
const startTime = new BN((Date.now() / 1000 - 60 * 60 * 24 * 2) | 0);
|
||||
const saltNonce = new BN((Math.random() * 1000) | 0);
|
||||
let signStr = web3.utils.soliditySha3.apply(this,
|
||||
[user, brick.address, brickFactory.address,
|
||||
startTime, saltNonce, ...ids]);
|
||||
let signature = await web3.eth.sign(signStr, executor);
|
||||
signature = signature.replace(/00$/, "1a").replace(/01$/, "1d");
|
||||
await brick.setApprovalForAll(brickFactory.address, true, { from: user });
|
||||
expectRevert( brickFactory.mintNft(
|
||||
brick.address,
|
||||
ids,
|
||||
startTime,
|
||||
saltNonce,
|
||||
signature,
|
||||
{ from: user }
|
||||
), "expired, please send another transaction with new signature");
|
||||
|
||||
});
|
||||
|
||||
it("should revert for invalied signature", async () => {
|
||||
const startTime = new BN(Date.now() / 1000 | 0);
|
||||
const saltNonce = new BN((Math.random() * 1000) | 0);
|
||||
let signStr = web3.utils.soliditySha3.apply(this,
|
||||
[user, brick.address, brickFactory.address,
|
||||
startTime, saltNonce, ...ids]);
|
||||
let signature = await web3.eth.sign(signStr, executor);
|
||||
signature = signature.replace(/00$/, "1a").replace(/01$/, "1d");
|
||||
await brick.setApprovalForAll(brickFactory.address, true, { from: user });
|
||||
expectRevert( brickFactory.mintNft(
|
||||
brick.address,
|
||||
ids,
|
||||
startTime,
|
||||
saltNonce,
|
||||
signature,
|
||||
{ from: user }
|
||||
), "ECDSA: invalid signature.");
|
||||
|
||||
});
|
||||
|
||||
it("should revert for unsupported NFT", async () => {
|
||||
const startTime = new BN(Date.now() / 1000 | 0);
|
||||
const saltNonce = new BN((Math.random() * 1000) | 0);
|
||||
let signStr = web3.utils.soliditySha3.apply(this,
|
||||
[user, brick.address, brickFactory.address,
|
||||
startTime, saltNonce, ...ids]);
|
||||
let signature = await web3.eth.sign(signStr, executor);
|
||||
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
|
||||
await brick.setApprovalForAll(brickFactory.address, true, { from: user });
|
||||
expectRevert( brickFactory.mintNft(
|
||||
'0x50A8e60041A206AcaA5F844a1104896224be6F39',
|
||||
ids,
|
||||
startTime,
|
||||
saltNonce,
|
||||
signature,
|
||||
{ from: user }
|
||||
), "GoldBricksFactory: Unsupported NFT");
|
||||
|
||||
});
|
||||
|
||||
it("should burn gold bricks correctly", async () => {
|
||||
const startTime = Date.now() / 1000 | 0;
|
||||
const saltNonce = (Math.random() * 1000) | 0;
|
||||
let signStr = web3.utils.soliditySha3.apply(this,
|
||||
[user, brick.address, brickFactory.address, startTime, saltNonce, ...ids]);
|
||||
let signature = await web3.eth.sign(signStr, executor);
|
||||
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
|
||||
await brick.setApprovalForAll(brickFactory.address, true, { from: user });
|
||||
await brick.batchMint(user, ids, { from: admin });
|
||||
const receipt = await brickFactory.decomposeNft(
|
||||
brick.address,
|
||||
ids,
|
||||
startTime,
|
||||
saltNonce,
|
||||
signature,
|
||||
{ from: user }
|
||||
);
|
||||
const e = expectEvent(receipt, "TokenDecomposed", {});
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
expect(e.args.ids[i]).to.be.bignumber.equal(ids[i]);
|
||||
}
|
||||
const userBrickBalance = await brick.balanceOf(user);
|
||||
expect(userBrickBalance).to.be.bignumber.equal(new BN(0));
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user