diff --git a/contracts/public_data_storage.sol b/contracts/public_data_storage.sol index 1c00eda..0046770 100644 --- a/contracts/public_data_storage.sol +++ b/contracts/public_data_storage.sol @@ -289,7 +289,7 @@ contract PublicDataStorage { // 更新这次cycle的score排名 if (cycleInfo.score_list.maxlen() < topRewards) { - cycleInfo.score_list.SetMaxLen(topRewards); + cycleInfo.score_list.setMaxLen(topRewards); } cycleInfo.score_list.updateScore(dataMixedHash, dataInfo.score); diff --git a/contracts/sortedlist.sol b/contracts/sortedlist.sol index 926435e..736993d 100644 --- a/contracts/sortedlist.sol +++ b/contracts/sortedlist.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import "hardhat/console.sol"; + library SortedScoreList { struct List { uint256 max_length; @@ -12,16 +14,20 @@ library SortedScoreList { function _ensureSize(List storage self) private { uint32 cur_length = 0; bytes32 current = self.head; + bytes32 prev = current; while (cur_length < self.max_length && current != bytes32(0)) { cur_length += 1; + prev = current; current = self.sorted[current]; } // 这里其实只会去掉最后一个 if (current != bytes32(0)) { - self.sorted[current] = bytes32(0); + self.sorted[prev] = bytes32(0); delete self.scores[current]; delete self.sorted[current]; + + console.log("after length %d", length(self)); } } @@ -47,36 +53,39 @@ library SortedScoreList { } function updateScore(List storage self, bytes32 mixedHash, uint256 score) public { - if (score == 0) { + if (self.scores[mixedHash] == score) { return; } - if (self.head == bytes32(0)) { // 第一个插入的数据 - self.head = mixedHash; - self.scores[mixedHash] = score; - } else { - // 由于max_length有限,这里做两次遍历也并不过多消耗性能 - _deleteScore(self, mixedHash); - bytes32 current = self.head; - while (true) { - bytes32 next = self.sorted[current]; - // 同score的数据先到先占,这里利用了score的默认值为0的特性,这个循环一定会结束 - if (self.scores[next] < score) { - // TODO: 如果插入的数据是最后一个的话,会变成先插入再删除,这里可以优化 - - self.sorted[mixedHash] = next; - self.sorted[current] = mixedHash; - self.scores[mixedHash] = score; - - break; + // 由于max_length有限,这里做两次遍历也并不过多消耗性能 + _deleteScore(self, mixedHash); + + bytes32 currect = self.head; + bytes32 prev = bytes32(0); + uint cur_index = 0; + while (true) { + // 同score的数据先到先占,这里利用了score的默认值为0的特性,这个循环一定会结束 + if (self.scores[currect] < score) { + // TODO: 如果插入的数据是最后一个的话,会变成先插入再删除,这里可以优化 + if (prev != bytes32(0)) { + self.sorted[prev] = mixedHash; } - current = next; - next = self.sorted[current]; + self.sorted[mixedHash] = currect; + self.scores[mixedHash] = score; + break; } + cur_index += 1; + prev = currect; + currect = self.sorted[currect]; + } - // 这里认为,往存储里写一个bytes32 end的值,比遍历要贵 - _ensureSize(self); + if (cur_index == 0) { + console.log("insert into head"); + self.head = mixedHash; } + + // 这里认为,往存储里写一个bytes32 end的值,比遍历要贵 + _ensureSize(self); } function length(List storage self) public view returns (uint256) { @@ -118,8 +127,8 @@ library SortedScoreList { return 0; } - function SetMaxLen(List storage self, uint256 max_length) public { - require(max_length > self.max_length); + function setMaxLen(List storage self, uint256 max_length) public { + require(max_length > self.max_length, "max_length must be greater than current max_length"); self.max_length = max_length; } diff --git a/contracts/test_list.sol b/contracts/test_list.sol index bc89be9..1139742 100644 --- a/contracts/test_list.sol +++ b/contracts/test_list.sol @@ -18,4 +18,20 @@ contract TestList { function getSortedList() public view returns (bytes32[] memory) { return list.getSortedList(); } + + function setMaxLen(uint256 maxLen) public { + list.setMaxLen(maxLen); + } + + function maxLen() public view returns (uint256) { + return list.maxlen(); + } + + function exists(bytes32 mixedHash) public view returns (bool) { + return list.exists(mixedHash); + } + + function getRanking(bytes32 mixedHash) public view returns (uint256) { + return list.getRanking(mixedHash); + } } \ No newline at end of file diff --git a/test/test_sortedlist.ts b/test/test_sortedlist.ts new file mode 100644 index 0000000..c436833 --- /dev/null +++ b/test/test_sortedlist.ts @@ -0,0 +1,97 @@ +import hre from "hardhat"; +import { expect } from "chai"; +import { TestList } from "../typechain-types"; + +const toSorted = require('array.prototype.tosorted') + +const testDatas = [ + { "hash": "0x39c767e230f1cc4d8fa7baa4ef8c39bc2e4add8680d09bfe086e1efdaa0d6437", "score": 290 }, // 10 + { "hash": "0x96aa737f656f90a2f6f90a293b404f2a44529add34f863a18bf51d47bbaec589", "score": 332 }, // 9 + { "hash": "0xde0f510c9a18f7f15d24f9364e93d1e07a98d5a845fc6f230643a9da89b79ac4", "score": 100 }, // 14 + { "hash": "0x02663478495af95f645416a042fedb2d7237b41c50f6fc5471d7a1235e558f52", "score": 785 }, // 3 + { "hash": "0x7008a25999df0f489a692c3f3c4ccb4aaa45c6867ca2fb417b8b9930ab619dfd", "score": 471 }, // 8 + { "hash": "0xa8889b6afd0e4de49140777bf0b8da7910e5361e463c9f990b36d0770b3f8eae", "score": 474 }, // 7 + { "hash": "0x79218525b0548862f03172bc3c88ad7988b47aab05e1c54febab059e4d33efd4", "score": 88 }, // 15 + { "hash": "0x742797a513cef1dfd466b3decd632284fccc6e1cfcb07d78ad33406a642cb408", "score": 244 }, // 12 + { "hash": "0xd0b648691c90f1d35c814664b32dad56e442eeb58f8d93aafb2cd9f0d346d89e", "score": 679 }, // 6 + { "hash": "0xaa1dc0f0587b2a302e0cd6136026b56ff418f77a9803b87b6e16f28f216193fe", "score": 78 }, // 17 + { "hash": "0xd3fff898a835a8c4702885d65e1ce5394648b26b6b6d044eef0ea998f41b428d", "score": 226 }, // 13 + { "hash": "0xb217112856caa6e5c1dbce3414e4ecb3b4380231208d07601635127bd44ce213", "score": 251 }, // 11 + { "hash": "0x2df33298245c73618a534a77babfdb6aba2cbe447a96cb1e57e53a4af570d86a", "score": 83 }, // 16 + { "hash": "0x257d8456e82b21525dd866ab75dd0fb86b9b2f2e1cec0df6295cd486374fbea3", "score": 691 }, // 5 + { "hash": "0x974319f60fafd6cdbf5dcb6e0b58f64df96e43c4d02198d5393e2e3e5549a531", "score": 782 }, // 4 + { "hash": "0x2568f5563305e91f72b8e7b340178834d1aa9aac63b52dfa13e687a4f5f51646", "score": 840 }, // 2 + { "hash": "0x79e0685a902fe9e705e19acbd80ae3910381dacded27f7a24556566df1335c21", "score": 910 }] // 1 + +const sortdatas = testDatas.toSorted((a: any, b: any) => { + if (a.score < b.score) { + return 1; + } else if (a.score > b.score) { + return -1; + } else { + return 0; + } +}) + +describe("ShortedList", function () { + let contract: TestList; + async function deployContracts() { + let listLibrary = await (await hre.ethers.getContractFactory("SortedScoreList")).deploy(); + contract = await (await hre.ethers.deployContract("TestList", {libraries: { + SortedScoreList: await listLibrary.getAddress() + }})).waitForDeployment(); + } + + before(async () => { + await deployContracts() + }); + + it("set max len", async () => { + await (await contract.setMaxLen(8)).wait(); + expect(await contract.maxLen()).to.equal(8); + }) + + it("set max len again will revert", async () => { + await expect(contract.setMaxLen(4)).to.be.revertedWith("max_length must be greater than current max_length"); + }); + + it("set max len greater will be accepted", async () => { + await (await contract.setMaxLen(16)).wait(); + expect(await contract.maxLen()).to.equal(16); + }) + + it("one element", async () => { + await (await contract.addScore(testDatas[0].hash, testDatas[0].score)).wait(); + + expect(await contract.exists(testDatas[0].hash)).to.equal(true); + expect(await contract.getLength()).to.equal(1); + + expect(await contract.getRanking(testDatas[0].hash)).to.equal(1); + }) + + it("add all elements", async () => { + // 这里准备了17个数据,插入之后排序最后一位的数据会被删除 + for (let index = 0; index < testDatas.length; index++) { + await (await contract.addScore(testDatas[index].hash, testDatas[index].score)).wait(); + } + + expect(await contract.getLength()).to.equal(16); + + // 检查顺序 + for (let index = 0; index < sortdatas.length - 1; index++) { + expect(await contract.getRanking(sortdatas[index].hash)).to.equal(index+1); + } + }) + + it("update one element", async () => { + // 选择原来12位的数据,更新分数为254,超过原来11位的251,12就变成11了,之前的11就变成12了 + let hash = "0x742797a513cef1dfd466b3decd632284fccc6e1cfcb07d78ad33406a642cb408"; + await (await contract.addScore(hash, 254)) + + expect(await contract.getLength()).to.equal(16); + + expect(await contract.getRanking(hash)).to.equal(11); + + expect(await contract.getRanking("0xb217112856caa6e5c1dbce3414e4ecb3b4380231208d07601635127bd44ce213")).to.equal(12); + }) +}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 574e785..8947e66 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "lib": ["ES2023"] } }