关键词:
合约结构介绍
1.SPDX 版权声明
第 1 行 // SPDX-License-Identifier: MIT
就是合约的版权声明。其中 SPDX-License-Identifier(SPDX 许可标示) 是标注当前的智能合约采用什么样的对外开放标准,该标准规定了别人是否拥有商业开发,学习使用等权利。代码中使用的 MIT
规定了其他人随便用该代码,但出问题不负责。MIT 详细解释;SPDX 许可标示的注释在文件的任何位置都可以被编译器识别到的,按照规范建议把它放在文件的顶部第一行。
如果一个项目开源了智能合约的源代码,可以更好地建立社区信任。但是由于提供源代码就不可避免的涉及到版权或者法律问题。所以 solidity 鼓励开源,但是开源并不等于放弃版权。如果你不想指定任何许可证,或者代码就是不想开源,Solidity 推荐使用 UNLICENSED
;UNLICENSED
不存在于 SPDX 许可证列表中,与 UNLICENSE (授予所有人所有权利)不同,它比 UNLICENSE
多一个 D
字母。
需要注意: 源代码这里,编译器不会验证 SPDX 许可标示是否符合规范,比如我可以写为
// SPDX-License-Identifier: ANBANG
,并不会影响代码的运行。但是这里的标示会被打包在bytecode metadata
里。
bytecode metadata 介绍
当我们使用 remix
编译合约的时候,会在根目录创建 artifacts
文件夹,其中包含 build-info
记录构建信息的文件夹,以及每个合约名字作为文件名的文件夹,比如 contract Hello
将生成
-
Hello.json
文件 -
Hello_metadata.json
文件
Hello.json
文件结构
deploy: ,
data:
bytecode: ,
deployedBytecode: ,
gasEstimates: ,
methodIdentifiers: ,
,
abi: [],
;
Hello_metadata.json
文件结构
compiler:
version: "0.8.17+commit.xxx",
,
language: "Solidity",
output:
abi: [],
devdoc: ,
userdoc: ,
,
settings: ,
sources:
"aaa.sol":
keccak256:
"0x637c141739144cd991b9350336a1f8c3b948811d7ed743fefb4aad99d7bb362f",
license: "kyp",
urls: [
"bzz-raw://9eea517225b90242d6e3761046f5f5a8f0a2393747c89f3af01f34ad00764dc4",
"dweb:/ipfs/QmXp5wap9ZNC9fihdA7aLMe7bKWBjeAuv7khEuvKrgp9Bx",
],
,
,
version: 1,
;
// SPDX-License-Identifier: kyp
中的 kyp
就是在 sources -> filename.sol -> license
中
2.pragma solidity 版本限制
第 2 行 pragma solidity ^0.8.17;
指令,它是告诉编译器,我当前的合约代码采用的是 Solidity 0.8.17 这个版本为基础编写的,解析部署时需要在匹配的版本下进行,在区块链浏览器上进行合约验证时,也需要选择匹配的版本。
⓵ 使用 ^
的意义和优点
而 ^0.8.17
中的 ^
表示小版本兼容,大版本不兼容,相当于 pragma solidity >= 0.8.17 < 0.9.0;
。他既不允许低于0.8.17
的编译器编译,也不允许大于等于 0.9.0
版本的编译器进行编译。之所以这么写,不写死 pragma solidity 0.8.17;
是为了可以享受到编译器的补丁版,比如以后出来了 0.8.40
版本,那么当前合约是可以运行在未来的 0.8.40
这个新版本的编译器。但是如果是大版本升级到了 0.9.0
,那么编译器不会用新版的0.9.x
解析,会使用 0.8 的最后一个稳定版本来进行解析编译。这里如果不加 ^
,直接写pragma solidity 0.8.17;
,就是告诉编译器,当前合约只选择在 0.8.17
版本来编译和部署;这样做的缺点就是享受不到以后出的补丁版的编译器。
⓶ 跨大版本的合约
如果你打算跨大版本的合约,可以使用>
/>=
/<
/<=
来操作,比如 pragma solidity >=0.7.0 <0.9.0;
。
注意:
pragma
指令只对当前的源文件起作用,如果把文件B
导入到文件A
,文件 B 的 pragma 将不会自动应用于文件 A。
总结:
pragma solidity ^0.8.17;
是用来告诉编译器应该选择什么版本来解析编译当前代码。
pragma
指令只对当前的源文件起作用。注:一份源文件可以包含多个版本声明、多个导入声明和多个合约声明。
3.contract 关键字
第 3 行的 contract Hello
是合约的基本结构;其中 contract
声明了当前代码块内是一个完整的合约。而 Hello
是当前合约的名字,合约名称是必须的,首字母一般采用大写字母开头。
contract
代表特殊的意义,这种有特殊意义的词,在编程界里一般被称为 保留关键字
;保留关键字是现在或者将来被用到的特殊代指,都有固定意义,所以保留关键字不能作为变量名和函数名字。
-
总结:
-
contract 基本结构是
contract ContractName
-
Solidity 合约中,合约的名字是必须的。
-
合约的名称,一般约定为 大驼峰命名方式
-
contract 是保留关键字
-
保留关键字不能作为变量名和函数名
-
-
扩展:
注:一份源文件可以包含多个版本声明、多个导入声明和多个合约声明。
⓵ 变量
合约内的 message
叫做状态变量,状态变量是永久地存储在合约存储中的值。关于变量的更多信息,会在后续 [变量] 那一章详细介绍
⓶ 函数
函数是代码的可执行单元,是一组逻辑的集合。关于变量的更多信息,会在后续 函数 那一章详细介绍
⓷ this 关键字
Solidity 中 this
代表合约对象本身;
-
可以通过
address(this)
获取合约地址。 -
可以通过
this.fnName
获取 external 函数
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; contract Demo function contractAds() external view returns (address) return address(this); function testExternal() external view returns (address) return this.contractAds();
⓸ 合约地址/合约创建者地/合约调用者地址
这三个地址概念一定要完全理解。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; // 这三个地址的概念一定要理解清楚 contract Demo address public owner; constructor() // 可以用在 constructor 内获取当前合约地址 owner = address(this); // 不可以在构造函数内调用函数,因为此时合约还没有完成构建好。 // this.caller(); 相当于从外部调用 caller 方法 // owner = this.caller(); function caller() external view returns (address) return this.contractAds(); // 内部调用 external 可见性的函数 function contractAds() external view returns (address) return address(this);
⓹ 合约属性:type 关键字
-
type(C).name
:获得合约名 -
type(C).creationCode
:获得包含创建合约字节码的内存字节数组。它可以在内联汇编中构建自定义创建例程,尤其是使用 create2 操作码。 不能在合约本身或派生的合约访问此属性。 因为会引起循环引用。 -
type(C).runtimeCode
:获得合约的运行时字节码的内存字节数组。这是通常由 C 的构造函数部署的代码。 如果 C 有一个使用内联汇编的构造函数,那么可能与实际部署的字节码不同。 还要注意库在部署时修改其运行时字节码以防范定期调用(guard against regular calls)。 与 .creationCode 有相同的限制,不能在合约本身或派生的合约访问此属性。 因为会引起循环引用。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; contract Hello string public message = "Hello World!"; contract Demo function name() external pure returns (string memory) return type(Hello).name; function creationCode() external pure returns (bytes memory) return type(Hello).creationCode; function runtimeCode() external pure returns (bytes memory) return type(Hello).runtimeCode;
除了上面介绍的版权声明,版本限制,contract 外,合约文件还包括 import
, interface
,library
,一起展开介绍下
4.import 导入声明
功能:从其他文件内倒入需要的变量或者函数。
⓵ 导入方式
既可以导入本地文件,也可以导入 url(网络上的 ipfs,http 或者 git 文件)
-
导入所有的全局标志
import "filename";
到当前全局范围
-
导入本地文件:
import "./ERC20.sol";
,其中./
表示当前目录,查找路径参考 -
导入网络文件:
import "https://github.com/aaa/.../tools.sol";
-
导入本地 NPM 库:
-
$ npm install @openzeppelin/contracts
-
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
-
-
-
导入所有的全局标志,并创建新的全局符号
-
方式一:
import * as symbolName from "filename";
-
方式二:
import "filename" as symbolName;
-
-
按需导入,按需修改名称
-
import symbol1 as aliasName, symbol2 from "filename";
-
不推荐导入变量标示名到当前全局范围的方式,因为不可控,容易污染当前的命名空间。如果全局导入,推荐使用 import "filename" as symbolName;
注:一份源文件可以包含多个版本声明、多个导入声明和多个合约声明。
⓶ 导入时候的本地路径
上文中的 filename 总是会按路径来处理,以 /
作为目录分割符、以 .
标示当前目录、以 ..
表示父目录。 当 .
或 ..
后面跟随的字符是 /
时,它们才能被当做当前目录或父目录。 只有路径以当前目录 .
或父目录 ..
开头时,才能被视为相对路径。
用 import "./x.sol" as x;
语句导入当前源文件同目录下的文件 x.sol
。 如果用import "x.sol" as x;
代替,可能会引入不同的文件(在全局 include directory
中)。
最终导入哪个文件取决于编译器(见下文)到底是怎样解析路径的。 通常,目录层次不必严格映射到本地文件系统, 它也可以映射到能通过诸如 ipfs,http 或者 git 发现的资源。
5.interface: 接口
⓵ 接口使用案例
在下面的例子中,定义了 cat 合约以及 dog 合约。他们都有 eat
方法.以此他们都可以被上面的 animalEat
接口所接收。
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; contract Cat uint256 public age; function eat() public returns (string memory) age++; return "cat eat fish"; function sleep1() public pure returns (string memory) return "sleep1"; contract Dog uint256 public age; function eat() public returns (string memory) age += 2; return "dog miss you"; function sleep2() public pure returns (string memory) return "sleep2"; interface AnimalEat function eat() external returns (string memory); contract Animal function test(address _addr) external returns (string memory) AnimalEat general = AnimalEat(_addr); return general.eat();
⓶ type(I).interfaceId
返回接口I
的 bytes4
类型的接口 ID,接口 ID 参考: EIP-165 定义的, 接口 ID 被定义为 XOR (异或) 接口内所有的函数的函数选择器(除继承的函数。
上面的代码种,可以增加如下的函数来查看 interfaceId
;
contract Animal // ... function interfaceId() external pure returns (bytes4) return type(AnimalEat).interfaceId;
更多内容在interface:接口那一章详细介绍。
6.library:库合约
库与合约类似,但它的目的是在一个指定的地址,且仅部署一次,然后通过 EVM 的特性来复用代码。
library Set struct Data mapping(uint => bool) flags; function test()
其他合约调用库文件的内容直接通过库文件名.方法名例如:Set.test()
。
智能合约语言solidity教程系列1-类型介绍(代码片段)
...的都比它好,大家还是别看了。写在前面Solidity是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么Solidity教程会是一系列文章,本文是第一篇:介绍Solidity... 查看详情
智能合约语言solidity教程系列6-结构体与映射(代码片段)
...的文章列表请查看分类-Solidity。写在前面Solidity是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么本系列文章一部分是参考Solidity官方文档(当前最新版... 查看详情
solidity智能合约单元测试介绍(代码片段)
Solidity智能合约单元测试介绍当前在各种区块链中,生态最全的要属兼容EVM的区块链,在该类区块链上的智能合约绝大部分使用Solidity编写。因此,对Solidity编写的智能合约进行单元测试便成为一项经常性的工作。本文... 查看详情
solidity智能合约单元测试介绍(代码片段)
Solidity智能合约单元测试介绍当前在各种区块链中,生态最全的要属兼容EVM的区块链,在该类区块链上的智能合约绝大部分使用Solidity编写。因此,对Solidity编写的智能合约进行单元测试便成为一项经常性的工作。本文... 查看详情
智能合约语言solidity教程系列3-函数类型(代码片段)
...的文章列表请查看分类-Solidity。写在前面Solidity是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么本文前半部分是参考Solidity官方文档(当前最新版本:0.... 查看详情
solidity开发以太坊代币智能合约(代码片段)
智能合约开发是以太坊编程的核心之一,而代币是区块链应用的关键环节,下面我们来用solidity语言开发一个代币合约的实例,希望对大家有帮助。以太坊的应用被称为去中心化应用(DApp),DApp的开发主要包括两大部分:智能... 查看详情
solidity编写智能合约遇到的各种问题(代码片段)
solidity编写智能合约遇到的各种问题合约部署1.creationofXXXXXerrored:transactionexecutionfailed2.creationofCSerrored:Errorencodingarguments:SyntaxError:UnexpectedtokenxinJSONatposition47合约调用1.通过接口调用其他.sol文件内的函数solidity的数据结构1.Mapping的删... 查看详情
智能合约最佳实践之solidity编码规范(代码片段)
...的总结,供大家参考,希望可以帮助大家写出更好规范的智能合约。命名规范避免使用小写的l,大写的I,大写的O应该避免在命名中单独出现,因为很容易产生混淆。合约、库、事件、枚举及结构体命名合约、库、事件及结构体... 查看详情
第一行代码:以太坊-使用solidity语言开发和测试智能合约(代码片段)
智能合约是以太坊的核心之一,用户可以利用智能合约实现更灵活的代币以及其他DApp。不过在深入讲解如何开发智能合约之前,需要先介绍一下以太坊中用于开发智能合约的Solidity语言,以及相关的开发和测试环境。智能合约就... 查看详情
智能合约语言solidity教程系列9-错误处理(代码片段)
...请扫描下面的二维码查看。 写在前面Solidity是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么欢迎订阅区块链技术专栏阅读更全面的分析文章。什么... 查看详情
以太坊智能合约开发:solidity语言快速入门(代码片段)
在本文中,我们从一个简单的智能合约样例出发,通过对智能合约源文件结构的剖析与介绍,使大家对Solidity语言有一个初步的认识。最后,我们将该智能合约样例在Remix合约编译器中编译、部署,观察其执行... 查看详情
智能合约语言solidity教程系列7-以太单位及时间单位(代码片段)
...的文章列表请查看分类-Solidity。写在前面Solidity是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么货币单位(EtherUnits)一个数字常量(字面量)后面跟随... 查看详情
第84篇笔记-智能合约(solidity)编程(代码片段)
1.源文件结构示例://SPDX-License-Identifier:GPL-3.0pragmasolidity>=0.4.16<0.9.0;contractSimpleStorageuintstoredData;functionset(uintx)publicstoredData=x;functionget()publicviewreturns(uint)returnsto 查看详情
通学智能合约系列(二十)--结构体<上>(代码片段)
hello,大家好,欢迎继续坚持学习智能合约。这们这节来看看solidity结构体.3、结构体定义与初始化在solidity的世界中,同样定义了结构体来表示复杂的对象类型,我们一起看看他是怎么来定义和初始化的吧~pragmasolidity^0.... 查看详情
智能合约从入门到精通:调用数据的布局和abi(代码片段)
简介:本文将介绍Solidity语言的调用数据的布局和ABI详解。其中调用数据的布局将主要介绍以太坊合约间调用时的消息格式ABI。好久时间没有更新文章,前文中我们介绍了Solidity的特性与内部机制,本文我们将Solidity的调用数据的... 查看详情
智能合约语言solidity教程系列5-数组介绍
写在前面Solidity是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么本文前半部分是参考Solidity官方文档(当前最新版本:0.4.20)进行翻译,后半部分对官... 查看详情
使用browser-solidity在go-ethereum上进行简单的智能合约部署(代码片段)
...在本文讲解之前,先介绍以下几点基本概念。1.1、什么是智能合约? 智能合约是存储在区块链上的一段代码,它们可以被区块链上的交易所触发,触发后,这段代码可以从区块链上读取数据或者向区块链上写入数据。 1.2... 查看详情
通过一个案例精通以太坊智能合约和solidity(代码片段)
作者介绍SilverCEO星际区块链信息发展有限公司项目组件??这个项目是一个构建在以太坊上的游戏,感谢这个团队给我们提供的案例:https://cryptozombies.io??从功能的角度看,有如下脚本:zombiefactory.sol:定义zombie和生成zombie。zombiefeedi... 查看详情