今天是手把手教你写智能合约的第一天,今天会介绍一下开发智能合约使用到的编程语言,还有以太坊虚拟机EVM的底层结构。
之后的课程会教大家开发智能合约的细节,包括编程语言的语法细节,常见的智能合约漏洞,编译,部署的到以太坊公链上,还有gas费的消耗机制,还有常见的智能合约协议,ERC20、ERC721、ERC1155...,以及如何使用nodejs开发一个智能合约应用。
1. 使用的语言
其实开发智能合约的编程语言有很多种,不过大部分都使用solidity来进行开发,我就给大家重点介绍一下solidity语言。
下面给大家分享一些solidity的在线文档,还有线上编译器
solidity中文文档:https://learnblockchain.cn/docs/solidity/
solidity在线编译器:https://remix.ethereum.org/
github: https://github.com/ethereum/solidity
1.1 solidity介绍
Solidity 是一门面向合约的、为实现智能合约而创建的高级编程语言。智能合约是管理以太坊状态里账户行为的程序。
Solidity是一种针对Ethereum虚拟机(EVM)设计的 花括号语言 。 它受到了C++、Python和JavaScript的影响。你可以在 语言影响 部分找到更多关于Solidity受到哪些语言启发的细节。
Solidity 是静态类型语言,支持继承、库和复杂的用户定义类型等特性。
在部署合约时,应该尽量使用最新版本,因为新版本会有一些重大的新特性以及bug修复(除特殊情况)。
1.2 代码示例
// SPDX-License-Identifier: GPL-3.0
// 使用的solidity 版本
pragma solidity >=0.7.0 <0.9.0;
/// 合约对象
contract Ballot {
// 状态变量 会永久存储在智能合约中
address public manager;
// 合约的构造函数
constructor(){
manager = msg.sender;
}
// 获取合约余额
function getBalance() public view returns (uint256){
return address(this).balance;
}
}
2.以太坊虚拟机 EVM
evm在线文档:https://takenobu-hs.github.io/downloads/ethereum_evm_illustrated.pdf
eth开发者文档: https://ethereum.org/zh/developers/docs/smart-contracts/
EVM 作为一个堆栈机运行,其栈的深度为 1024 个项。 每个项目都是 256 位字,为了便于使用,选择了 256 位加密技术(如 Keccak-256 哈希或 secp256k1 签名)。
在执行期间,EVM 会维护一个瞬态内存(作为字可寻址的字节数组),该内存不会在交易之间持久存在。
然而,合约确实包含一个 Merkle Patricia 存储 trie(作为可字寻址的字数组),该 trie 与帐户和部分全局状态关联。
3.存储结构
以太坊虚拟机(Ethereum Virtual Machine,EVM)的存储方式可以分为四种:栈(Stack)、状态存储(Storage)、虚拟机内存(Memory)和只读内存。
3.1 栈
EVM是基于栈的虚拟机,栈中的每一个元素的长度是256位,基本的算数运算和逻辑运算都是使用栈完成。
3.2 虚拟机内存
虚拟机内存实际上是一个连续的数组空间,用于存放如字符串等较复杂的数据结构。 即数据在内存中,因此数据仅在其生命周期内(函数调用期间)有效
memory/内存
* 内存是一个字节数组,槽大小位256位(32字节)
* 数据仅在函数执行期间存在,执行完毕后就被销毁
* 读或写一个内存槽都会消耗少量的gas 如:3gas
* 为了避免矿工的工作量过大,22个操作之后的单操作成本会上涨
3.3 只读内存
只读内存是EVM最特殊的一种存储结构,主要用于存放参数和返回值。
calldata/调用数据
* 调用数据是不可修改、非持久化的区域,用来保存函数参数,其行为类似于内存
* 外部函数的参数必须使用calldata,但是也可用于其他变量
* 调用数据避免了数据拷贝,并确保数据不被修改
* 函数也可以返回使用calldata声明的数组和结果,但是不可能分配这些类型
3.4 状态存储
*态存储时key-value的存储结构,用于持久化数据。与栈和虚拟机内存不同,状态存储的值会被记录到以太坊的状态树当中。
storage/存储
* 存储中的数据是永久存在的。存储是一个key/value库- 存储中的数据写入区块链,因此会修改状态,这也是存储使用成本高的原因。
* 占用一个256位的槽需要消耗大量的gas 如:20000 gas
* 修改一个已经使用的存储槽的值,需要消耗大量的gas 如:5000 gas
* 当清零一个存储槽时,会返还一定数量的gas
* 存储按256位的槽位分配,即使没有完全使用一个槽位,也需要支付其开销
3.5 代码示例
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract StorageTest{
string public name; //状态变量会默认声明为 storage
function stackFun() public pure returns(uint256) {
return 10 * 10;
}
function memoryFun(string memory _name) public returns(string memory){
_name = "test";
name = _name;
return name;
}
function calldataFun(string calldata _name) public returns(string calldata result){
//_name = "test";
name = _name;
return _name;
}
}
4.调用方式
在以太坊成功部署的智能合约,可以通过如下的三种方式调用合约中的公共函数(External/Public):
-
通过客户端发送消息调用交易(Message Call Transaction),其中包含了数据参数以及目标函数签名的哈希值。
这种函数调用方式必须在交易得到确认后才能生效。并且,矿工会对该交易收取Gas来作为执行函数时所需要的代价,因此,该方式是一种写操作,即会对消息调用者的账户余额以及合约的状态进行更改
-
通过另一个合约来间接的调用。
这种函数调用方式最终可以被追溯成另一笔消息调用交易。
-
通过客户端调用view(或pure)函数。
这种函数调用方式并不会改变合约的状态,也不需要耗费Gas。