Solidity学习思路
- 看基础,主要资料为官网和哔哩哔哩教程,了解清楚基本语法、基本类型、基本关键字、基本结构等;
- 配IDE,搭建一整套开发调试环境,如:在线Remix、在线ChainIDE等;
- 开发调试基础合约,如:Hello World,存储Storage等,主要熟悉智能合约的整个开发部署流程;
- 尝试开发复杂合约,如投票,同时对比学习别人的投票合约;
- 了解辅助工具库,如OpenZeppelin,避免重复造轮子,提高开发上层应用的质量和效率;
- 整理文章与问题,与人交流,完善不足之处;
- 加入技术社区,继续学习成长,同时捕捉行业最新动向。
问题交流
获取函数返回值的两种方法,这样理解对吗?
一种是函数直接返回结果值,SDK调用直接取到返回值;
另一种是函数没有返回值,但是函数emit 一个event,SDK调用后,获取到event的执行log,然后从log中提取出需要的结果值。
当KV存储为作品hash到用户地址时,怎么实现通过地址查询所有的作品?
我理解KV的mapping存储方式,键查询值很容易,但是值查询键很困难耗时。像在NFT的ERC721环境下,KV为token到地址。这种,我怎么查询某地址的所有token呢。(moralis解决)智能合约编写我理解为面向对象式编程,中间涉及类、类变量、函数、函数参数、函数变量、函数返回值、内置函数、关键字修饰等,当被调用时触发函数的执行,并同步函数过程数据到区块链。我编写一个智能合约的话,我会按照需求梳理一下大致逻辑,把合约主要变量和方法确定下来,把方法的主要逻辑确定下去,然后编写基本逻辑,然后测试。这种理解和开发思路没有问题吧?(√)
国内的话,智能合约开发语言有什么建议呢?我的优先级是Solidity、Go、Rust、C++,但只打算学习Solidity和Go。
像NFT的链上token标识,建议使用自然数递增标识,还是使用固定长度的数字串标识呢?如果使用固定长度的数字串标识,有什么算法推荐吗?(自然数递增标识)
像NFT的链上token标识,使用自然数递增标识的话,需要考虑高并发下,自增混乱的情况下,比如两个token拿到了同一个ID。之前做电商后端遇到过,高并发的时候,两个人买到了同一件商品,是通过加锁解决的。不知道这种问题在智能合约下会不会发生。(控制放在后端实现。)
在智能合约开发中,我还应该注意什么,以及还应该学习什么,以提高我的智能合约开发能力。
Solidity官网
Solidity变量命名规则
合约和库名称
合约和库名称应该使用驼峰式风格。比如:SimpleToken
,SmartBank
,CertificateHashRepository
,Player
。
结构体名称
结构体名称应该使用驼峰式风格。比如:MyCoin
,Position
,PositionXY
。
事件名称
事件名称应该使用驼峰式风格。比如:Deposit
,Transfer
,Approval
,BeforeTransfer
,AfterTransfer
。
函数名称
函数名称不同于结构,应该使用混合式命名风格。比如:getBalance
,transfer
,verifyOwner
,addMember
,changeOwner
。
函数参数命名
函数参数命名应该使用混合式命名风格。比如:initialSupply
,account
,recipientAddress
,senderAddress
,newOwner
。 在编写操作自定义结构的库函数时,这个结构体应该作为函数的第一个参数,并且应该始终命名为 self
。
局部变量和状态变量名称
使用混合式命名风格。比如:totalSupply
,remainingSupply
,balancesOf
,creatorAddress
,isPreSale
,tokenExchangeRate
。
常量命名
常量应该全都使用大写字母书写,并用下划线分割单词。比如:MAX_BLOCKS
,TOKEN_NAME
,TOKEN_TICKER
,CONTRACT_VERSION
。
修饰符命名
使用混合式命名风格。比如:onlyBy
,onlyAfter
,onlyDuringThePreSale
。
枚举变量命名
在声明简单类型时,枚举应该使用驼峰式风格。比如:TokenGroup
,Frame
,HashStyle
,CharacterLocation
。
避免命名冲突
single_trailing_underscore_
当所起名称与内建或保留关键字相冲突时,建议照此惯例在名称后边添加下划线。
Solidity关键字
public/private/internal/external 可见性分析
public:任意访问(合约中函数默认);公共函数是合约接口的一部分,可以通过内部调用或通过消息调用。对公共状态变量而言,会有自动访问限制符的函数生成。
private:仅当前合约内;私有函数和状态变量仅仅在定义该合约中可见, 在派生的合约中不可见,可防止其他合约访问和修改信息。
internal:仅当前合约及所继承的合约(状态变量声明时默认)。
external: 仅外部访问(在内部也只能用外部访问方式访问);外部函数是合约接口的一部分,这意味着它们可以从其他合约调用,也可以通过事务调用。外部函数 f 不能被内部调用 (即 f () 不执行,但 this.f () 执行)。外部函数,当他们接收大数组时,更有效。
当一个状态变量的权限为 public 类型时,它就会自动生成一个可供外部调用的 get 函数。
只有 public 类型的函数才可以供外部访问。
状态变量声明时,默认为 internal 类型,只有显示声明为 public 类型的状态变量才会自动生成一个和状态变量同名的 get 函数以供外部获取当前状态变量的值。
函数声明时默认为 public 类型,和显式声明为 public 类型的函数一样,都可供外部访问。
internal 类型的状态变量可供外部和子合约调用。
internal 类型的函数和 private 类型的函数一样,智能合约自己内部调用,它和其他语言中的 protected 不完全一样。
子合约只能继承父合约中的所有的 public 类型的函数,可以对其进行重写,不能继承 internal/private 的函数。
内部 (internal) 函数只能在当前合约内被调用(在当前的代码块内,包括内部库函数,和继承的函数中),访问函数直接用函数名 f。
外部 (external) 函数由地址和函数方法签名两部分组成,可作为外部函数调用的参数,或返回值,访问函数用 this.f 。
函数可以被定义为 external, public, internal or private,缺省是 public。
对状态变量而言,external 是不可能的,默认是 internal。
pure/constant/view/payable 限制访问
- PAYABLE:一个函数要进行货币操作必须要带上 payable 关键字。
- VIEW:view 的作用和 constant 一模一样, 可以读取状态变量但是不能改。
- PURE:pure 则更为严格,pure 修饰的函数不能改也不能读状态变量,智能操作函数内部变量,否则编译通不过。
- CONSTANT:
- 常量:状态变量可以被定义为 constant,常量。这样的话,它必须在编译期间通过一个表达式赋值。赋值的表达式不允许:1)访问 storage;2)区块链数据,如 now,this.balance,block.number;3)合约执行的中间数据,如 msg.gas;4)向外部合约发起调用。目前仅有值类型和字符串支持常量。
- 常函数:函数也可被声明为常量,这类函数将承诺自己不修改区块链上任何状态。
constant、view、pure 三个函数修饰词的作用是告诉编译器,函数不改变 / 不读取状态变量,这样函数执行就可以不消耗 gas 了,因为不需要矿工来验证。
其他关键字
modifier:被 modifier 关键字声明的关键字所修饰的函数只能在满足 modifier 关键字声明的关键字的要求后才会被执行,比如声明某函数只有管理员有权限。
Storage 变量是指永久存储在区块链中的变量。 函数内局部变量的默认数据位置为 storage。 状态变量的默认数据位置是 storage。(局部变量: 函数中的参数、函数内声明的变量;状态变量: 合约中定义的变量(在函数之外)。)
Memory 变量则是临时的,当外部函数对某合约调用完成时,内存型变量即被移除。函数的返回值的默认数据位置是 memory。
calldata:一般只有在外部函数(external)的参数被强制指定为 calldata,这种数据位置是只读的,不会持久化到区块链中。
常见函数
回退函数 fallback function
每一个合约有且仅有一个没有名字的函数。这个函数无参数,也无返回值。如果调用合约时,没有匹配上任何一个函数 (或者没有传哪怕一点数据),就会调用默认的回退函数。此外,当合约收到 ether 时(没有任何其它数据),这个函数也会被执行。
事件 EVENT
在介绍事件前,我们先明确事件,日志这两个概念。事件发生后被记录到区块链上成为了日志。总的来说,事件强调功能,一种行为;日志强调存储,内容。
事件是以太坊 EVM 提供的一种日志基础设施。事件可以用来做操作记录,存储为日志。也可以用来实现一些交互功能,比如通知 UI,返回函数调用结果等。
OpenZeppelin
OpenZeppelin 的智能合约代码库是以太坊开发者的宝库,OpenZeppelin 代码库包含了经过社区审查的 ERC 代币标准、安全协议以及很多的辅助工具库,这些代码可以帮助开发者专注业务逻辑的,而无需重新发明轮子。
基于 OpenZeppelin 开发合约,即可以提高代码的安全性,又可以提高开发效率。
https://learnblockchain.cn/article/1012
1. 使用 Ownable 进行所有者限制
OpenZeppelin 的 Ownable 合约提供的 onlyOwner 修饰器是用来限制某些特定合约函数的访问权限。
Ownable 合约的部署账号会被当做合约的拥有者(owner),某些合约函数,例如转移所有权,就限制在只允许拥有者(owner)调用。
2. 使用 Roles 进行角色控制
进行访问控制另一个相对于 Ownable 合约 更高级一些的是使用 Roles 库, 它可以定义多个角色,对于需要多个访问层次的控制时,应当考虑使用 Roles 库。
由于 Roles 是一个 Solidity 库而非合约,因此不能通过继承的方式来使用,需要使用 solidity 的 using 语句来将库中定义的函数附加到指定的数据类型上。
3. 安全的算术运算库:SafeMath
永远不要直接使用算术运算符例如:+、-、*、/ 进行数学计算,除非你了解如何检查溢出漏洞,否则就没法保证这些算术计算的安全性。
SafeMath 库的作用是帮我们进行算术运中进行必要的检查,避免代码中因算术运算 (如溢出)而引入漏洞。
和 Roles 库的用法类似,你需要使用 using 语句将 SafeMath 库中的函数附加到 uint256 类型上。
4. 安全类型转换库:SafeCast
作为一个智能合约开发者,我们常常会思考如何减少合约的执行时间以及空间,节约代码空间的一个办法就是使用更少位数的整数类型。 但不幸的是,如果你使用 uint8 作为变量类型,那么在调用 SafeMath 库函数之前,就必须先将其转换为 uint256 类型,然后在调用 SafeMath 库函数之后,还需要再转换回 uint8 类型。SafeCast 库的作用就在于可以帮你完成这些转换而无需担心溢出问题。
5. ERC20Detailed
不需要自己实现完整的 ERC20 代币合约 ,OpenZeppelin 已经帮我们实现好了, 我们只需要继承和初始化就好了。
OpenZeppelin 的 ERC20 进行了标准的基础实现,ERC20Detailed 合约包含了额外的选项:例如代币名称、代币代号以及小数点位数。
6. 非同质化代币:ERC721Enumerable / ERC721Full
OpenZeppelin 也提供了非同质化代币的实现,我们同样不需要把完整的把标准实现一次。
如果需要枚举一个账号的所持有的 ERC721 资产,需要使用 ERC721Enumerable 合约而不是基础的 ERC721,
ERC721Enumerable 提供了_tokensOfOwner() 方法 直接支持枚举特定账号的所有资产。如果你希望有所有的扩展功能合约,那么可以直接选择 ERC721Full。
7. 用 Address 库识别地址
有时候在 Solidity 合约中需要了解一个地址是普通钱包地址还是合约地址。 OpenZeppelin 的 Address 库提供了一个方法 isContract() 可以帮我们解决这个问题。
8. Counters 生成唯一ID
如ERC721环境下,生成唯一标识的TokenID。
a simple way to get a counter that can only be incremented or decremented. Very useful for ID generation, counting contract activity, among others.
使用示例
1 | import "@openzeppelin/contracts/utils/Counters.sol"; |
扩展
质押
权益质押(staking)通常指将 cryptocurrency 作为抵押物锁定,以保障某一区块链网络或智能合约协议的安全。权益质押的 cryptocurrency 资产通常与 DeFi 流动性、收益奖励和治理权挂钩。用 Cryptocurrency 进行权益质押,即:将通证锁定在某一区块链网络或协议中,以获得回报;并且这些通证将用于为用户提供关键服务。
权益证明(PoS)是区块链上的一种抗女巫攻击机制,验证节点必须要在网络中质押通证,才能有机会向链上添加新区块。在 PoS 区块链上,任何人只要质押了一定数额的原生通证,就可以加入网络成为验证节点(staker),并产出区块。验证节点质押的通证数量或者一名用户运行的验证节点数量通常决定了它被选为出块节点的概率。也就是说,质押的通证数量越多,或者控制的验证节点数量越多,那么就越有可能被选中成为出块节点。
当验证节点成功创建有效区块时,通常会从协议收到质押奖励以及部分用户交易费。PoS 区块链为了减少恶意行为,往往会采取处罚机制。在这个机制下,验证节点如果违反了协议规则,将会被没收部分或全部质押的通证。一些 PoS 区块链中如果节点下线或出块节点未能正常出块,也会被没收质押的通证。
合约升级 / 合约代理
moralis
主要对象是公链,在 Ethereum、BSC、Polygon、Solana 和 Elrond 区块链上构建和部署去中心化应用程序 (dApp) 的最快方式。
How to get all the NFT collections owned by an address
Migrations
投票合约
1 | // 匹配Solidity版本 |
积分合约
1 | // SPDX-License-Identifier: MIT |
合约升级
1 | // SPDX-License-Identifier: MIT |
版权合约
- 版权注册管理:
版权hash(远端内容IPFS、OSS),版权title,版权拥有者authors
注册版权manifestAuthorship,注册联合创作者版权manifestJointAuthorship,获取版权信息getManifestation - 版权证据管理:
版权hash,证据提供者evidenceProviders,证据数evidenceCounts
获取证据总数getEvidenceCount,增加证据addEvidence,增加证据提供者addEvidenceProvider - 版权过期管理:
版权hash,创建时间creationTime,过期时间expiryTime
查询是否过期isExpired,续期setExpiry
参考GitHub:https://github.com/pseudoyu/uright/tree/master/contracts
支付合约
支付存证管理:
支付ID,支付时间戳,支付流水号,支付人,支付金额,支付方式,支付……
支付存证增加,获取支付存证信息支付分佣管理:
支付分佣ID,支付分佣时间戳,支付分佣流水号,分佣人,分佣比例,分佣金额,分佣……
支付分佣增加,获取分佣记录,获取某分佣详情