From 36822295b9d6b6202098ab1c6769d5596842c530 Mon Sep 17 00:00:00 2001 From: weiqiushi Date: Wed, 11 Sep 2024 17:47:50 +0800 Subject: [PATCH] =?UTF-8?q?Update=20tag=20contract=20and=20doc=EF=BC=8C=20?= =?UTF-8?q?add=20tag=20testcase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/data_tag.sol | 234 +++++++++++++++++++++------ "doc/TAG\347\263\273\347\273\237.md" | 111 ++----------- test/test_data_tag.ts | 126 +++++++++++++++ 3 files changed, 324 insertions(+), 147 deletions(-) create mode 100644 test/test_data_tag.ts diff --git a/contracts/data_tag.sol b/contracts/data_tag.sol index 2810fc3..ac06938 100644 --- a/contracts/data_tag.sol +++ b/contracts/data_tag.sol @@ -3,102 +3,232 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/Ownable.sol"; +import "hardhat/console.sol"; + contract DataTag is Ownable { - struct Tag { - string name; - string desc; - bytes32 parent; - bytes32[] children; + // 通用的,包含赞同/反对信息的Meta数据结构。tag和data-tag都会用到这个数据结构 + struct MetaData { + bool valid; // 这个字段通常用在mapping的value,用这个字段表示这个value是否为空 + string meta; // 元数据。在tag系统下,它是tag的描述;在data-tag系统下,它是附加这个tag的原因 + uint128 like; + uint128 dislike; + mapping(address => int8) userlike; // 1代表赞,0代表不点,-1代表否 } - mapping(bytes32 => Tag) public tags; // 由name的hash做key - - struct DataTagInfo { - bool valid; // valid为true,表示数据有这个tag。因为solidity的map始终返回值 + struct MetaDataOutput { + string meta; uint128 like; uint128 dislike; - mapping(address => int8) userlike; // 1代表赞,0代表不点,-1代表否 + int8 myLike; + } + + struct Tag { + string name; // 这个tag的单独名字 + bytes32 parent; // parent为0表示顶层TAG + bytes32[] children; // 考虑一个TAG的子TAG不会很多 + mapping(address => MetaData) metas; // 每个用户对tag的描述 + // 由于TAG可能包括很多数据,合约里不提供反向查询,靠后端扫描事件来重建数据库 } + // 由于mapping不能返回给外界,需要一个单独的结构体来支持view函数 + struct TagOutput { + string fullName; // 直接返回整个路径,由"/"分隔 + bytes32 parent; + string[] children; + // 因为使用哪种描述,是前/后端的逻辑,这里就不返回任何描述信息 + } + + mapping(bytes32 => Tag) tags; // 由name的hash做key + struct Data { bytes32[] tags; - mapping(bytes32 => DataTagInfo) tag_info; // 每个TAG的赞否数据,一个账户只能点一次赞或否 + mapping(bytes32 => MetaData) tag_info; // 每个TAG的赞否数据,一个账户只能点一次赞或否 + } + + struct DataOutput { + bytes32[] tags; } mapping(bytes32 => Data) datas; - event TagCreated(bytes32 tagHash, bytes32 tagParent, string name); - event AddDataTag(bytes32 dataHash, bytes32 tag); - event RateDataTag(address rater, bytes32 dataHash, bytes32 tag, int8 like); + // 更新tag的meta描述,首次更新相当于创建这个tag + event TagUpdated(bytes32 indexed tagHash, address indexed from); + // 数据的tag改变。如果是新增tag,oldTag为bytes32(0) + // oldTag不为空,出现在父TAG被子TAG替换的情况 + event ReplaceDataTag(bytes32 indexed dataHash, bytes32 indexed oldTag, bytes32 indexed newTag); + // 给tag的meta点赞或反对 + event RateTagMeta(bytes32 indexed tagHash, address indexed from, address indexed rater, int8 like); + // 给数据的tag本身点赞或反对 + event RateDataTag(bytes32 indexed dataHash, bytes32 indexed tag, address indexed rater, int8 like); constructor() Ownable(msg.sender) {} - function createTag(string[] calldata new_tags, string[] calldata descs) onlyOwner public { + function calcTagHash(string[] calldata name) pure public returns (bytes32) { + string memory fullName = ""; + for (uint i = 0; i < name.length; i++) { + fullName = string.concat(fullName, "/", name[i]); + } + return keccak256(abi.encodePacked(fullName)); + } + + /** + * 设置一个tag的描述。设置描述也等于自己对这个描述点赞 + * @param new_tags tag的全路径,比如["a", "b", "c"]表示一个tag的全路径是a/b/c + * @param meta 这个tag本身的meta信息。如果路径中间的某个tag不存在,这个tag的meta信息默认为空,等待后续更新 + */ + function setTagMeta(string[] calldata new_tags, string calldata meta) public { bytes32 parent = bytes32(0); + string memory fullName = ""; for (uint i = 0; i < new_tags.length; i++) { - bytes32 tagHash = keccak256(abi.encodePacked(new_tags[i])); - if (bytes(tags[tagHash].name).length != 0) { - parent = tagHash; - continue; + require(bytes(new_tags[i]).length != 0, "empty name"); + + fullName = string.concat(fullName, "/", new_tags[i]); + bytes32 tagHash = keccak256(abi.encodePacked(fullName)); + if (bytes(tags[tagHash].name).length == 0) { + // create tag + tags[tagHash].name = new_tags[i]; + if (parent != bytes32(0)) { + tags[tagHash].parent = parent; + tags[parent].children.push(tagHash); + } } - tags[tagHash] = Tag(new_tags[i], descs[i], parent, new bytes32[](0)); + parent = tagHash; + + MetaData storage tagMetaData = tags[tagHash].metas[msg.sender]; + if (!tagMetaData.valid || i == new_tags.length - 1) { + // set meta data + string memory tagMeta = ""; + if (i == new_tags.length - 1) { + tagMeta = meta; + } - if (parent != bytes32(0)) { - tags[parent].children.push(tagHash); + tagMetaData.meta = tagMeta; + tagMetaData.valid = true; + emit TagUpdated(tagHash, msg.sender); + + // 自己给自己tag的描述点赞 + _rateTagMeta(tagHash, msg.sender, 1); } + } + } - emit TagCreated(tagHash, parent, new_tags[i]); - parent = tagHash; + function _rateMeta(MetaData storage meta, int8 like) internal returns (bool) { + int8 oldRating = meta.userlike[msg.sender]; + if (oldRating == like) { + return false; + } + + meta.userlike[msg.sender] = like; + + if (oldRating == 1) { + meta.like--; + } else if (oldRating == -1) { + meta.dislike--; + } + + if (like == 1) { + meta.like++; + } else if (like == -1) { + meta.dislike++; + } + + return true; + } + + function _rateTagMeta(bytes32 tagHash, address from, int8 like) internal { + MetaData storage meta = tags[tagHash].metas[from]; + + if (_rateMeta(meta, like)) { + emit RateTagMeta(tagHash, from, msg.sender, like); } } - function modifyTagDesc(string calldata tag, string calldata desc) onlyOwner public { - bytes32 tagHash = keccak256(abi.encodePacked(tag)); - require(bytes(tags[tagHash].name).length != 0, "tag not exist"); - tags[tagHash].desc = desc; + function rateTagMeta(bytes32 tagHash, address from, int8 like) public { + require(tags[tagHash].metas[from].valid, "meta not exist"); + _rateTagMeta(tagHash, from, like); } - // 添加同时也等于点赞 - function addDataTag(bytes32 dataHash, string[] calldata data_tags) public { + /** + * 给数据附加tag。可以一次性附加多个, 先实现成tag必须存在才能附加 + * @param dataHash 数据的hash + * @param data_tags 要附加的全部tagHash + */ + // TODO: 不允许同时存在父TAG和子TAG。在存在父TAG的情况下,再设置子TAG,父TAG会被替换成子TAG + function addDataTag(bytes32 dataHash, bytes32[] calldata data_tags, string[] calldata data_tag_metas) public { + require(data_tags.length == data_tag_metas.length, "invalid input"); + for (uint i = 0; i < data_tags.length; i++) { - bytes32 tagHash = keccak256(abi.encodePacked(data_tags[i])); - if (!datas[dataHash].tag_info[tagHash].valid) { + bytes32 tagHash = data_tags[i]; + require(bytes(tags[tagHash].name).length != 0, "tag not exist"); + + MetaData storage dataTagMeta = datas[dataHash].tag_info[tagHash]; + + if (!dataTagMeta.valid) { datas[dataHash].tags.push(tagHash); - datas[dataHash].tag_info[tagHash].valid = true; - emit AddDataTag(dataHash, tagHash); + dataTagMeta.valid = true; + dataTagMeta.meta = data_tag_metas[i]; + emit ReplaceDataTag(dataHash, bytes32(0), tagHash); } - _ratingDataTag(dataHash, tagHash, 1); + _rateDataTag(dataHash, tagHash, 1); } } - function ratingDataTag(bytes32 dataHash, string calldata tag, int8 like) public { - bytes32 tagHash = keccak256(abi.encodePacked(tag)); + /** + * 评价数据的tag本身 + * @param dataHash 数据的dataHash + * @param tagHash tag的hash + * @param like 1代表赞同,-1代表反对,0代表取消之前的评价 + */ + function rateDataTag(bytes32 dataHash, bytes32 tagHash, int8 like) public { require(datas[dataHash].tag_info[tagHash].valid, "tag not exist"); - _ratingDataTag(dataHash, tagHash, like); + _rateDataTag(dataHash, tagHash, like); } - function _ratingDataTag(bytes32 dataHash, bytes32 tagHash, int8 like) internal { - int8 oldRating = datas[dataHash].tag_info[tagHash].userlike[msg.sender]; - if (oldRating == like) { - return; + function _rateDataTag(bytes32 dataHash, bytes32 tagHash, int8 like) internal { + MetaData storage dataTagMeta = datas[dataHash].tag_info[tagHash]; + if (_rateMeta(dataTagMeta, like)) { + emit RateDataTag(dataHash, tagHash, msg.sender, like); } + } - datas[dataHash].tag_info[tagHash].userlike[msg.sender] = like; + function getTagName(bytes32 tagHash) public view returns (string memory) { + return tags[tagHash].name; + } - if (oldRating == 1) { - datas[dataHash].tag_info[tagHash].like--; - } else if (oldRating == -1) { - datas[dataHash].tag_info[tagHash].dislike--; + function getTagInfo(bytes32 tagHash) public view returns (TagOutput memory) { + string memory fullName = tags[tagHash].name; + bytes32 parent = tags[tagHash].parent; + while (parent != bytes32(0)) { + fullName = string.concat(tags[parent].name, "/", fullName); + parent = tags[parent].parent; } - if (like == 1) { - datas[dataHash].tag_info[tagHash].like++; - } else { - datas[dataHash].tag_info[tagHash].dislike++; + fullName = string.concat("/", fullName); + + string[] memory childrenNames = new string[](tags[tagHash].children.length); + for (uint i = 0; i < tags[tagHash].children.length; i++) { + childrenNames[i] = getTagName(tags[tagHash].children[i]); } - emit RateDataTag(msg.sender, dataHash, tagHash, like); + + return TagOutput(fullName, tags[tagHash].parent, childrenNames); + } + + function getTagMeta(bytes32 tagHash, address from) public view returns (MetaDataOutput memory) { + MetaData storage meta = tags[tagHash].metas[from]; + return MetaDataOutput(meta.meta, meta.like, meta.dislike, meta.userlike[msg.sender]); + } + + // 通过dataHash, 查询这个data有哪些tag + function getDataTags(bytes32 dataHash) public view returns (bytes32[] memory) { + return datas[dataHash].tags; + } + + // 通过dataHash和tagHash,查询tag在这个data上的meta信息 + function getDataTagMeta(bytes32 dataHash, bytes32 tagHash) public view returns (MetaDataOutput memory) { + MetaData storage meta = datas[dataHash].tag_info[tagHash]; + return MetaDataOutput(meta.meta, meta.like, meta.dislike, meta.userlike[msg.sender]); } } \ No newline at end of file diff --git "a/doc/TAG\347\263\273\347\273\237.md" "b/doc/TAG\347\263\273\347\273\237.md" index 7214b0e..bcdae09 100644 --- "a/doc/TAG\347\263\273\347\273\237.md" +++ "b/doc/TAG\347\263\273\347\273\237.md" @@ -1,101 +1,22 @@ TAG系统: -1. 任何人都可以新建TAG -2. TAG是一个分层结构,一个TAG会有至多一个父TAG,和多个子TAG,TAG的全名是它的路径拼接 -3. TAG的全名不能重复 -4. 任何人都可以给TAG附加一个描述,每个人的描述是独立的 -5. 任何账号都可以 赞同/反对 TAG的某个描述 -6. 在前/后端展示上,我们可以限定某些"认证"账号赞同的描述,才是一个tag的有效描述。存在有效描述的tag才是有效的tag -7. 必须要在合约端做一个无效字符判定,无效字符至少包括路径分隔符'/' +1. TAG是一个分层结构,一个TAG会有至多一个父TAG,和多个子TAG,TAG的全名是它的路径拼接,以"/"开头,以"/"分隔 +2. TAG的全路径不能重复 +3. 任何人都可以给TAG附加一个描述,每个人的描述是独立的 +4. 任何账号都可以 赞同/反对 TAG的某个描述 +5. 在前/后端展示上,我们可以限定某些"认证"账号赞同的描述,才是一个tag的有效描述。存在有效描述的tag才是有效的tag +6. 必须要在合约端做一个无效字符判定,无效字符至少包括路径分隔符'/' +7. 如果一个TAG至少有一个描述,我们说这个TAG是"存在"的 TAG和数据的关系: -1. 一个用户可以给一个数据附加多个TAG,此时,他可以选择给tag新增一个描述,或者使用某个已有的描述 -2. 当用户给数据附加一个已有的描述时,看作对这个描述点赞同 -3. 基于2,任何一个tag都默认有一个赞同,即tag的创建者的赞同 -4. 多个用户可以给同一个数据附加不同的TAG -5. 何时给数据附加TAG: +1. 一个用户可以给一个数据附加多个已存在的TAG,用户可以给每个TAG再附加一段描述,表示自己为什么要附加这个TAG +2. 数据的TAG是全局的,每个用户都能看到数据上已附加的所有TAG +3. 数据不能同时存在父TAG和子TAG。比如,当数据已有TAG`"/A/B"`时,不能给数据再附加TAG`"/A"` +4. 当用户给TAG附加子TAG时,会替换掉它的任意父TAG。比如,当数据已有TAG`"/A"`时,再附加`"/A/B/C"`,会替换掉已有的TAG`"/A"` +5. 当用户给数据附加一个已有的TAG时,看作对这个附加行为点赞同 +6. 基于2,data上的任何一个tag都默认有一个赞同,即附加者的赞同 +7. 何时给数据附加TAG: 1. 在中心系统SHOW之前,需要"另存为"以消耗用户空间,此时对数据附加TAG 2. 用户收藏数据时,给数据附加TAG。收藏可以看作一种轻量级的"另存为" -6. 任何账号都可以 赞同/反对 数据下的某个TAG +8. 任何账号都可以 赞同/反对 数据下的某个TAG -考虑将TAG作为一个独立的合约,而不是公共数据的一部分 -```c - -// 通用的,包含赞同/反对信息的Meta数据结构。tag和data-tag都会用到这个数据结构 -struct MetaData { - bool valid; // 这个结构通常用在mapping的value,用这个字段表示这个value是否为空 - string meta; // 元数据。在tag系统下,它是tag的描述;在data-tag系统下,它是附加这个tag的原因 - uint128 like; // 如果考虑gas cost,这个字段可以取消 - uint128 dislike; // 如果考虑gas cost,这个字段可以取消 - mapping(address => int8) userlike; // 1代表赞,0代表不点,-1代表否 -} - -struct Tag { - string name; // 这个tag的单独名字 - bytes32 parent; // parent为0表示顶层TAG - bytes32[] children; // 考虑一个TAG的子TAG不会很多 - mapping(address => MetaData) metas; // 每个用户对tag的描述 - // 由于TAG可能包括很多数据,合约里不提供反向查询,靠后端扫描事件来重建数据库 -} - -// 由于mapping不能返回给外界,需要一个单独的结构体来支持view函数 -struct TagOutput { - string[] fullName; // 直接返回整个路径 - bytes32 parent; - bytes32[] children; - // 因为使用哪种描述,是前/后端的逻辑,这里就不返回任何描述信息 - // TODO:考虑返回最后更新的描述信息,但这要多一个字段存储最后更新的地址,而且类似"编辑战争",最后更新的描述不一定是更正确的描述 -} - -mapping(bytes32 => Tag) public tags; // 由name的hash做key - - -// 先按照Data绑定tag来思考,tag展示哪个描述由UI决定 -struct Data { - bytes32[] tags; // 这里用于遍历,或者返回一个数据关联的所有tag。如果考虑gas cost,这个字段可以取消 - mapping(bytes32 => MetaData) // 每个TAG的赞否数据,一个账户只能点一次赞或否 -} - -mapping(bytes32 => Data) public datas; // 在被公共数据合约使用时,由mixhash做key,其他的合约可以有不同的data key使用方式 - // (这里会不会有问题?不同hash type的同一份数据会被视为不同的数据) - -event TagUpdated(bytes32 tagHash, address from, bytes32 tagParent, string name) -event AddDataTag(bytes32 dataHash, bytes32 tag) -event RateTagMeta(bytes32 tagHash, address from, int8 like) -event RateDataTag(bytes32 dataHash, bytes32 tag, int8 like) - -// 只列出写接口 -contract DataTag { - // 给tag设置meta,首次设置时,会被看作创建了这个tag - // 输入是已经分割好的tag路径 - function setTagMeta(string[] tags, string desc) { - // 设置/A/B/C - emit TagUpdated(A, msg.sender, 0, "") - emit TagUpdated(B, msg.sender, A, "") - emit TagUpdated(C, msg.sender, B, descC) - - // 同一个人再设置/A/D - emit TagUpdated(D, msg.sender, A, descD) - - // 不同的人设置/A/D - emit TagUpdated(A, msg.sender, 0, "") - emit TagUpdated(D, msg.sender, A, descD) - } - - // 添加多个tag. 已添加的tag不会被添加. 需要输入tagID - function addDataTag(bytes32 dataHash, bytes32[] tagids) { - for tagid of tagids { - emit addDataTag(dataHash, tagid) - } - } - - // 赞同/反对一个tag的描述 - function rateTagMeta(bytes32 dataHash, address from, int8 like) { - emit RateTagMeta(dataHash, from, like) - } - - // 赞同/反对一个数据被附加的tag - function rateDataTag(bytes32 dataHash, bytes32 tagid, int8 like) { - emit RateDataTag(dataHash, tagid, like) - } -} -``` \ No newline at end of file +考虑将TAG作为一个独立的合约,而不是公共数据的一部分 \ No newline at end of file diff --git a/test/test_data_tag.ts b/test/test_data_tag.ts new file mode 100644 index 0000000..53b41a0 --- /dev/null +++ b/test/test_data_tag.ts @@ -0,0 +1,126 @@ +import { ethers, upgrades } from "hardhat" +import { DataTag } from "../typechain-types" +import { expect } from "chai" +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; + +let DATA_HASH = "0x8000000000004640f98243c692fba84742845ca34ba0a92a9438e33319abcd7f"; + +describe("data tag", function () { + let data_tag: DataTag + let tag_hashs: any = {}; + let signers: HardhatEthersSigner[]; + before(async () => { + data_tag = await ethers.deployContract("DataTag"); + tag_hashs["A"] = await data_tag.calcTagHash(["A"]); + tag_hashs["B"] = await data_tag.calcTagHash(["A", "B"]); + tag_hashs["C"] = await data_tag.calcTagHash(["A", "B", "C"]); + tag_hashs["D"] = await data_tag.calcTagHash(["A", "D"]); + + signers = await ethers.getSigners(); + }) + + it("set tag meta", async () => { + // 测试错误的空路径 + await expect(data_tag.setTagMeta(["A", ""], "meta")).to.be.revertedWith("empty name"); + + // 创建路径/A/B/C + let tx = data_tag.setTagMeta(["A", "B", "C"], "meta_A_B_C"); + + await expect(tx).to.emit(data_tag, "TagUpdated").withArgs(tag_hashs["A"], signers[0].address); + await expect(tx).to.emit(data_tag, "TagUpdated").withArgs(tag_hashs["B"], signers[0].address); + await expect(tx).to.emit(data_tag, "TagUpdated").withArgs(tag_hashs["C"], signers[0].address); + + await expect(tx).to.emit(data_tag, "RateTagMeta").withArgs(tag_hashs["A"], signers[0].address, signers[0].address, 1); + await expect(tx).to.emit(data_tag, "RateTagMeta").withArgs(tag_hashs["B"], signers[0].address, signers[0].address, 1); + await expect(tx).to.emit(data_tag, "RateTagMeta").withArgs(tag_hashs["C"], signers[0].address, signers[0].address, 1); + + expect(await data_tag.getTagInfo(tag_hashs["A"])).to.deep.equal(["/A", ethers.ZeroHash, ["B"]]); + + // 创建路径/A/D + tx = data_tag.setTagMeta(["A", "D"], "meta_A_D"); + await expect(tx).to.emit(data_tag, "TagUpdated").withArgs(tag_hashs["D"], signers[0].address); + await expect(tx).to.emit(data_tag, "RateTagMeta").withArgs(tag_hashs["D"], signers[0].address, signers[0].address, 1); + + expect(await data_tag.getTagInfo(tag_hashs["A"])).to.deep.equal(["/A", ethers.ZeroHash, ["B", "D"]]); + + // 由于不能检查是否没有某个特定事件,这里用事件个数来检查 + expect((await(await tx).wait())?.logs.length).to.equal(2); + + // 用另一个账户创建路径/A/B/C + tx = data_tag.connect(signers[1]).setTagMeta(["A", "B", "C"], "meta_A_B_C_1"); + + await expect(tx).to.emit(data_tag, "TagUpdated").withArgs(tag_hashs["A"], signers[1].address); + await expect(tx).to.emit(data_tag, "TagUpdated").withArgs(tag_hashs["B"], signers[1].address); + await expect(tx).to.emit(data_tag, "TagUpdated").withArgs(tag_hashs["C"], signers[1].address); + + await expect(tx).to.emit(data_tag, "RateTagMeta").withArgs(tag_hashs["A"], signers[1].address, signers[1].address, 1); + await expect(tx).to.emit(data_tag, "RateTagMeta").withArgs(tag_hashs["B"], signers[1].address, signers[1].address, 1); + await expect(tx).to.emit(data_tag, "RateTagMeta").withArgs(tag_hashs["C"], signers[1].address, signers[1].address, 1); + }) + + it("check tag meta", async() => { + // 检查tag信息 + expect(await data_tag.getTagInfo(tag_hashs["A"])).to.deep.equal(["/A", ethers.ZeroHash, ["B", "D"]]); + expect(await data_tag.getTagInfo(tag_hashs["B"])).to.deep.equal(["/A/B", tag_hashs["A"], ["C"]]); + expect(await data_tag.getTagInfo(tag_hashs["C"])).to.deep.equal(["/A/B/C", tag_hashs["B"], []]); + expect(await data_tag.getTagInfo(tag_hashs["D"])).to.deep.equal(["/A/D", tag_hashs["A"], []]); + + // 检查meta + expect(await data_tag.getTagMeta(tag_hashs["A"], signers[0].address)).to.deep.equal(["", 1, 0, 1]); + expect(await data_tag.getTagMeta(tag_hashs["C"], signers[0].address)).to.deep.equal(["meta_A_B_C", 1, 0, 1]); + expect(await data_tag.getTagMeta(tag_hashs["D"], signers[0].address)).to.deep.equal(["meta_A_D", 1, 0, 1]); + + // 用另一个账户检查meta + expect(await data_tag.connect(signers[1]).getTagMeta(tag_hashs["D"], signers[0].address)).to.deep.equal(["meta_A_D", 1, 0, 0]); + expect(await data_tag.connect(signers[1]).getTagMeta(tag_hashs["C"], signers[1].address)).to.deep.equal(["meta_A_B_C_1", 1, 0, 1]); + }) + + it("change tag meta", async() => { + let tx = data_tag.setTagMeta(["A", "B", "C"], "meta_A_B_C1"); + await expect(tx).to.emit(data_tag, "TagUpdated").withArgs(tag_hashs["C"], signers[0].address); + expect((await(await tx).wait())?.logs.length).to.equal(1); + + expect(await data_tag.getTagMeta(tag_hashs["C"], signers[0].address)).to.deep.equal(["meta_A_B_C1", 1, 0, 1]); + }) + + it("rate tag meta", async() => { + // signer1反对signer0的tagC + let tx = data_tag.connect(signers[1]).rateTagMeta(tag_hashs["C"], signers[0], -1); + await expect(tx).to.emit(data_tag, "RateTagMeta").withArgs(tag_hashs["C"], signers[0].address, signers[1].address, -1); + + expect(await data_tag.connect(signers[1]).getTagMeta(tag_hashs["C"], signers[0].address)).to.deep.equal(["meta_A_B_C1", 1, 1, -1]); + + // signer2支持signer0的tagC + tx = data_tag.connect(signers[2]).rateTagMeta(tag_hashs["C"], signers[0], 1); + await expect(tx).to.emit(data_tag, "RateTagMeta").withArgs(tag_hashs["C"], signers[0].address, signers[2].address, 1); + expect(await data_tag.connect(signers[2]).getTagMeta(tag_hashs["C"], signers[0].address)).to.deep.equal(["meta_A_B_C1", 2, 1, 1]); + + expect(await data_tag.connect(signers[3]).getTagMeta(tag_hashs["C"], signers[0].address)).to.deep.equal(["meta_A_B_C1", 2, 1, 0]); + + // signer1重新赞同对signer0的tagC + tx = data_tag.connect(signers[1]).rateTagMeta(tag_hashs["C"], signers[0], 1); + await expect(tx).to.emit(data_tag, "RateTagMeta").withArgs(tag_hashs["C"], signers[0].address, signers[1].address, 1); + expect(await data_tag.connect(signers[1]).getTagMeta(tag_hashs["C"], signers[0].address)).to.deep.equal(["meta_A_B_C1", 3, 0, 1]); + + // signer1取消对signer0的tagC的评价 + tx = data_tag.connect(signers[1]).rateTagMeta(tag_hashs["C"], signers[0], 0); + await expect(tx).to.emit(data_tag, "RateTagMeta").withArgs(tag_hashs["C"], signers[0].address, signers[1].address, 0); + expect(await data_tag.connect(signers[1]).getTagMeta(tag_hashs["C"], signers[0].address)).to.deep.equal(["meta_A_B_C1", 2, 0, 0]); + }) + + it("add data tag", async () => { + let tx = data_tag.addDataTag(DATA_HASH, [tag_hashs["D"]], ["signer0 add tag D"]); + await expect(tx).to.emit(data_tag, "ReplaceDataTag").withArgs(DATA_HASH, ethers.ZeroHash, tag_hashs["D"]); + await expect(tx).to.emit(data_tag, "RateDataTag").withArgs(DATA_HASH, tag_hashs["D"], signers[0].address, 1); + + tx = data_tag.connect(signers[1]).addDataTag(DATA_HASH, [tag_hashs["C"]], ["signer1 add tag C"]); + await expect(tx).to.emit(data_tag, "ReplaceDataTag").withArgs(DATA_HASH, ethers.ZeroHash, tag_hashs["C"]); + await expect(tx).to.emit(data_tag, "RateDataTag").withArgs(DATA_HASH, tag_hashs["C"], signers[1].address, 1); + }) + + it("check data tag", async () => { + expect(await data_tag.getDataTags(DATA_HASH)).to.deep.equal([tag_hashs["D"], tag_hashs["C"]]); + expect(await data_tag.connect(signers[1]).getDataTagMeta(DATA_HASH, tag_hashs["C"])).to.deep.equal(["signer1 add tag C", 1, 0, 1]); + expect(await data_tag.getDataTagMeta(DATA_HASH, tag_hashs["D"])).to.deep.equal(["signer0 add tag D", 1, 0, 1]); + }) +}) \ No newline at end of file