Solidity合约开发注意项

目录

    • 一、原则
    • 二、查询里的异常处理
    • 三、小数
    • 四、访问子合约的变量
    • 五、实现结构体继承的功能
    • 六、address转string
    • 七、字符串判等比较
    • 八、简单的地址合法性校验
    • 九、字符串拼接
    • 十、实现Set集合
    • 十一、参数是结构体的event的签名

最近在开发一组自主链权限治理的Solidity合约,业务上涉及治理委员会、权重、提案、撤销、投票和决议等,技术上也有一些新的注意点。

一、原则

solidity合约要尽可能简单简洁,如果可以依赖SDK层或者应用层,考虑尽可能丢弃掉较多的计算逻辑,以及过于细致的复杂校验。
尽可能在remix中调测,迅速排除语法的问题。

二、查询里的异常处理

交易里的异常处理建议使用require来保证可以回滚已修改状态,对于查询里的异常处理,没有找到标准的方式,所以只要合理即可,可以返回错误码,也可以require抛出异常。

三、小数

solidity里的小数支持不太友好,所以一般都是默认约定乘以10的n倍,使用整数来间接代表小数。
在治理合约的业务里,如果SDK层要计算某个提案在每次投票后的实时的同意率(便于实时看到),不论保留几位小数,都要注意不要用四舍五入,而应该使用小数位后几位去尾的舍入方式。
举个例子,假设当前设置了提案投票的同意率阈值是70%(也就是0.7),某个提案当前的同意加权票数是69999,参与加权票数是100000,二者相除得到实时的同意率为0.69999。

R = 所有同意委员的加权票数 所有投票委员的加权票数 R = \frac {所有同意委员的加权票数}{所有投票委员的加权票数} R=所有投票委员的加权票数所有同意委员的加权票数
如果四舍五入,结果都是0.7,而实际上并没有达到真正的70%阈值,存在误导性;而如果去尾,结果都会小于0.7,不会存在误导性。

四、访问子合约的变量

主合约ParentContract使用子合约ChildContract的变量childVariable,需要带括号。

// 子合约
pragma solidity ^0.8.0;contract ChildContract {uint256 public childVariable;constructor(uint256 initialValue) {childVariable = initialValue;}
}// 父合约
pragma solidity ^0.8.0;contract ParentContract {ChildContract public childContract;constructor(uint256 initialValue) {// 创建一个子合约实例childContract = new ChildContract(initialValue);}// 访问子合约的变量,带括号访问function getChildVariable() public view returns (uint256) {return childContract.childVariable();}
}

五、实现结构体继承的功能

solidity并非高级语言,没有结构体之间继承的概念,如果非要用,可以通过solidity中字节码指令的方式基本实现。
比如业务提案有多种类型,每种类型的业务参数也不尽相同,考虑将公共的参数抽象到父结构体的概念中,将所有类型的提案作为子结构体的概念,并通过组合的形式内含一个父结构体。如下:

// 提案父结构体
struct BaseProposal {uint256 proposalId;             // 提案IDaddress proposer;               // 提案人ProposalType proposalType;      // 提案类型【根据不同提案类型,在提案通过后执行相应动作】ProposalStatus proposalStatus;  // 提案状态uint blockNum;                  // 块高
}
// 变更委员权重业务提案
struct GovernorProposal {BaseProposal baseProposal;address account;                    // 委员账号地址uint weight;                        // 委员权重
}
// 系统配置业务提案
struct SystemConfigProposal {BaseProposal baseProposal;string key;                         // Kstring value;                       // V
}

使用这种方式的初衷是什么?
对治理业务来说,委员发起的每一个提案,都需要保存在storage空间中,如果不抽象一个父结构体,可能需要为每一种类型的提案都创建一个mapping保存提案id和提案对象的映射,如果提案类型太多,也就需要定义更多mapping,看起来不够优雅,也会导致有的查询需求必须组合所有的mapping才能满足,稍微麻烦一些。而如果抽象一个父结构体,只需要一个mapping即可,通过提案id查询也更方便。

// 所有的业务提案
mapping(uint256 => ProposalCommon.BaseProposal) internal _proposalMap;

下面举例发起一个设置系统配置的提案,重点关注保存新提案_proposalMap.storageSystemConfigProposal(proposalId)

// 发起一个设置系统配置的提案
function proposeSetSystemConfig(string memory key, string memory value) public onlyGovernor returns(uint256) {uint256 proposalId;ProposalCommon.ProposalStatus proposalStatus;// _proposalMgr子合约创建新提案(proposalId, proposalStatus) = _proposalMgr.createProposal(msg.sender, blockLimit, ProposalCommon.LockType.SystemConfig, address(_systemConfigBuiltin), ProposalCommon.ProposalType.SetSystemConfig);// 保存新提案ProposalCommon.SystemConfigProposal storage proposal = _proposalMap.storageSystemConfigProposal(proposalId);proposal.baseProposal = ProposalCommon.BaseProposal({proposalId: proposalId,proposer: msg.sender,proposalType: proposalType,proposalStatus: proposalStatus,blockNum: block.number});proposal.key = key;proposal.value = value;// 如果提案状态是approved则执行业务操作execPropose(proposalId);return proposalId;
}

storageSystemConfigProposal具体实现如下:

pragma solidity >= 0.7.0 < 0.9.0;import "./ProposalCommon.sol";// 从mapping中存取某种业务提案的派生结构体
library LProposalCasting {function storageSystemConfigProposal(mapping(uint256 => ProposalCommon.BaseProposal) storage proposalMap, uint256 proposalId)internalviewreturns (ProposalCommon.SystemConfigProposal storage proposal) {assembly {let ptr := mload(0x40)mstore(add(ptr, 0x00), proposalId)mstore(add(ptr, 0x20), proposalMap.slot)proposal.slot := keccak256(ptr, 0x40)}}
}

具体存取每一个子提案到父提案_proposalMap的实现原理参考https://medium.com/@sina.tadayyon/struct-inheritance-in-solidity-143c712f9092,打不开尝试翻墙。

六、address转string

function toString(address _address) internal view returns (string memory) {bytes32 value = bytes32(uint256(uint160(_address)));bytes memory alphabet = "0123456789abcdef";bytes memory str = new bytes(42);str[0] = '0';str[1] = 'x';for (uint256 i = 0; i < 20; i++) {str[2 + i * 2] = alphabet[uint8(value[i + 12] >> 4)];str[3 + i * 2] = alphabet[uint8(value[i + 12] & 0x0f)];}return string(str);
}

七、字符串判等比较

keccak256(bytes(_systemConfigBuiltin.get("enable_governance"))) == keccak256(bytes("true"))

八、简单的地址合法性校验

function checkAddress(address contractOrAccountAddress) public view returns (bool) {// 检查合约地址和账号地址,长度=42,且以0x开头string memory addressStr = toString(contractOrAccountAddress);return bytes(addressStr).length == 42 && bytes(addressStr)[0] == 0x30 && bytes(addressStr)[1] == 0x78;
}

九、字符串拼接

拼接字符串str1和str2,

string(abi.encodePacked(str1, str2))

拼接字符串str1和整数number,

function concatStringAndUint(string memory str1, uint256 number) public pure returns (string memory) {string memory str2 = uintToString(number);// 连接两个字符串string memory result = string(abi.encodePacked(str1, str2));return result;
}

十、实现Set集合

solidity里没有Set集合,需要单独实现。

pragma solidity ^0.8.0;library LibAddressSet {struct AddressSet {mapping(address => uint256) indexMapping;address[] values;}function add(AddressSet storage self, address value) internal {require(value != address(0x0), "LibAddressSet: value can't be 0x0");require(!contains(self, value),"LibAddressSet: value already exists in the set.");self.values.push(value);self.indexMapping[value] = self.values.length;}function contains(AddressSet storage self, address value)internalviewreturns (bool){return self.indexMapping[value] != 0;}function remove(AddressSet storage self, address value) internal {require(contains(self, value), "LibAddressSet: value doesn't exist.");uint256 toDeleteindexMapping = self.indexMapping[value] - 1;uint256 lastindexMapping = self.values.length - 1;address lastValue = self.values[lastindexMapping];self.values[toDeleteindexMapping] = lastValue;self.indexMapping[lastValue] = toDeleteindexMapping + 1;delete self.indexMapping[value];self.values.pop();}function getSize(AddressSet storage self) internal view returns (uint256) {return self.values.length;}function get(AddressSet storage self, uint256 index)internalviewreturns (address){return self.values[index];}function getAll(AddressSet storage self)internalviewreturns (address[] memory){address[] memory output = new address[](self.values.length);for (uint256 i; i < self.values.length; i++) {output[i] = self.values[i];}return output;}
}

十一、参数是结构体的event的签名

定义了一个事件LogSet7,参数是结构体NameChangeInfo,如下:

pragma solidity ^0.4.25;
pragma experimental ABIEncoderV2;contract Hello {string name;event LogSet7(NameChangeInfo nameChangeInfo);constructor() public {name = "hello";}struct NameChangeInfo {string oldName;string newName;}function set(string newName) public {emit LogSet7(NameChangeInfo(name, newName));name = newName;}
}

事件LogSet7(DecisionProposal decisionProposal)的签名,下面的都不正确:

  • LogSet7(NameChangeInfo nameChangeInfo)
  • LogSet7(NameChangeInfo)
  • LogSet7(struct)
  • LogSet7(tuple)

正确是应该是LogSet7((string,string)),将结构体的参数类型列表拼起来。


-... . - - . .-. .- -. -.. -... . - - . .-.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/8126.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Web浪漫历程:揭秘二十年间与您“约会”的浏览器发展

&#x1f9d1;‍&#x1f4bc; 个人简介&#xff1a;一个不甘平庸的平凡人&#x1f36c; &#x1f5a5;️ Node专栏&#xff1a;Node.js从入门到精通 &#x1f5a5;️ TS知识总结&#xff1a;十万字TS知识点总结 &#x1f449; 你的一键三连是我更新的最大动力❤️&#xff01;…

Kafka基础架构与核心概念

Kafka简介 Kafka是由Apache软件基金会开发的一个开源流处理平台&#xff0c;由Scala和Java编写。Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff0c;它可以处理消费者在网站中的所有动作流数据。架构特点是分区、多副本、多生产者、多订阅者&#xff0c;性能特点主要是…

React + 二级目录 + Nginx配置

背景&#xff1a; 由于子域名有限&#xff0c;我们需要将不同的前端项目进行二级目录区分。 一、项目 现有三个项目&#xff1a; bsrm-web-manage bsrm-web-operator bsrm-web-testingorganization 二、期望访问路径 http://bsrm.life.com/bsrm-web-manage http://bsrm.lif…

手机照片误删除?无需担忧,点击这里,即可轻松恢复

手机照片误删除&#xff1f;无需担忧&#xff0c;点击这里&#xff0c;即可轻松恢复 开头&#xff1a;在数字化时代&#xff0c;手机已成为我们生活中不可或缺的伙伴。随着手机摄影的普及&#xff0c;我们记录了许多珍贵的瞬间和回忆。然而&#xff0c;有时候我们不小心误删除…

Python编译过程和执行原理

hello&#xff0c;这里是Token_w的文章&#xff0c;主要讲解python的基础学习&#xff0c;希望对大家有所帮助 整理不易&#xff0c;感觉还不错的可以点赞收藏评论支持&#xff0c;感谢&#xff01; 目录 一. Python执行原理二. Python内部执行过程2.1 编译过程概述2.2 过程图解…

遥感数字图像处理实验教程(韦玉春)--部分实验问题回答

个人的学习思考&#xff0c;仅供参考。 目录 实验三、图像合成和显示增强 一、目的 二、要求 三、实验 实验五、图像变换 一、目的 二、要求 三、实验 实验六、图像滤波 一、目的 二、要求 三、实验 实验七、图像分割 一、目的 二、要求 三、实验 实验八、图…

linux判断端口是否占用(好用)

netstat 一般的话使用 netstat -tunlp | grep xxx参数作用-t指明显示TCP端口-u指明显示UDP端口-l仅显示监听套接字(所谓套接字就是使应用程序能够读写与收发通讯协议(protocol)与资料的程序)-p显示进程标识符和程序名称&#xff0c;每一个套接字/端口都属于一个程序。-n不进行…

Could not find toolchain file: E:/Git/build/cmake/android.toolchain.cmake问题解决

问题描述 在Windows下编译MNN的Android平台时&#xff0c;需要运行.sh文件&#xff0c;那么之后几种方式&#xff1a; 在power shell中启动git bash&#xff0c;但是这个窗口一闪而过&#xff0c;看不清输出的信息&#xff0c;可以通过在脚本中最后一行加入&#xff1a;sleep…

如何轻松找到竞品独立站?竞品独立站搜罗神器曝光!

独立站竞品调研对做好独立站来说的重要性不言而喻。还在纠结想做独立站但不会找竞品独立站&#xff1f;不知道怎么分析竞品独立站&#xff1f;今天东哥倾囊相授&#xff0c;把压箱底的秘籍教给你&#xff01; 怎么找竞品独立站&#xff1f; 1、谷歌购物广告 在谷歌购物页搜索产…

爬虫002_python程序的终端运行_文件运行_ipython的使用---python工作笔记020

用python运行一个文件,就是要写一个.py结尾的文件 然后保存 然后直接cmd中,python 然后写上py文件的路径就可以了 然后看一下内容 看一下终端中运行,直接输入python进入python环境,然后写python代码 回车运行 退出可以用exit()

设计模式——单例模式

1 概述 单例模式就是保证一个类只有一个对象实例。 为了保证无法创建多余的对象实例&#xff0c;单例类中需要自己创建对象实例&#xff0c;并把自己的构造方法私有化以防止其他地方调用创建对象&#xff0c;且需要提供一个公共的方法给其他类来获取该单例类的实例。 同时单例…

Linux笔记——rpm与yum下载软件命令介绍

系列文章目录 Linux笔记——进程管理Linux笔记——进程管理与网络监控技术讲解Linux笔记——进程管理 Linux笔记——管道相关命令以及shell编程 Linux笔记——磁盘进行分区与挂载介绍 文章目录 系列文章目录 前言 一 RPM介绍 1.1 RPM简单介绍 1.2 RPM命令语法 1.2.1 …

数学建模学习(2):数学建模各类常用的算法全解析

一、评价类算法 常见的评价算法 1.层次分析法 基本思想 是定性与定量相结合的多准则决策、评价方法。将决策的有关元素分解成 目标层、准则层和方案层 &#xff0c;并通过人们的 判断对决策方案的 优劣进行排序 &#xff0c;在此基础上进行定性和定量分析。它把人的思维过程…

工程师副高职称的评审条件

根据《专业技术人员职务评审暂行规定》和《工程技术人员职务评审暂行办法》&#xff0c;工程师副高职称的评审条件主要包括以下几方面&#xff1a; • 学历要求&#xff1a;具有本科及以上学历&#xff0c;或者具有大专学历并通过国家承认的专业技术资格考试。 • 工作年限要…

Python怎么实现模式匹配

什么是模式匹配 模式匹配是一种用于在数据中寻找特定模式或结构的技术。它可以用于识别、查找和提取符合特定模式要求的数据。 在计算机科学中&#xff0c;模式匹配通常用于字符串处理和数据分析领域。一些常见的模式匹配模式包括&#xff1a; 1. 字符串匹配&#xff1a;在一…

【实战】 八、用户选择器与项目编辑功能(上) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(十四)

文章目录 一、项目起航&#xff1a;项目初始化与配置二、React 与 Hook 应用&#xff1a;实现项目列表三、TS 应用&#xff1a;JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook&…

Go语言基础语法

Go语言基础语法 Hello World变量&常量定义if 判断语句for 循环语句&#xff08;只有for&#xff09;switch 选择语句数组&#xff08;较少使用&#xff09;切片&#xff08;较多使用&#xff09;map&#xff08;实际最常用&#xff09;range函数指针结构体结构体方法错误处…

23 自定义控件

案例&#xff1a;组合Spin Box和Horizontal Slider实现联动 新建Qt设计师界面&#xff1a; 选择Widget&#xff1a; 选择类名&#xff08;生成.h、.cpp、.ui文件&#xff09; 在smallWidget.ui中使用Spin Box和Horizontal Slider控件 可以自定义数字区间&#xff1a; 在主窗口w…

Docker 单机/集群 部署 Nacos2.2.0

单机部署 1- 拉取镜像 docker pull nacos/nacos-server:v2.2.02- 准备挂载的配置文件目录和日志目录 日志目录(空目录)&#xff1a;./nacos/logs配置文件&#xff1a;./nacos/conf/application.properties 从官网下载 nacos 压缩包&#xff1a;Release 2.2.0 (Dec 14, 2022…

sql进阶:求满足某列数值相加无限接近90%的行(90分位)

sql 一、案例分析二、思路三、代码实现一、案例分析 表中有某个id列和数值列,求数值列占比为90%的id,如有个用户表,存储id和消费金额order_cnt,求一条sql查出消费占比无限接近90%的所有客户,如表中总消费为10000,占比最高的是4000、3000、2800,对应A、B、C用户,查出A、B、C用户…