赞
踩
DDCMS使用基于Solidity的智能合约进行开发。Solidity的智能合约语义上是图灵完备的,该语言支持各种基础类型(Booleans,Integers,Address,Bytes,Enum等)、复杂类型(Struct,Mapping,Array等)、复杂的表达式、控制结构和远程调用,以及接口、继承等面向对象的高级语言特性。Solidity是以太坊和FISCO-BCOS所支持的智能合约语言。
智能合约功能强大,因而真实世界中的复杂商业逻辑和应用可以在区块链上轻松实现。然而,智能合约一旦部署,它会在所有区块链节点上独立重复运行,因此原则上认为,只有各业务方需要进行共识的、逻辑可复用的业务才有必要通过智能合约在链上实现。此外,智能合约发布之后,若出现问题需要修复或者业务逻辑变更,是无法通过简单地在原有合约基础上修改再重新发布来解决的。因此,在设计之初还需要结合业务场景思考合适的合约更新机制。总体上,DDCMS合约的设计原则是:功能完备、逻辑清晰、模块解耦、结构清晰、安全完备、支持升级。
DDCMS-Contract用于追踪DDCMS的使用过程,数据目录生命周期中的每一个关键环节均会在链上留痕、存证,保证系统的可追溯、可监管。 它由三个模块构成:AccountContract, ProductContract, DataSchemaContract,由系统运营方部署。
AccountContract
: 负责账户管理,包括机构的注册、审核等功能。系统中的数据提供方、见证机构,均需要在AccountContract中注册并审核,才能使用DDCMS。系统运营方则会在部署合约时自动注册。ProductContract
: 负责业务管理,包括业务的创建、审核等功能。其中,业务的创建由数据提供方进行,其审核由见证方进行,当票数超过半数,即通过审核。DataSchemaContract
:负责数据目录管理,包括数据目录的创建、审核等。其中,数据目录的创建由数据提供方进行,其审核由见证方进行,当票数超过半数,即通过审核。下文将会基于业务目标对这三个模块展开描述。
AccountContract合约的主要目的是提供一个基本的账户管理机制,使用户可以在区块链上注册账户,并由管理员审核和管理账户。通过使用合约中定义的账户类型和状态,可以根据需求限制不同账户的权限和功能。这种账户管理机制可以用于各种场景,例如身份验证、权限控制和数据访问控制等。
不同角色(Person、Company、Witness、Admin)拥有不同的操作权限:
操作 | Person | Company | Witness | Admin |
---|---|---|---|---|
审核用户操作 | N | N | Y | Y |
增删业务、数据目录管理 | Y | Y | Y | Y |
审核业务、数据目录管理 | N | N | Y | Y |
查询业务操作 | Y | Y | Y | Y |
存储结构主要包映射(mapping)和结构体(struct):
addressToDid
:这是一个地址(address)到 DID 的映射。它用于快速查找已注册账户的 DID。通过将用户的地址与其对应的 DID 关联起来,可以方便地根据地址查询账户信息。didToAccount
:这是一个 DID 到账户数据的映射。它存储了所有已注册账户的数据。使用 DID 作为键值,可以根据 DID 快速定位到相应的账户数据,而无需遍历整个映射。AccountData
结构体:该结构体定义了账户数据的格式。它包含以下字段:
address
:账户的地址。did
:账户的 DID。accountType
:账户的类型。accountStatus
:账户的状态。hash
:账户的哈希值。在DDCMS的分布式数据管理中,DID(Decentralized Identifier)作为身份标识符具有以下好处和优势:
使用DID作为身份标识符可以为DDCMS带来以下好处:
register接口(用户注册)
accountType
表示账户类型,hash
是需要提供的哈希值。函数会检查参数的有效性,并且确保地址没有重复注册。注册成功后,会生成唯一的标识符(DID),并将账户信息存储在映射中,并触发 AccountRegisteredEvent
事件。approve接口(审核用户)
did
是待审核账户的标识符, agree
表示是否同意审核。检查账户是否存在和审核状态是否正确。审核通过后,更新账户状态为已审核,并根据账户类型增加统计数量,并触发 AccountApprovedEvent
事件。审核不通过则更新账户状态为已拒绝,并触发 AccountDeniedEvent
事件。getAccountByDid接口(根据标识符查询账户信息)
AccountData
结构体。getAccountByAddress接口(根据地址查询账户信息)
AccountData
结构体。pragma solidity >=0.6.0 <=0.8.17; pragma experimental ABIEncoderV2; // 账户合约 contract AccountContract { // 账户注册事件 event AccountRegisteredEvent( bytes32 did, // 账户的唯一ID address addr, // 账户地址 AccountType accountType, // 账户类型 bytes32 hash // ); // 账户批准事件 event AccountApprovedEvent(bytes32 did); // 账户拒绝事件 event AccountDeniedEvent(bytes32 did); //枚举类型和结构体定义 enum AccountType { Person, //个人 (0 预留) Company, //公司 (1) Witness, //数据见证方 (2) Admin //管理员 (3) } // 审核的状态枚举变量 enum AccountStatus { Approving, //审核中 (0) Approved, //已审核 (1) Denied, //已拒绝 (2) Disabled //禁用 (3 预留) } // 账户的数据 struct AccountData { address addr; //账户地址 bytes32 did; //唯一标识符 AccountType accountType; //账户类型 AccountStatus accountStatus;//账户状态 bytes32 hash; //哈希值 } // 存储 mapping(address => bytes32) public addressToDid; // 地址到 DID 的映射,用于快速查找注册的账户 mapping(bytes32 => AccountData) public didToAccount; // DID 到账户数据的映射,存储了所有已注册的账户 mapping(AccountType => uint256) public accountTypeNumbers; // 不同账户类型数量的统计 // 修饰符 判断只有管理员权限 modifier onlyAdmin() { AccountData memory accountData = _getAccountByAddress(msg.sender); require( accountData.accountStatus == AccountStatus.Approved, "Invalid account status" ); require( accountData.accountType == AccountType.Admin, "Account are not admin" ); _; } // 构造函数 constructor() public{ _register( msg.sender, AccountType.Admin, AccountStatus.Approved, bytes32(0) ); } // 用户函数(注册账户) function register( AccountType accountType, bytes32 hash ) external returns (bytes32 did) { // 检查参数 require(hash != bytes32(0), "Invalid hash"); address addr = msg.sender; require(addressToDid[addr] == bytes32(0), "address already registered"); // 注册账户 did = _register(addr, accountType, AccountStatus.Approving, hash); } // 管理员函数(审核账户) function approve(bytes32 did, bool agree) external onlyAdmin { // 获取账户信息 AccountData storage account = didToAccount[did]; require(account.addr != address(0), "Account not exist"); require( account.accountStatus == AccountStatus.Approving, "Invalid account status" ); // 根据审核结果,更新账户状态和账户类型数量 if (agree) { account.accountStatus = AccountStatus.Approved; accountTypeNumbers[account.accountType]++; emit AccountApprovedEvent(did); } else { account.accountStatus = AccountStatus.Denied; emit AccountDeniedEvent(did); } } // 根据唯一ID查询账户信息 function getAccountByDid( bytes32 did ) external view returns (AccountData memory) { return didToAccount[did]; } // 根据地址查询账户信息 function getAccountByAddress( address addr ) external view returns (AccountData memory) { return _getAccountByAddress(addr); } // 生成唯一ID标识符 function _generateDid( AccountType userType, address initAddr, bytes32 hash ) internal pure returns (bytes32 id) { uint256 header = uint256(userType) << 240; uint256 body = (uint256(keccak256(abi.encodePacked(initAddr, hash))) << 16) >> 16; id = bytes32(header | body); } // 注册账户 function _register( address accountAddress, AccountType accountType, AccountStatus accountStatus, bytes32 hash ) internal returns (bytes32 did) { did = _generateDid(accountType, accountAddress, hash); addressToDid[accountAddress] = did; didToAccount[did] = AccountData( accountAddress, did, accountType, accountStatus, hash ); emit AccountRegisteredEvent(did, accountAddress, accountType, hash); } // 根据地址查询账户信息 function _getAccountByAddress( address addr ) internal view returns (AccountData memory) { bytes32 did = addressToDid[addr]; require(did != 0, "address not registered"); return didToAccount[did]; } }
ProductContract合约的业务逻辑是管理业务的创建和审批过程,并记录相关的投票信息。
该合约导入了AccountContract合约,具有用户账户的权限管理,主要包含四个核心函数,分别是createProduct
、approveProduct
、getProduct
和getVoteInfo
。createProduct
函数允许公司账户创建新的业务,要求提供有效唯一的业务散列值,创建业务完成触发事件该业务进行上链。approveProduct
函数由见证人账户调用,用于审批所有的业务,根据投票结果超过半数,将该业务状态变为已批准或已拒绝,触发业务投票事件。getProduct
函数用于查询业务的详细信息,包括散列值、所有者ID和状态。最后,getVoteInfo
函数用于查询业务的投票信息,包括同意的数量、反对数量、通过阈值和见证人数量。
在我们的分布式数据管理(DDCMS)智能合约中,可以实现去中心化、不可篡改的文档存储,提高透明度和可追溯性,自动化任务通过智能合约,实现更安全的身份验证和访问控制,减少中介成本,跨边界合作,确保数据隐私。这种整合为文档管理系统带来了更高的效率、可信度和可扩展性,促进了数据安全和合规性。
有两个角色权限,分别是Company和Witness。Company可以新增业务,然后Witness作为见证人可以对该业务进行审批。
ProductInfo
结构体:用于存储业务产品的基本信息,包括哈希值、所有者ID和业务状态。这个结构体的设计允许合约跟踪每个产品的状态和所有者。
VoteInfo
结构体:用于存储有关业务产品投票的信息,包括同意票数、否决票数、投票阈值和见证人数量。这个结构体的设计有助于确保业务产品是否被批准或否决,并根据见证人的数量来动态计算阈值。
mapping
数据结构:用于维护多个映射,包括:
products
:将业务产品ID与 ProductInfo
结构体相关联,以便按ID检索产品信息。hashToId
:将业务产品哈希与产品ID相关联,以确保每个产品具有唯一的哈希值。ownerProductCount
:跟踪每个所有者创建的产品数量,用于生成唯一的产品ID。productCreationVotes
:将产品ID与 VoteInfo
结构体相关联,用于管理产品的投票信息。productVoters
:将产品ID与见证人的投票状态相关联,以确保每个见证人只能投一票。优势和好处:
createProduct接口(创建一个新的业务)
CreateProductEvent
事件。approveProduct接口(见证人对业务进行审批)
VoteProductEvent
事件。getProduct接口(查询业务的详细信息)
getVoteInfo接口(查询业务投票信息)
pragma solidity >=0.6.0 <=0.8.17; pragma experimental ABIEncoderV2; import "./IdGeneratorLib.sol"; import "./AccountContract.sol"; // 业务管理合约 contract ProductContract { // 创建业务事件 event CreateProductEvent(bytes32 productId, bytes32 hash); // 投票当前的业务产品事件 event VoteProductEvent( bytes32 productId, bytes32 voterId, bool agree, uint256 agreeCount, uint256 denyCount, ProductStatus afterStatus ); // 枚举类型定义业务的状态 enum ProductStatus { Approving, // 审批中 Approved, // 已通过 Denied, // 已否决 Disabled, // 已禁用 Banned // 已封禁 } // 存储业务的结构体 struct ProductInfo { bytes32 hash; // 哈希值 bytes32 ownerId; // 所有者ID ProductStatus status; // 业务状态 } // 存储投票信息的结构体 struct VoteInfo { uint256 agreeCount; // 赞同票数 uint256 denyCount; // 否决票数 uint256 threshold; // 投票阈值 uint256 witnessCount; // 见证人数量 } // 账户合约 AccountContract private accountContract; // 存储产品信息的映射 mapping(bytes32 => ProductInfo) products; // 维护产品归属关系的映射 mapping(bytes32 => bytes32) private hashToId; // 存储产品创建投票信息的映射 mapping(bytes32 => uint256) private ownerProductCount; // 限定只有公司账户可以调用的修饰符 mapping(bytes32 => VoteInfo) private productCreationVotes; // 限定只有见证人账户可以调用的修饰符 mapping(bytes32 => mapping(bytes32 => bool)) public productVoters; // 限定只有公司账户可以调用的修饰符 modifier onlyCompany() { _requireAccount(msg.sender, AccountContract.AccountType.Company); _; } // 限定只有见证人账户可以调用的修饰符 modifier onlyWitness() { _requireAccount(msg.sender, AccountContract.AccountType.Witness); _; } // 构造函数,初始化与账户合约的地址 constructor(address _accountContract) public { accountContract = AccountContract(_accountContract); } //创建业务的函数 function createProduct( bytes32 hash ) external onlyCompany returns (bytes32 productId, uint256 witnessCount) { //requires require(hash != bytes32(0), "Invalid hash"); require(hashToId[hash] == 0, "duplicate product hash"); //Generate product id AccountContract.AccountData memory owner = accountContract .getAccountByAddress(msg.sender); uint256 ownerNonce = ownerProductCount[owner.did]; productId = IdGeneratorLib.generateId(owner.did, ownerNonce); products[productId] = ProductInfo( hash, owner.did, ProductStatus.Approving ); hashToId[hash] = productId; ownerNonce++; ownerProductCount[owner.did] = ownerNonce; //Initialize voting params witnessCount = accountContract.accountTypeNumbers( AccountContract.AccountType.Witness ); productCreationVotes[productId] = VoteInfo( 0, 0, witnessCount / 2 + 1, witnessCount ); emit CreateProductEvent(productId, hash); } // 可见机构审批业务 function approveProduct( bytes32 productId, bool agree ) external onlyWitness returns ( bytes32 witnessDid, uint256 agreeCount, uint256 denyCount, ProductStatus afterStatus ) { //Product id validation ProductInfo storage product = products[productId]; require(product.ownerId != bytes32(0), "product not existed"); require( product.status == ProductStatus.Approving, "Invalid product status" ); VoteInfo storage voteInfo = productCreationVotes[productId]; //Vote AccountContract.AccountData memory witness = accountContract .getAccountByAddress(msg.sender); witnessDid = witness.did; require(!productVoters[productId][witnessDid], "Duplicate vote"); uint256 threshold = voteInfo.threshold; if (agree) { agreeCount = voteInfo.agreeCount + 1; voteInfo.agreeCount = agreeCount; denyCount = voteInfo.denyCount; if (agreeCount >= threshold) { afterStatus = ProductStatus.Approved; } } else { denyCount = voteInfo.denyCount + 1; voteInfo.denyCount = denyCount; agreeCount = voteInfo.agreeCount; if (denyCount > (voteInfo.witnessCount - 1) / 2) { afterStatus = ProductStatus.Denied; } } product.status = afterStatus; productVoters[productId][witnessDid] = true; //event emit VoteProductEvent( productId, witnessDid, agree, agreeCount, denyCount, afterStatus ); } // 查询业务详细信息 function getProduct( bytes32 productId ) external view returns (ProductInfo memory productInfo) { productInfo = products[productId]; require(productInfo.ownerId != 0, "Product not exist"); } // 查询投票人信息 function getVoteInfo( bytes32 productId ) external view returns (VoteInfo memory) { return productCreationVotes[productId]; } // 判断当前的账户是否状态正常 function _requireAccount( address addr, AccountContract.AccountType accountType ) internal view { AccountContract.AccountData memory accountInfo = accountContract .getAccountByAddress(addr); require( accountInfo.accountStatus == AccountContract.AccountStatus.Approved, "Invalid account status" ); require( accountInfo.accountType == accountType, "Account is not witness" ); } }
DataSchemaContract合约是一个数据目录管理合约,主要业务是创建和管理业务产品的数据目录。数据目录管理是指用于描述和定义数据结构的模板或规范,它定义了产品中的各种数据字段、类型和关联规则等信息。该合约是导入了AccountContract
合约和ProductContract
合约,是基于用户权限管理和业务产品的管理上开发的。
用户创建和审批数据目录管理。用户可以提供数据模式的哈希值和所属业务产品的ID,创建后处于审核中状态。见证人可以投票同意或反对,根据投票门槛来确定审批结果。用户可以查询数据模式信息和投票详情。这个合约确保了数据目录管理的安全。
用户可以查询数据目录管理共享的详细信息和投票情况。这提供了透明度和可追溯性,用户可以了解数据模式的来源和状态,以及投票结果的信息。这符合DDCMS的开放和透明的原则,用户可以参与到数据目录的管理和决策过程中。
这里也是有两个角色权限,分别是Company和Witness。Company可以新增数据目录管理,然后Witness作为见证人可以对该数据目录进行审批。
DataSchemaInfo
结构体:用于存储数据目录管理的信息,包括数据模式的哈希值(hash)、拥有者的ID(ownerId)、业务的ID(productId)以及数据目录的状态(status)。这个结构体用于追踪每个数据目录管理的基本信息。
VoteInfo
结构体:用于存储数据目录管理创建时的投票信息,包括同意数(agreeCount)、反对数(denyCount)、投票门槛(threshold)以及见证者数量(witnessCount)。这个结构体用于追踪数据目录管理的投票情况。
mapping
数据结构:合约中使用了多个 mapping 数据结构来存储关键信息:
dataSchemas
:通过数据目录管理ID查找数据模式信息。hashToId
:通过数据目录管理哈希值查找数据目录管理ID。productDataSchemaCount
:记录每个业务的数据目录管理数量,用于生成唯一的数据目录管理ID。dataSchemaCreationVotes
:存储数据目录管理创建的投票信息,用于记录见证人的投票情况。dataSchemaVoters
:存储数据目录管理的投票者,以防止重复投票。优势和好处:
它是去中心化的,意味着没有一个中心服务器掌控所有数据,而是分散存储在区块链网络中。其次,它具有可扩展性,可以轻松地添加更多的节点和参与者,使系统更加强大。此外,存储在区块链上的数据安全可靠,不能被篡改,确保了数据的安全性和完整性。所有参与者都可以查看数据模式的详细信息和投票情况,让系统变得更加透明和可追溯。这个存储结构让DDCMS成为一个开放、安全、可信的内容管理系统,能够满足各种需求,保证数据的质量和一致性。
createDataSchema接口(创建数据目录)
hash
)和业务产品的ID(productId
),返回数据模式的ID(dataSchemaId
)和见证人数量(witnessCount
)。在创建数据模式前,会进行参数校验,验证产品和拥有者的信息,并生成数据模式ID。approveDataSchema接口(审批数据目录)
dataSchemaId
)和是否同意审批(agree
),返回见证人的DID(witnessDid
)、同意数(agreeCount
)、反对数(denyCount
)和审批后的数据模式状态(afterStatus
)。只有见证人可以调用该函数。在审批数据模式前,会校验数据模式的状态,并进行投票,如果同意数达到门槛则通过,如果反对数超过一半则拒绝。getDataSchema接口(获取数据模式信息)
getVoteInfo接口(获取投票信息)
pragma solidity >=0.6.0 <=0.8.17; pragma experimental ABIEncoderV2; import "./IdGeneratorLib.sol"; import "./AccountContract.sol"; import "./ProductContract.sol"; // 数据目录管理业务合约 contract DataSchemaContract { // 创建数据目录管理事件 event CreateDataSchemaEvent(bytes32 dataSchemaId, bytes32 hash); // 见证人审批数据目录管理事件 event VoteDataSchemaEvent( bytes32 dataSchemaId, bytes32 voterId, bool agree, uint256 agreeCount, uint256 denyCount, DataSchemaStatus afterStatus ); // 枚举类型定义数据目录管理的状态 enum DataSchemaStatus { Approving, // 审核中 Approved, // 已审核通过 Denied, // 已拒绝 Disabled, // 已禁用 Banned // 已封禁 } // 存储数据目录管理的信息 struct DataSchemaInfo { bytes32 hash; // 数据模式的哈希值 bytes32 ownerId; // 拥有者的ID bytes32 productId; // 业务的ID DataSchemaStatus status; // 数据目录的状态 } // 存储投票信息 struct VoteInfo { uint256 agreeCount; // 同意数 uint256 denyCount; // 反对数 uint256 threshold; // 投票门槛,超过该门槛则通过 uint256 witnessCount; // 见证者数量 } // 账户合约实例 AccountContract private accountContract; // 业务合约实例 ProductContract private productContract; // 存储数据目录管理的映射,通数据目录管理ID查找数据模式信息 mapping(bytes32 => DataSchemaInfo) dataSchemas; // 存储数据目录管理哈希值到数据目录管理ID的映射 mapping(bytes32 => bytes32) private hashToId; // 存储产品的数据目录管理式数量 mapping(bytes32 => uint256) private productDataSchemaCount; // 存储数据目录管理创建的投票信息 mapping(bytes32 => VoteInfo) private dataSchemaCreationVotes; // 存储数据目录管理的投票者 mapping(bytes32 => mapping(bytes32 => bool)) private dataSchemaVoters; //初始化构造函数 传入账户合约地址和业务合约地址 constructor(address _accountContract, address _productContract) public { accountContract = AccountContract(_accountContract); productContract = ProductContract(_productContract); } //只有见证人有权限 modifier onlyWitness() { AccountContract.AccountData memory accountInfo = accountContract .getAccountByAddress(msg.sender); require( accountInfo.accountStatus == AccountContract.AccountStatus.Approved, "Invalid account status" ); require( accountInfo.accountType == AccountContract.AccountType.Witness, "Account is not witness" ); _; } // 创建数据目录管理理函数 function createDataSchema( bytes32 hash, bytes32 productId ) external returns (bytes32 dataSchemaId, uint256 witnessCount) { //requires require(hash != bytes32(0), "Invalid hash"); require(productId != bytes32(0), "Invalid productId"); require(hashToId[hash] == 0, "duplicate data schema hash"); // 获取当前的账户信息 AccountContract.AccountData memory ownerAccount = accountContract .getAccountByAddress(msg.sender); // 获取当前的业务信息 ProductContract.ProductInfo memory productInfo = productContract .getProduct(productId); //Validate product and owner require( productInfo.status == ProductContract.ProductStatus.Approved, "product not approved" ); require( productInfo.ownerId == ownerAccount.did, "must be product owner" ); require( ownerAccount.accountStatus == AccountContract.AccountStatus.Approved, "owner not approved" ); // 生成数据目录管理ID uint256 productNonce = productDataSchemaCount[productId]; dataSchemaId = IdGeneratorLib.generateId(productId, productNonce); dataSchemas[dataSchemaId] = DataSchemaInfo( hash, ownerAccount.did, productId, DataSchemaStatus.Approving ); hashToId[hash] = dataSchemaId; productNonce++; productDataSchemaCount[productId] = productNonce; witnessCount = accountContract.accountTypeNumbers( AccountContract.AccountType.Witness ); dataSchemaCreationVotes[dataSchemaId] = VoteInfo( 0, 0, witnessCount / 2 + 1, witnessCount ); emit CreateDataSchemaEvent(dataSchemaId, hash); } // 审核数据目录管理函数 function approveDataSchema( bytes32 dataSchemaId, bool agree ) external onlyWitness returns ( bytes32 witnessDid, uint256 agreeCount, uint256 denyCount, DataSchemaStatus afterStatus ) { //Arg validations require(dataSchemaId != 0, "Invalid data schema id"); // 获取当前的数据目录管理的信息 DataSchemaInfo storage dataSchema = dataSchemas[dataSchemaId]; require( dataSchema.status == DataSchemaStatus.Approving, "Invalid data schema status" ); // 获取当前见证人账户信息 AccountContract.AccountData memory witness = accountContract .getAccountByAddress(msg.sender); witnessDid = witness.did; VoteInfo storage voteInfo = dataSchemaCreationVotes[dataSchemaId]; require(!dataSchemaVoters[dataSchemaId][witnessDid], "Duplicate vote"); uint256 threshold = voteInfo.threshold; uint256 witnessCount = voteInfo.witnessCount; // 进行审批管理操作 if (agree) { agreeCount = voteInfo.agreeCount + 1; voteInfo.agreeCount = agreeCount; denyCount = voteInfo.denyCount; if (agreeCount >= threshold) { afterStatus = DataSchemaStatus.Approved; } } else { denyCount = voteInfo.denyCount + 1; voteInfo.denyCount = denyCount; agreeCount = voteInfo.agreeCount; if (denyCount > (voteInfo.witnessCount - 1) / 2) { afterStatus = DataSchemaStatus.Denied; } } dataSchema.status = afterStatus; dataSchemaVoters[dataSchemaId][witnessDid] = true; emit VoteDataSchemaEvent( dataSchemaId, witnessDid, agree, agreeCount, denyCount, afterStatus ); } // 查询数据管理信息 function getDataSchema( bytes32 dataSchemaId ) external view returns (DataSchemaInfo memory dataSchema) { dataSchema = dataSchemas[dataSchemaId]; require(dataSchema.ownerId != 0, "data schema not exist"); } // 查询投票人信息 function getVoteInfo( bytes32 dataSchemaId ) external view returns (VoteInfo memory) { return dataSchemaCreationVotes[dataSchemaId]; } // 判断当前账户是否有权限 function _requireAccount( address addr, AccountContract.AccountType accountType ) internal view {} }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。