From 1c2f2546289e0756266d3f20ee2a4539f07bbe6a Mon Sep 17 00:00:00 2001 From: weiqiushi Date: Thu, 28 Dec 2023 16:22:11 +0800 Subject: [PATCH] Create data and show data logic passed test --- contracts/public_data_storage.sol | 74 ++++++++----------- scripts/ERCMerkleTree.ts | 117 +++++++++++++++++++++++++++++ scripts/ERCMerkleTreeUtil.ts | 48 ++++++++++++ scripts/deploy.ts | 21 +++--- scripts/generate_mixhash.ts | 94 +++++++++++++++++++++++ scripts/generate_proof.ts | 62 ++++++++++++++++ scripts/generate_test_data.ts | 32 ++++++++ test/test_public_data.ts | 119 ++++++++++++++++++++---------- 8 files changed, 476 insertions(+), 91 deletions(-) create mode 100644 scripts/ERCMerkleTree.ts create mode 100644 scripts/ERCMerkleTreeUtil.ts create mode 100644 scripts/generate_mixhash.ts create mode 100644 scripts/generate_proof.ts create mode 100644 scripts/generate_test_data.ts diff --git a/contracts/public_data_storage.sol b/contracts/public_data_storage.sol index 4375fcc..0036f10 100644 --- a/contracts/public_data_storage.sol +++ b/contracts/public_data_storage.sol @@ -4,6 +4,8 @@ import "./gwt.sol"; import "./sortedlist.sol"; import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +import "hardhat/console.sol"; + using SortedScoreList for SortedScoreList.List; //Review:这个作为ERC的一部分,要仔细考虑一下 @@ -62,7 +64,7 @@ contract PublicDataStorage { struct CycleDataInfo { uint256 score; - address[] last_showers; + address[5] last_showers; uint8 shower_index; uint8 withdraw_status; } @@ -102,7 +104,6 @@ contract PublicDataStorage { event SupplierPubished(address supplier, bytes32 mixedHash, uint256 amount); event ShowDataProof(address supplier, bytes32 dataMixedHash, uint256 nonce_block_high, uint32 index_m, bytes32 proof_result); event WithdrawAward(bytes32 mixedHash, address user, uint256 amount); - event ChallengeSuccess(address challenger, address challenged, uint256 show_block_number, bytes32 mixedHash, uint256 amount); constructor(address _gwtToken) { gwtToken = GWTToken(_gwtToken); @@ -124,8 +125,8 @@ contract PublicDataStorage { } } - function getDataSize(bytes32 dataHash) public pure returns (uint64) { - return uint64(uint256(dataHash) >> 192) | uint64((1 << 62) - 1); + function lengthFromMixedHash(bytes32 dataMixedHash) public pure returns (uint64) { + return uint64(uint256(dataMixedHash) >> 192 & ((1 << 62) - 1)); } function _verifyBlockNumber(bytes32 dataMixedHash, uint256 blockNumber) internal pure returns(bool) { @@ -166,7 +167,7 @@ contract PublicDataStorage { if (fixedDataSize < sysMinDataSize) { fixedDataSize = sysMinDataSize; } - return (fixedDataSize * 10 ** 18) >> 30; + return (uint256(fixedDataSize) * 10 ** 18) >> 30; } function createPublicData( @@ -180,10 +181,10 @@ contract PublicDataStorage { require(dataMixedHash != bytes32(0), "data hash is empty"); PublicData storage publicDataInfo = public_datas[dataMixedHash]; - require(publicDataInfo.maxDeposit == 0); + require(publicDataInfo.maxDeposit == 0, "public data already exists"); // get data size from data hash - uint64 dataSize = getDataSize(dataMixedHash); + uint64 dataSize = lengthFromMixedHash(dataMixedHash); // 区分质押率和最小时长。最小时长是系统参数,质押率depositRatio是用户参数 // 质押率影响用户SHOW数据所需要冻结的质押 // minAmount = 数据大小*最小时长*质押率, @@ -200,7 +201,7 @@ contract PublicDataStorage { // TODO: 这里要考虑一下Owner的粒度: 合约Owner,Collection Owner,Token Owner publicDataInfo.nftContract = publicDataContract; } else { - require(dataMixedHash == IERC721VerfiyDataHash(publicDataContract).tokenDataHash(tokenId)); + require(dataMixedHash == IERC721VerfiyDataHash(publicDataContract).tokenDataHash(tokenId), "NFT data hash mismatch"); publicDataInfo.nftContract = publicDataContract; publicDataInfo.tokenId = tokenId; } @@ -227,8 +228,7 @@ contract PublicDataStorage { function addDeposit(bytes32 dataMixedHash, uint256 depositAmount) public { PublicData storage publicDataInfo = public_datas[dataMixedHash]; - require(publicDataInfo.maxDeposit > 0); - require(publicDataInfo.owner == msg.sender); + require(publicDataInfo.maxDeposit > 0, "public data not exist"); // transfer deposit gwtToken.transferFrom(msg.sender, address(this), depositAmount); @@ -281,7 +281,7 @@ contract PublicDataStorage { } function _getLockAmount(bytes32 dataMixedHash) internal view returns(uint256) { - uint64 dataSize = getDataSize(dataMixedHash); + uint64 dataSize = lengthFromMixedHash(dataMixedHash); return _dataSizeToGWT(dataSize) * sysMinDepositRatio * sysMinLockWeeks; } @@ -295,6 +295,8 @@ contract PublicDataStorage { supplierInfo.avalibleBalance -= lockAmount; supplierInfo.lockedBalance += lockAmount; supplierInfo.unlockBlock = block.number + sysConfigShowTimeout; + + emit SupplierBalanceChanged(supplierAddress, supplierInfo.avalibleBalance, supplierInfo.lockedBalance); } function _verifyDataProof(bytes32 dataMixedHash,uint256 nonce_block_high, uint32 index, bytes16[] calldata m_path, bytes calldata leafdata, bytes32 noise) private view returns(bytes32,bytes32) { @@ -309,35 +311,23 @@ contract PublicDataStorage { //验证leaf_data+index+path 和 dataMixedHash是匹配的,不匹配就revert // hash的头2bits表示hash算法,00 = sha256, 10 = keccak256 uint8 hashType = uint8(uint256(dataMixedHash) >> 254); - bytes32 dataHash; - if (hashType == 0) { - // sha256 - dataHash = _merkleRootWithSha256(m_path, index, _bytes32To16(sha256(leafdata))); - } else if (hashType == 2) { - // keccak256 - dataHash = _merkleRootWithKeccak256(m_path, index, _bytes32To16(keccak256(leafdata))); - } else { - revert("invalid hash type"); - } + bytes32 dataHash = _merkleRoot(hashType,m_path,index, _hashLeaf(hashType,leafdata)); //验证leaf_data+index+path 和 dataMixedHash是匹配的,不匹配就revert // 只比较后192位 require(dataHash & bytes32(uint256((1 << 192) - 1)) == dataMixedHash & bytes32(uint256((1 << 192) - 1)), "mixhash mismatch"); // 不需要计算插入位置,只是简单的在Leaf的数据后部和头部插入,也足够满足我们的设计目的了? - bytes memory new_leafdata; + bytes memory new_leafdata = bytes.concat(leafdata, nonce); + bytes32 new_root_hash = _merkleRoot(hashType,m_path,index, _hashLeaf(hashType,new_leafdata)); + bytes32 pow_hash = bytes32(0); + if(noise != 0) { //Enable PoW - new_leafdata = bytes.concat(leafdata, nonce); - bytes32 new_root_hash = _merkleRoot(hashType,m_path,index, _hashLeaf(hashType,new_leafdata)); - - new_leafdata = bytes.concat(noise, leafdata, nonce); - return (new_root_hash,_merkleRoot(hashType,m_path,index, _hashLeaf(hashType,new_leafdata))); - } else { - //Disable PoW - new_leafdata = bytes.concat(leafdata, nonce); - return (_merkleRoot(hashType,m_path,index, _hashLeaf(hashType,new_leafdata)),0); + pow_hash = _hashLeaf(hashType, bytes.concat(noise, leafdata, nonce)); } + + return (new_root_hash, pow_hash); } function _merkleRoot(uint8 hashType,bytes16[] calldata proof, uint32 leaf_index,bytes16 leaf_hash) internal pure returns (bytes32) { @@ -380,7 +370,7 @@ contract PublicDataStorage { function _merkleRootWithKeccak256(bytes16[] calldata proof, uint32 leaf_index,bytes16 leaf_hash) internal pure returns (bytes32) { bytes16 currentHash = leaf_hash; - bytes32 computedHash = 0; + bytes32 computedHash = bytes32(0); for (uint32 i = 0; i < proof.length; i++) { if (proof[i] != bytes32(0)) { if (leaf_index % 2 == 0) { @@ -389,8 +379,8 @@ contract PublicDataStorage { computedHash = _efficientKeccak256(proof[i], currentHash); } } - currentHash = _bytes32To16(computedHash); + //require(leaf_index >= 2, "invalid leaf_index"); leaf_index = leaf_index / 2; } @@ -419,11 +409,11 @@ contract PublicDataStorage { function showData(bytes32 dataMixedHash, uint256 nonce_block, uint32 index, bytes16[] calldata m_path, bytes calldata leafdata) public { address supplier = msg.sender; - require(nonce_block < block.number && block.number - nonce_block < maxNonceBlockDistance); + require(nonce_block < block.number && block.number - nonce_block <= maxNonceBlockDistance, "invalid nonce block"); _LockSupplierPledge(supplier, dataMixedHash); // 每个块的每个supplier只能show一次数据 - require(all_shows[block.number][supplier] == false); + require(all_shows[block.number][supplier] == false, "already showed in this block"); // check block.number meets certain conditions // TODO: 这个条件是否还需要?现在有showTimeout来控制show的频率了,可能会更好 @@ -465,7 +455,6 @@ contract PublicDataStorage { // 已经有挑战存在:判断是否结果更好,如果更好,更新结果,并更新区块高度 if(root_hash < publicDataInfo.proof_result) { //根据经济学模型对虚假的proof提供者进行惩罚 - uint64 dataSize = getDataSize(dataMixedHash); uint256 punishAmount = _getLockAmount(dataMixedHash); supplier_infos[publicDataInfo.prover].lockedBalance -= punishAmount; @@ -474,6 +463,7 @@ contract PublicDataStorage { gwtToken.transfer(msg.sender, punishAmount); oldProver = publicDataInfo.prover; emit SupplierPubished(publicDataInfo.prover, dataMixedHash, punishAmount); + emit SupplierBalanceChanged(publicDataInfo.prover, supplier_infos[publicDataInfo.prover].avalibleBalance, supplier_infos[publicDataInfo.prover].lockedBalance); publicDataInfo.proof_result = root_hash; publicDataInfo.proof_block = block.number; @@ -488,7 +478,7 @@ contract PublicDataStorage { CycleInfo storage cycleInfo = cycle_infos[_cycleNumber()]; CycleDataInfo storage dataInfo = cycleInfo.data_infos[dataMixedHash]; if (is_new_show) { - dataInfo.score += getDataSize(dataMixedHash); + dataInfo.score += lengthFromMixedHash(dataMixedHash); // insert supplier into last_showers if (dataInfo.shower_index >= 5) { @@ -562,7 +552,7 @@ contract PublicDataStorage { function withdrawAward(uint cycleNumber, bytes32 dataMixedHash) public { // 判断这次的cycle已经结束 - require(block.number > cycleNumber * blocksPerCycle + startBlock); + require(block.number > cycleNumber * blocksPerCycle + startBlock, "cycle not finish"); CycleInfo storage cycleInfo = cycle_infos[_cycleNumber()]; CycleDataInfo storage dataInfo = cycleInfo.data_infos[dataMixedHash]; //REVIEW:一次排序并保存的GAS和32次内存排序的成本问题? @@ -573,8 +563,8 @@ contract PublicDataStorage { // REVIEW 这个函数做的事情比较多,建议拆分,或则命名更优雅一些 uint8 withdrawUser = _getWithdrawRole(dataMixedHash); - require(withdrawUser > 0); - require(dataInfo.withdraw_status & withdrawUser == 0); + require(withdrawUser > 0, "cannot withdraw"); + require(dataInfo.withdraw_status & withdrawUser == 0, "already withdraw"); // 计算该得到多少奖励 uint256 totalReward = cycleInfo.total_award * 8 / 10; @@ -590,8 +580,4 @@ contract PublicDataStorage { emit WithdrawAward(dataMixedHash, msg.sender, reward); } - - function lengthFromMixedHash(bytes32 dataMixedHash) public pure returns (uint64) { - return uint64(uint256(dataMixedHash) >> 192 & ((1 << 62) - 1)); - } } diff --git a/scripts/ERCMerkleTree.ts b/scripts/ERCMerkleTree.ts new file mode 100644 index 0000000..4e99223 --- /dev/null +++ b/scripts/ERCMerkleTree.ts @@ -0,0 +1,117 @@ +import { ethers } from 'hardhat'; +import { Buffer } from "node:buffer"; +import { equalsBytes } from './ERCMerkleTreeUtil'; + +export enum HashType { + Sha256, + Keccak256, +} + +export function calcHash(buf: Uint8Array, type: HashType): Uint8Array { + let ret = type == HashType.Sha256 ? ethers.sha256(buf) : ethers.keccak256(buf); + + // 取低16bytes + return ethers.getBytes(ret); +} + +export interface MerkleTreeData { + type: HashType; + tree: string[][]; + root: string; +} + +const ZERO_BYTES16 = ethers.getBytes(ethers.ZeroHash).slice(16); + +export class MerkleTree { + private leaf_hash: Uint8Array[] = []; + private tree: Uint8Array[][] = []; + private root: Uint8Array = new Uint8Array(32); + constructor(public type: HashType) { + + } + + static load(data: MerkleTreeData): MerkleTree { + let ret = new MerkleTree(data.type); + ret.tree = data.tree.map((layer) => layer.map((v) => ethers.getBytes(v))); + ret.root = ethers.getBytes(data.root); + return ret; + } + + addLeaf(leaf: Uint8Array) { + this.leaf_hash.push(calcHash(leaf, this.type).slice(16)); + } + + calcTree() { + let cur_layer = this.leaf_hash; + this.tree.push(cur_layer); + let hash = new Uint8Array(32); + while (cur_layer.length > 1) { + let next_layer = []; + for (let i = 0; i < cur_layer.length; i += 2) { + if (i == cur_layer.length - 1) { + next_layer.push(cur_layer[i]); + } else { + hash = calcHash(new Uint8Array(Buffer.concat([cur_layer[i], cur_layer[i + 1]])), this.type); + next_layer.push(hash.slice(16)); + } + } + cur_layer = next_layer; + this.tree.push(cur_layer); + } + + this.root = hash; + } + + getRoot(): Uint8Array { + return this.root; + } + + getPath(index: number): Uint8Array[] { + let ret = []; + for (let layer = 0; layer < this.tree.length - 1; layer++) { + if (index % 2 == 1) { + ret.push(this.tree[layer][index - 1]); + } else { + ret.push(this.tree[layer][index + 1] || ZERO_BYTES16); + } + index = Math.floor(index / 2); + } + + return ret; + } + + save(): MerkleTreeData { + return { + type: this.type, + tree: this.tree.map((layer) => layer.map(ethers.hexlify)), + root: ethers.hexlify(this.getRoot()), + }; + } + + proofByPath(proof: Uint8Array[], leaf_index: number, leafdata: Uint8Array): Uint8Array { + let leaf_hash = calcHash(leafdata, this.type); + let currentHash = leaf_hash; + for (let i = 0; i < proof.length; i++) { + if (currentHash.length == 32) { + currentHash = currentHash.slice(16); + } + + if (!equalsBytes(proof[i], ZERO_BYTES16)) { + if (leaf_index % 2 == 0) { + currentHash = calcHash(new Uint8Array(Buffer.concat([currentHash, proof[i]])), this.type); + } else { + currentHash = calcHash(new Uint8Array(Buffer.concat([proof[i], currentHash])), this.type); + } + } + + leaf_index = Math.floor(leaf_index / 2); + } + + return currentHash; + } + + verify(proof: Uint8Array[], leaf_index: number, leafdata: Uint8Array): boolean { + let calc_root = this.proofByPath(proof, leaf_index, leafdata); + return equalsBytes(calc_root, this.getRoot()); + } +} diff --git a/scripts/ERCMerkleTreeUtil.ts b/scripts/ERCMerkleTreeUtil.ts new file mode 100644 index 0000000..49ff036 --- /dev/null +++ b/scripts/ERCMerkleTreeUtil.ts @@ -0,0 +1,48 @@ +export function compareBytes(a: Uint8Array, b: Uint8Array): number { + const n = Math.min(a.length, b.length); + + for (let i = 0; i < n; i++) { + if (a[i] !== b[i]) { + return a[i]! - b[i]!; + } + } + + return a.length - b.length; +} + +export function equalsBytes(a: Uint8Array, b: Uint8Array): boolean { + if (a.length !== b.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + return true; +} + +export function checkBounds(array: unknown[], index: number) { + if (index < 0 || index >= array.length) { + throw new Error('Index out of bounds'); + } +} + +export function throwError(message?: string): never { + throw new Error(message); +} + +export function concatBytes(...arrays: Uint8Array[]) { + if (!arrays.every((a) => a instanceof Uint8Array)) + throw new Error('Uint8Array list expected'); + if (arrays.length === 1) + return arrays[0]; + const length = arrays.reduce((a, arr) => a + arr.length, 0); + const result = new Uint8Array(length); + for (let i = 0, pad = 0; i < arrays.length; i++) { + const arr = arrays[i]; + result.set(arr, pad); + pad += arr.length; + } + return result; +} diff --git a/scripts/deploy.ts b/scripts/deploy.ts index e630ce6..026dfa0 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -5,26 +5,29 @@ async function main() { const dmcContract = await (await ethers.deployContract("DMCToken", [ethers.parseEther("1000000")])).waitForDeployment(); let dmcAddress = await dmcContract.getAddress(); console.log("DMCToken deployed to:", dmcAddress); - const pstContract = await (await ethers.deployContract("GWTToken", [dmcAddress])).waitForDeployment(); + const gwtContract = await (await ethers.deployContract("GWTToken", [dmcAddress])).waitForDeployment(); - let pstAddress = await pstContract.getAddress(); - console.log("GWT deployed to:", pstAddress); - - const exchangeContract = await (await ethers.deployContract("StorageExchange", [pstAddress])).waitForDeployment(); + let gwtAddress = await gwtContract.getAddress(); + console.log("GWT deployed to:", gwtAddress); + /* + const exchangeContract = await (await ethers.deployContract("StorageExchange", [gwtAddress])).waitForDeployment(); let exchangeAddress = await exchangeContract.getAddress(); console.log("Exchange deployed to:", exchangeAddress); - + */ const sortedScoreList = await (await ethers.deployContract("SortedScoreList")).waitForDeployment(); let sortedScoreListAddress = await sortedScoreList.getAddress(); console.log("SortedScoreList deployed to:", sortedScoreListAddress); - const publicDataStorage = await (await ethers.deployContract("PublicDataStorage", [pstAddress], {libraries: {"SortedScoreList": sortedScoreListAddress}})).waitForDeployment(); + const publicDataStorage = await (await ethers.deployContract("PublicDataStorage", [gwtAddress], {libraries: {"SortedScoreList": sortedScoreListAddress}})).waitForDeployment(); let publicDataStorageAddress = await publicDataStorage.getAddress(); console.log("PublicDataStorage deployed to:", publicDataStorageAddress); + await(await gwtContract.enableTransfer([publicDataStorageAddress])).wait(); + if (network.name !== "hardhat") { fs.writeFileSync(`${network.name}-deployed.json`, JSON.stringify({ - PSTToken: pstAddress, - StorageExchange: exchangeAddress + DMCToken: dmcAddress, + GWTToken: gwtAddress, + PublicDataStore: publicDataStorageAddress })); } } diff --git a/scripts/generate_mixhash.ts b/scripts/generate_mixhash.ts new file mode 100644 index 0000000..0f4d2b7 --- /dev/null +++ b/scripts/generate_mixhash.ts @@ -0,0 +1,94 @@ +import { HashType, MerkleTree } from "./ERCMerkleTree"; +import fs from "node:fs" +import path from "node:path" +import { ethers } from "hardhat"; +export { HashType, MerkleTree } from "./ERCMerkleTree"; + +export function generateMixHash(filePath: string, type: HashType, treeStorePath: string): Uint8Array { + let file_op = fs.openSync(filePath, "r"); + let length = fs.statSync(filePath).size; + let buf = new Uint8Array(1024); + let begin = 0; + let tree = new MerkleTree(type); + process.stdout.write("begin read file\n"); + while (true) { + process.stdout.clearLine(0); + process.stdout.cursorTo(0); + buf.fill(0); + let n = fs.readSync(file_op, buf); + if (n == 0) { + break; + } + begin += n; + process.stdout.write(`reading file: ${begin}/${length}`); + tree.addLeaf(buf); + } + console.log("calcuteing tree...") + tree.calcTree(); + let root_hash = tree.getRoot(); + + //let full_hash = caclRoot(leaf_hash,type) + new DataView(root_hash.buffer).setBigUint64(0, BigInt(length), false); + + root_hash[0] &= (1 << 6) - 1; + switch (type) { + case HashType.Sha256: + break; + case HashType.Keccak256: + root_hash[0] |= 1 << 7; + break; + default: + throw new Error("unknown hash type"); + } + + fs.writeFileSync(treeStorePath, JSON.stringify(tree.save())); + + return root_hash; +} + +function recoverHash(filePath: string, merkle_tree_file: string): Uint8Array { + let length = fs.statSync(filePath).size; + + let tree = MerkleTree.load(JSON.parse(fs.readFileSync(merkle_tree_file, {encoding: 'utf-8'}))); + + let root_hash = tree.getRoot(); + + //let full_hash = caclRoot(leaf_hash,type) + new DataView(root_hash.buffer).setBigUint64(0, BigInt(length), false); + + root_hash[0] &= (1 << 6) - 1; + switch (tree.type) { + case HashType.Sha256: + break; + case HashType.Keccak256: + root_hash[0] |= 1 << 7; + break; + default: + throw new Error("unknown hash type"); + } + + return root_hash; +} + +export function getSize(mixedHashHex: string): number { + let mixedHash = ethers.getBytes(mixedHashHex); + mixedHash[0] &= (1 << 6) - 1; + let size = new DataView(mixedHash.buffer).getBigUint64(0, false); + + return Number(size); +} + +let test_file_path = "C:\\TDDOWNLOAD\\MTool_8C34B84D.zip"; +let hash_type = HashType.Keccak256; + +async function run(filepath: string, type: HashType) { + if (!fs.existsSync("merkleData")) { + fs.mkdirSync("merkleData"); + } + let merkle_store_path = path.join("merkleData", path.basename(filepath) + ".json"); + + let root_hash = generateMixHash(filepath, type, merkle_store_path); + console.log("root_hash: ", ethers.hexlify(root_hash)); +} + +//run(test_file_path, hash_type).then(() => {process.exit(0)}) \ No newline at end of file diff --git a/scripts/generate_proof.ts b/scripts/generate_proof.ts new file mode 100644 index 0000000..87a1247 --- /dev/null +++ b/scripts/generate_proof.ts @@ -0,0 +1,62 @@ +import {ethers} from "hardhat" +import { mine } from "@nomicfoundation/hardhat-network-helpers"; + +import fs from "node:fs" +import path from "node:path" +import { MerkleTree } from "./ERCMerkleTree"; +import { compareBytes } from "./ERCMerkleTreeUtil"; + +export async function generateProof(filepath: string, nonce_block_height: number, treeStorePath: string): Promise<[number, Uint8Array[], Uint8Array, Uint8Array]> { + console.log("loading merkle tree"); + let tree = MerkleTree.load(JSON.parse(fs.readFileSync(treeStorePath, {encoding: 'utf-8'}))); + + let length = fs.statSync(filepath).size; + let total_leaf_size = Math.ceil(length / 1024); + let nonce = ethers.getBytes((await ethers.provider.getBlock(nonce_block_height))!.hash!); + console.log("calc for nonce ", ethers.hexlify(nonce)) + let file_op = fs.openSync(filepath, "r"); + + let min_root; + let min_index; + for (let index = 0; index < total_leaf_size; index++) { + let buf = new Uint8Array(1024); + buf.fill(0); + + fs.readSync(file_op, buf, {position: index * 1024}); + let path = tree.getPath(index); + let new_root = tree.proofByPath(path, index, new Uint8Array(Buffer.concat([buf, nonce]))); + if (min_root == undefined) { + min_root = new_root; + min_index = index; + } else if (compareBytes(new_root, min_root) < 0) { + min_root = new_root; + min_index = index; + } + } + + console.log("min index:", min_index); + + let min_leaf = new Uint8Array(1024); + min_leaf.fill(0); + fs.readSync(file_op, min_leaf, {position: min_index! * 1024}); + + let path = tree.getPath(min_index!); + + return [min_index!, path, min_leaf, min_root!]; +} + +let test_file_path = "C:\\TDDOWNLOAD\\updateshaoniandream.apk"; + +async function main(filepath: string) { + if (!fs.existsSync("merkleData")) { + fs.mkdirSync("merkleData"); + } + let merkle_store_path = path.join("merkleData", path.basename(filepath) + ".json"); + await mine(); + let number = await ethers.provider.getBlockNumber() + let [min_index, noise] = await generateProof(filepath, number, merkle_store_path, false); + + console.log("min_index:", min_index, "at block", number); +} + +// main(test_file_path).then(() => {process.exit(0);}); \ No newline at end of file diff --git a/scripts/generate_test_data.ts b/scripts/generate_test_data.ts new file mode 100644 index 0000000..85a0a85 --- /dev/null +++ b/scripts/generate_test_data.ts @@ -0,0 +1,32 @@ +import { ethers } from "hardhat"; +import fs from "node:fs"; +import { HashType, generateMixHash } from "./generate_mixhash"; + +function genTestData() { + let datas = []; + let data_num = 10; + if (!fs.existsSync("testDatas")) { + fs.mkdirSync("testDatas"); + } + // 创建10个10K~20K之间的数据 + for (let index = 1; index <= data_num; index++) { + let data_length = Math.floor(Math.random() * 10240) + 10240; + let data = ethers.randomBytes(data_length); + + let data_file_path = `testDatas/test_data_${index}.bin`; + let merkle_file_path = `testDatas/test_data_${index}.merkle`; + fs.writeFileSync(data_file_path, data, {}); + + let hash = generateMixHash(data_file_path, HashType.Keccak256, merkle_file_path); + + datas.push({ + data_file_path, + merkle_file_path, + hash: ethers.hexlify(hash) + }); + } + + fs.writeFileSync("testDatas/test_data.json", JSON.stringify(datas)); +} + +genTestData(); \ No newline at end of file diff --git a/test/test_public_data.ts b/test/test_public_data.ts index e8bf21b..17e7870 100644 --- a/test/test_public_data.ts +++ b/test/test_public_data.ts @@ -1,69 +1,112 @@ import hre, { ethers } from "hardhat"; import { expect } from "chai"; -import { FakeNFTContract, GWTToken, PublicDataStorage } from "../typechain-types"; - -// 需要一个假的NFT合约,用来测试 -// 给这个假NFT合约放1个NFT进去 -// owner signer[0] - -// 让signer[0]通过NFT创建一个public data,检查数据账户余额和奖池余额 -// 让signer[1]在错误的块上show数据,应该会失败 -// 让signer[1]show数据,检查signer[1]余额和数据账户余额 -// 尝试在同一块内让signer[1]再次show数据,此处应该失败 -// 让signer[11]增加质押金,成为Data1的sponser -// 让signer[2]到[7]都show数据,确定last_shower正确 -// 前进到当前cycle结束 -// signer[12]通过普通数据创建一个public data,检查此轮的奖池余额是否正确 -// signer[0]将NFT的owner转移给signer[13] -// signer[0]提取奖金,应该会失败 -// signer[11], signer[12], signer[3-7]提取奖金,检查提取的余额是否正确 +import { DMCToken, FakeNFTContract, GWTToken, PublicDataStorage } from "../typechain-types"; + +import * as TestDatas from "../testDatas/test_data.json"; +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; +import { mine } from "@nomicfoundation/hardhat-network-helpers"; + +import { generateProof } from "../scripts/generate_proof"; describe("PublicDataStorage", function () { let contract: PublicDataStorage; + let dmcToken: DMCToken; let gwtToken: GWTToken; + let signers: HardhatEthersSigner[]; let nftContract: FakeNFTContract + async function deployContracts() { let listLibrary = await (await hre.ethers.getContractFactory("SortedScoreList")).deploy(); - - gwtToken = await (await hre.ethers.deployContract("GWTToken", [ethers.parseEther("100000000")])).waitForDeployment(); - nftContract = await (await hre.ethers.deployContract("FakeNFTContract")).waitForDeployment(); + dmcToken = await (await ethers.deployContract("DMCToken", [ethers.parseEther("10000000")])).waitForDeployment() + gwtToken = await (await ethers.deployContract("GWTToken", [await dmcToken.getAddress()])).waitForDeployment() + // nftContract = await (await hre.ethers.deployContract("FakeNFTContract")).waitForDeployment(); + contract = await (await hre.ethers.deployContract("PublicDataStorage", [await gwtToken.getAddress()], {libraries: { + SortedScoreList: await listLibrary.getAddress() + }})).waitForDeployment(); - // TODO: -// contract = await (await hre.ethers.deployContract("PublicDataStorage", {libraries: { -// SortedScoreList: await listLibrary.getAddress() -// }})).waitForDeployment(); + await (await gwtToken.enableTransfer([await contract.getAddress()])).wait(); } before(async () => { await deployContracts(); + signers = await ethers.getSigners() - // TODO: - - // await ((await nftContract.addData("", 1)).wait()); + for (const signer of signers) { + await (await dmcToken.transfer(await signer.address, ethers.parseEther("1000"))).wait(); + await (await dmcToken.connect(signer).approve(await gwtToken.getAddress(), ethers.parseEther("1000"))).wait(); + await (await gwtToken.connect(signer).exchange(ethers.parseEther("1000"))).wait(); + await (await gwtToken.connect(signer).approve(await contract.getAddress(), ethers.parseEther("210000"))).wait(); + } }); - it("create NFT public data"); + it("create public data", async () => { + // 需要的最小抵押:1/8 GB * 96(周) * 64(倍) = 768 GWT + await expect(contract.createPublicData(TestDatas[0].hash, 64, ethers.parseEther("768"), ethers.ZeroAddress, 0)) + .emit(contract, "PublicDataCreated").withArgs(TestDatas[0].hash) + .emit(contract, "SponsorChanged").withArgs(TestDatas[0].hash, ethers.ZeroAddress, signers[0].address) + .emit(contract, "DepositData").withArgs(signers[0].address, TestDatas[0].hash, ethers.parseEther("614.4"), ethers.parseEther("153.6")); - it("show data on wrong block"); + expect(await contract.dataBalance(TestDatas[0].hash)).to.equal(ethers.parseEther("614.4")); + }); - it("show data"); + it("deposit data", async () => { + await expect(contract.connect(signers[1]).addDeposit(TestDatas[0].hash, ethers.parseEther("100"))) + .emit(contract, "DepositData").withArgs(signers[1].address, TestDatas[0].hash, ethers.parseEther("80"), ethers.parseEther("20")); - it("show data twice on same block"); + expect(await contract.dataBalance(TestDatas[0].hash)).to.equal(ethers.parseEther("694.4")); + }); - it("change sponser"); + it("deposit data and became sponser", async () => { + await expect(contract.connect(signers[1]).addDeposit(TestDatas[0].hash, ethers.parseEther("1000"))) + .emit(contract, "DepositData").withArgs(signers[1].address, TestDatas[0].hash, ethers.parseEther("800"), ethers.parseEther("200")) + .emit(contract, "SponsorChanged").withArgs(TestDatas[0].hash, signers[0].address, signers[1].address); - it("several people show data"); + expect(await contract.dataBalance(TestDatas[0].hash)).to.equal(ethers.parseEther("1494.4")); + }); + + it("supplier pledge GWT", async () => { + await (expect(contract.connect(signers[2]).pledgeGwt(ethers.parseEther("10000")))) + .emit(contract, "SupplierBalanceChanged").withArgs(signers[2].address, ethers.parseEther("10000"), 0); - it("forward to next cycle"); + await (expect(contract.connect(signers[3]).pledgeGwt(ethers.parseEther("10000")))) + .emit(contract, "SupplierBalanceChanged").withArgs(signers[3].address, ethers.parseEther("10000"), 0); + }); - it("create normal public data"); + it("show data", async () => { + let nonce_block = await ethers.provider.getBlockNumber(); + await mine(); - it("change NFT owner"); + let [min_index, path, leaf, proof] = await generateProof(TestDatas[0].data_file_path, nonce_block, TestDatas[0].merkle_file_path); - it("wrong people withdraw reward"); + // 这个操作会锁定signers[2]的余额 1/8 GB * 24(周) * 64(倍) = 192 GWT + await expect(contract.connect(signers[2]).showData(TestDatas[0].hash, nonce_block, min_index, path, leaf)) + .emit(contract, "ShowDataProof").withArgs(signers[2].address, TestDatas[0].hash, nonce_block, min_index, proof) + .emit(contract, "SupplierBalanceChanged").withArgs(signers[2].address, ethers.parseEther("9808"), ethers.parseEther("192")); + }); - it("several people withdraw reward"); + it("show data on same block"); + + it("show data again", async() => { + await mine(720); + + let nonce_block = await ethers.provider.getBlockNumber(); + await mine(); + let [min_index, path, leaf, proof] = await generateProof(TestDatas[0].data_file_path, nonce_block, TestDatas[0].merkle_file_path); + let tx = contract.connect(signers[3]).showData(TestDatas[0].hash, nonce_block, min_index, path, leaf); + await expect(tx) + .emit(contract, "SupplierReward").withArgs(signers[2].address, TestDatas[0].hash, ethers.parseEther("149.44")) + .emit(contract, "ShowDataProof").withArgs(signers[3].address, TestDatas[0].hash, nonce_block, min_index, proof) + .emit(contract, "SupplierBalanceChanged").withArgs(signers[3].address, ethers.parseEther("9808"), ethers.parseEther("192")); + + // signers[1]得到奖励, 奖励从data[0]的余额里扣除 + // 得到的奖励:1494.4 * 0.1 * 0.8 = 119.552 + // 余额扣除:1494.4 * 0.1 = 149.44 + await expect(tx).changeTokenBalance(gwtToken, signers[2].address, ethers.parseEther("119.552")) + + + expect(await contract.dataBalance(TestDatas[0].hash)).to.equal(ethers.parseEther("1344.96")); + }); }); \ No newline at end of file