惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

pseudoyu

周报 #109 - Be a whole person 周报 #108 - AI 与创作的困境 周报 #107 - 基于 Multica 与 Impeccable 的开发/设计工作流 周报 #106 - OpenClaw 实践、搬家与生活的能量 周报 #105 - 打破无聊的社会 2025 年末回顾 - Anything different is good 周报 #104 - 被遗忘的生活一隅 周报 #103 - 工作随想与被吞噬的生活 周报 #102 - 我是如何使用 AI 的 周报 #101 - 消失了三个月,我的生活发生了什么变化 周报 #100 - 过去这十年 周报 #99 - 离开 RSS3,迷茫与重启人生 周报 #98 - 生活之书与 Agentic Coding 周报 #97 - 日本旅行小记 28 岁的自白:去找寻自己 周报 #96 - 基于 Roo Code/Cursor 的 iOS 开发工作流 周报 #95 - All AI 与 No AI 周报 #94 - 生活的界限与真实感 周报 #93 - 婚礼纪录片与失落的电影院 周报 #92 - 数码产品 Porn 与断舍离 周报 #91 - 一席、消失的时代与自由意志 周报 #90 - OVH 服务器、Coolify 与对 AI Coding 的思考 周报 #89 - 博客改版与 TheWan App 发布 周报 #88 - 方大同、《回留》与人生清单 周报 #87 - 我们主持了自己的宝可梦婚礼,还为它办了一个展 周报 #86 - 用影像打败时间 周报 #85 - 京华烟云 Moment in Peking 周报 #84 - 想起他们 Echos of Reverie 周报 #83 - 年初收纳(房间、设备、服务、软件) 周报 #82 - Brand New Year 与写日记 2024 年末回顾 - 不完美的日子 周报 #81 - Engaged -> Married 与时间的尺度 周报 #80 - 关于求婚、爱情与婚姻 求婚誓言 | 爱的本质是被看见 周报 #79 - 清迈旅居、曼谷之行与 Follow RSSHub 开发实践 #01:Telegram 频道 RSS 订阅实现与部署方案 Follow —— 信息获取的另一种形态 周报 #78 - NAS、Chromebook 与 Zeabur 折腾小记 周报 #77 - 一间只属于自己的房间(对亲密关系的思考) 周报 #76 - 关于 Adulting 的思考 周报 #75 - 回答 Dayu 关于独立博客的 9 个问题 周报 #74 - 清迈异闻录 周报 #73 - 《社交网络》、Ego 与中秋的现充生活 周报 #72 - 滑板体验、Rust Conf 与 Follow 公测(含邀请码) 周报 #71 - Indie App 体验计划 周报 #70 - 消失的附近,Burnout 与 Boreout 周报 #69 - AI Coding 体验与学习的源动力 周报 #68 - 关于写博客这件事与我生活的色彩 使用 GoatCounter 与 Zeabur 搭建网站数据统计系统 周报 #67 - 使用 follow 重塑我的信息输入系统 周报 #66 - 10x 工程师、技术热情与个人工具箱 从零开始搭建你的免费博客评论系统(Remark42 + fly.io) 周报 #65 - Adventure X 体验、Apple Notes 笔记实践与 EpubKit 周报 #64 - 生活在此处(《斯通纳》与《Normal People》) 使用 WebP Cloud 与 Cloudflare WAF 为你的图床添加隐私和版权保护 当云服务器崩溃时,我是如何救援重要数据的 周报 #63 - 不愉快的订花经历、商家和消费者与日渐 AI 化的人 从零开始搭建你的免费图床系统(Cloudflare R2 + WebP Cloud) 2024 年了,我的博客有了什么变化 周报 #62 - 香港之行、5am club 计划与 Rust 学习 周报 #61 - 好朋友的婚礼与对婚姻的思考 27 岁的自白:成长是一件很扫兴的事 周报 #60 - 虫子旁、教育理念与 EpubKit 周报 #59 - 沙漠之行、家庭与家人 周报 #58 - 远程工作这一年 周报 #57 - 生活在别处(巴厘岛之行) 周报 #56 - 面基 Randy 与考科目二 周报 #55 - 油画体验、博客系统升级与对 self-hosting 的思考 周报 #54 - 漂流计划、钱包被盗与 Home Server 周报 #53 - 空白的三个月、生活漂流与春节 周报 #52 - 自谦型人格与感情的另一个视角 周报 #51 - 追星小鹿 Lawrence 与生病 周报 #50 - 天坛漫步、重拾阅读和费曼学习法 周报 #49 - 烧焦的键盘、庞贝神话展、健康调整与神奇的梦 周报 #48 - 内蒙之行、清迈游民、重启的周报与生活 周报 #47 - 采访、拳击与工作状态 周报 #46 - 告别长发、周报初心与合约开发 个人信息获取与知识管理系统(Heptabase + Logseq + Readwise) 周报 #45 - 读书笔记、知识管理与双城生活 周报 #44 - 静寂工人、xLog 与日常 周报 #43 - In the Mood for Love 周报 #42 - 感情观、生活状态与自我 周报 #41 - 生日的仪式感、时间管理与北京之行 26 岁的自白:推石头的人 周报 #40 - 开源之夏、代码之外与流逝的睡眠 周报 #39 - 初患新冠、过去的心绪与失而复得的分享欲 周报 #38 - Foundry、Logseq 与 Surge Ponte 周报 #37 - 浮生半月闲( 武汉 | 香港 | 五月天) 周报 #36 - 肠胃炎、捏捏近况与新的旅途 周报 #35 - 登报、搬家和生病的捏捏 周报 #34 - Don't Stop the Clocks 周报 #33 - 北京 x 故事 x 离别 周报 #32 - 武汉、久别重逢与流动的心绪 周报 #31 - 开源、前端开发与 ChatGPT 实践 周报 #30 - 开源预算、写作初心与对技术的谦卑 Cosmos 区块链架构与 Tendermint 共识机制 周报 #29 - 日语学习经验、Steam Deck 与居酒屋 周报 #28 - Mastodon、故乡与捉迷藏的猫 周报 #27 - 何以为家 周报 #26 - 博客、客制化键盘和新服务器
Solidity 智能合约开发 - 基础
pseudoyu@connect.hku.hk (pseudoyu) · 2022-05-24 · via pseudoyu

前言

去年读研的时候上的 HKU 的 <COMP7408 Distributed Ledger and Blockchain Technology>,课程中学习了以太坊智能合约的开发,做了一个简单的图书管理 ÐApp,然后毕业设计也选择了基于 Ethereum 做了一个音乐版权应用,详见 Uright - 区块链音乐版权管理ÐApp,对 Solidity 开发有一些基础了解。

后来工作后主要做联盟链和业务开发这一块,很久没有碰过合约,对于语法和底层一些概念都已经一知半解,正好最近做的项目是基于 EVM 的一条链,涉及了一些基本的存证、回检和迁移相关合约的开发,调试起来有些吃力,于是打算系统学习一下,梳理一下笔记成文章,敦促自己好好思考总结。

这系列文章也会收录在我的个人知识库项目 《区块链入门指南》中,希望在学习过程中不断完善。有兴趣的朋友也可以访问项目仓库参与贡献或提出建议。

本文为系列第一篇,主要涉及 Solidity 基础知识。

智能合约 与 Solidity 语言

智能合约是运行在链上的程序,合约开发者可以通过智能合约实现与链上资产/数据进行交互,用户可以通过自己的链上账户来调用合约,访问资产与数据。因为区块链保留区块历史记录的链式结构、去中心化、不可篡改等特征,智能合约相比传统应用来说能更公正、透明。

然而,因为智能合约需要与链进行交互,部署、数据写入等操作都会消耗一定费用,数据存储与变更成本也比较高,因此在设计合约时需要着重考虑资源的消耗。此外,常规智能合约一经部署就无法进行修改,因此,合约设计时也需要多考虑其安全性、可升级性与拓展性。

Solidity 是一门面向合约的、为实现智能合约而创建的高级编程语言,在 EVM 虚拟机上运行,语法整体类似于 Javascript,是目前最流行的智能合约语言,也是入门区块链与 Web3 所必须掌握的语言。针对上述的一些合约编写的问题,Solidity 也都有相对完善的解决方案支持,后续会详细讲解。

开发/调试工具

与常规编程语言不同,Solidity 智能合约的开发往往无法直接通过一个 IDE 或本地环境进行方便的调试,而是需要与一个链上节点进行交互。开发调试往往也不会直接与主网(即真实资产、数据与业务所在的链)进行交互,否则需要承担高额手续费。目前开发调试主要有以下几种方式与框架:

  1. Truffle。Truffle 是一个非常流行的 Javascript 的 Solidity 合约开发框架,提供了完整的开发、测试、调试工具链,可以与本地或远程网络进行交互。
  2. Brownie。Brownie 是一个基于 Python 的 Solidity 合约开发框架,以简洁的 Python 语法为调试和测试提供了便捷的工具链。
  3. Hardhat。Hardhat 是另一个基于 Javascript 的开发框架,提供了非常丰富的插件系统,适合开发复杂的合约项目。

除了开发框架外,更好地进行 Solidity 还需要熟悉一些工具:

  1. Remix IDE。通过 Ethereum 官方提供的基于浏览器的 Remix 开发工具进行调试,Remix 会提供完整的 IDE、编译工具、部署调试的测试节点环境、账户等,可以很方便地进行测试,这是我学习使用时用的最多的工具。Remix 还可以通过 MetaMask 插件与测试网、主网进行直接交互,部分生产环境也会使用它进行编译部署。
  2. Remix IDE 对于语法提示等并不完善,因此,可以使用 Visual Studio Code 配合 Solidity 进行编写,有更好的体验。
  3. MetaMask。一个常用的钱包应用,开发过程中可以通过浏览器插件与测试网、主网进行交互,方便开发者进行调试。
  4. Ganache。Ganache 是一个开源的虚拟本地节点,提供了一个虚拟链网络,可以通过各类 Web3.js、Remix 或一些框架工具与之交互,适合有一定规模的项目进行本地调试与测试。
  5. Infura。Infura 是一个 IaaS(Infrastructure as a Service)产品,我们可以申请自己的 Ethereum 节点,通过 Infura 提供的 API 进行交互,可以很方便地进行调试,也更接近生产环境。
  6. OpenZeppelin。OpenZeppelin 提供了非常多的合约开发库与应用,能兼顾安全、稳定的同时给予开发者更好的开发体验,降低合约开发成本。

合约编译/部署

Solidity 合约是以 .sol 为后缀的文件,无法直接执行,需要编译为 EVM(Ethereum Virtual Machine)可识别的字节码才能在链上运行。

compile_solidity

编译完成后,由合约账户进行部署到链上,其他账户可通过钱包与合约进行交互,实现链上业务逻辑。

核心语法

经过上文,我们对 Solidity 的开发、调试与部署有了一定了解。接下来我们就具体学习一下 Solidity 的核心语法。

数据类型

与我们常见的编程语言类似,Solidity 有一些内置数据类型。

基本数据类型

  • boolean,布尔类型有 truefalse 两种类型,可以通过 bool public boo = true; 来定义,默认值为 false
  • int,整数类型,可以指定 int8int256,默认为 int256,通过 int public int = 0; 来定义,默认值为 0,还可以通过 type(int).mintype(int).max 来查看类型最小和最大值
  • uint,非负整数类型,可以指定 uint8uint16uint256,默认为 uint256,通过 uint8 public u8 = 1; 来定义,默认值为 0
  • address,地址类型,可以通过 address public addr = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c; 来定义,默认值为 0x0000000000000000000000000000000000000000
  • bytesbyte[] 的缩写,分为固定大小数组和可变数组,通过 bytes1 a = 0xb5; 来定义

还有一些相对复杂的数据类型,我们单独进行讲解。

Enum

Enum 是枚举类型,可以通过以下语法来定义

enum Status {
    Unknown,
    Start,
    End,
    Pause
}

并通过以下语法来进行更新与初始化


// 实例化枚举类型
Status public status;

// 更新枚举值
function pause() public {
    status = Status.Pause;
}

// 初始化枚举值
function reset() public {
    delete status;
}

数组

数组是一种存储同类元素的有序集合,通过 uint[] public arr; 来进行定义,在定义时可以预先指定数组大小,如 uint[10] public myFixedSizeArr;

需要注意的是,我们可以在内存中创建数组(关于 memorystorage 等差异后续会详细讲解),但是必须固定大小,如 uint[] memory a = new uint[](5);

数组类型有一些基本操作方法,如下:

// 定义数组类型
uint[7] public arr;

// 添加数据
arr.push(7);

// 删除最后一个数据
arr.pop();

// 删除某个索引值数据
delete arr[1];

// 获取数组长度
uint len = arr.length;

mapping

mapping 是一种映射类型,使用 mapping(keyType => valueType) 来定义,其中键需要是内置类型,如 bytesintstring 或合约类型,而值可以是任何类型,如嵌套 mapping 类型。需要注意的是,mapping 类型是不能被迭代遍历的,需要遍历则需要自行实现对应索引。

下面说明一下各类操作:

// 定义嵌套 mapping 类型
mapping(string => mapping(string => string)) nestedMap;

// 设置值
nestedMap[id][key] = "0707";

// 读取值
string value = nestedMap[id][key];

// 删除值
delete nestedMap[id][key];

Struct

struct 是结构类型,对于复杂业务,我们经常需要定义自己的结构,将关联的数据组合起来,可以在合约内进行定义

contract Struct {
    struct Data {
        string id;
        string hash;
    }

    Data public data;

    // 添加数据
    function create(string calldata _id) public {
        data = Data{id: _id, hash: "111222"};
    }

    // 更新数据
    function update(string _id) public {
        // 查询数据
        string id = data.id;

        // 更新
        data.hash = "222333"
    }
}

也可以单独文件定义所有需要的结构类型,由合约按需导入

// 'StructDeclaration.sol'

struct Data {
    string id;
    string hash;
}
// 'Struct.sol'

import "./StructDeclaration.sol"

contract Struct {
    Data public data;
}

变量/常量/Immutable

变量是 Solidity 中可改变值的一种数据结构,分为以下三种:

  • local 变量
  • state 变量
  • global 变量

其中, local 变量定义在方法中,而不会存储在链上,如 string var = "Hello";;而 state 变量在方法之外定义,会存储在链上,通过 string public var; 定义变量,写入值时会发送交易,而读取值则不会;global 变量则是提供了链信息的全局变量,如当前区块时间戳变量,uint timestamp = block.timestamp;,合约调用者地址变量,address sender = msg.sender; 等。

变量可以通过不同关键字进行声明,表示不同的存储位置。

  • storage,会存储在链上
  • memory,在内存中,只有方法被调用的时候才存在
  • calldata,作为调用方法传入参数时存在

而常量是一种不可以改变值的变量,使用常量可以节约 gas 费用,我们可以通过 string public constant MY_CONSTANT = "0707"; 来进行定义。immutable 则是一种特殊的类型,它的值可以在 constructor 中初始化,但不可以再次改变。灵活使用这几种类型可以有效节省 gas 费并保障数据安全。

函数

在 Solidity 中,函数用来定义一些特定业务逻辑。

权限声明

函数分为不同的可见性,用户不同的关键字进行声明:

  • public,任何合约都可调用
  • private,只有定义了该方法的合约内部可调用
  • internal,只有在继承合约可调用
  • external,只有其他合约和账户可调用

查询数据的合约函数也有不同的声明方式:

  • view 可以读取变量,但不能更改
  • pure 不可以读也不可以修改

函数修饰符

modifier 函数修饰符可以在函数运行前/后被调用,主要用来进行权限控制、对输入参数进行校验以及防止重入攻击等。这三种功能修饰符可以通过以下语法定义:

modifier onlyOwner() {
    require(msg.sender == owner, "Not owner");
    _;
}

modifier validAddress(address _addr) {
    require(_addr != address(0), "Not valid address");
    _;
}

modifier noReentrancy() {
    require(!locked, "No reentrancy");
    locked = true;
    _;
    locked = false;
}

使用函数修饰符则是需要在函数声明时添加对应修饰符,如:

function changeOwner(address _newOwner) public onlyOwner validAddress(_newOwner) {
    owner = _newOwner;
}

function decrement(uint i) public noReentrancy {
    x -= i;

    if (i > 1) {
        decrement(i - 1);
    }
}

函数选择器

当函数被调用时,calldata 的前四个字节要指定以确认调用哪个函数,被称为函数选择器。

addr.call(abi.encodeWithSignature("transfer(address,uint256)", 0xSomeAddress, 123))

上述代码 abi.encodeWithSignature() 返回值的前四个字节就是函数选择器。我们如果在执行前预先计算函数选择器的话可以节约一些 gas 费。

contract FunctionSelector {
    function getSelector(string calldata _func) external pure returns (bytes4) {
        return bytes4(keccak256(bytes(_func)));
    }
}

条件/循环结构

条件

Solidity 使用 ifelse ifelse 关键字来实现条件逻辑:

if (x < 10) {
    return 0;
} else if (x < 20) {
    return 1;
} else {
    return 2;
}

也可以使用简写形式:

x < 20 ? 1 : 2;

循环

Solidity 使用 forwhiledo while 关键字来实现循环逻辑,但是因为后两者容易达到 gas limit 边界值,所以基本上不用。

for (uint i = 0; i < 10; i++) {
    // 业务逻辑
}
uint j;
while (j < 10) {
    j++;
}

合约

构造器

Solidity 的 constructor 可以在创建合约的时候执行,主要用来初始化

constructor(string memory _name) {
    name = _name;
}

如果合约之间存在继承关系,constructor 也会按照继承顺序。

接口

Interface,通过声明接口来进行合约交互,有以下要求:

  • 不能实现任何方法
  • 可以继承其他接口
  • 所有方法都必须声明为 external
  • 不能声明构造方法
  • 不能声明状态变量

接口用如下语法进行定义:

contract Counter {
    uint public count;

    function increment() external {
        count += 1;
    }
}

interface ICounter {
    function count() external view returns (uint);
    function increment() external;
}

调用则是通过

contract MyContract {
    function incrementCounter(address _counter) external {
        ICounter(_counter).increment();
    }

    function getCount(address _counter) external view returns (uint) {
        return ICounter(_counter).count();
    }
}

继承

Solidity 合约支持继承,且可以同时继承多个,使用 is 关键字。

函数可以进行重写,需要被继承的合约方法需要声明为 virtual,重写方法需要使用 override 关键字。

// 定义父合约 A
contract A {
    function foo() public pure virtual returns (string memory) {
        return "A";
    }
}

// B 合约继承 A 合约并重写函数
contract B is A {
    function foo() public pure virtual override returns (string memory) {
        return "B";
    }
}

// D 合约继承 B、C 合约并重写函数
contract D is B, C {
    function foo() public pure override(B, C) returns (string memory) {
        return super.foo();
    }
}

有几点需要注意的是,继承顺序会影响业务逻辑,state 状态变量是不可以被继承的。

如果子合约想调用父合约,除了直接调用外,还可以通过 super 关键字来调用,如下:

contract B is A {
    function foo() public virtual override {
        // 直接调用
        A.foo();
    }

    function bar() public virtual override {
        // 通过 super 关键字调用
        super.bar();
    }
}

合约创建

Solidity 中可以从另一个合约中使用 new 关键字来创建另一个合约

function create(address _owner, string memory _model) public {
    Car car = new Car(_owner, _model);
    cars.push(car);
}

solidity 0.8.0 后支持 create2 特性创建合约

function create2(address _owner, string memory _model, bytes32 _salt) public {
    Car car = (new Car){salt: _salt}(_owner, _model);
    cars.push(car);
}

导入合约/外部库

复杂业务中,我们往往需要多个合约之间进行配合,这时候可以使用 import 关键字来导入合约,分为本地导入 import "./Foo.sol"; 与外部导入 import "https://github.com/owner/repo/blob/branch/path/to/Contract.sol"; 两种方式。

外部库和合约类似,但不能声明状态变量,也不能发送资产。如果库的所有方法都是 internal 的话会被嵌入合约,如果非 internal,需要提前部署库并且链接起来。

library SafeMath {
    function add(uint x, uint y) internal pure returns (uint) {
        uint z = x + y;
        require(z >= x, "uint overflow");
        return z;
    }
}
contract TestSafeMath {
    using SafeMath for uint;
}

事件

事件机制是合约中非常重要的一个设计。事件允许将信息记录到区块链上,DApp 等应用可以通过监听事件数据来实现业务逻辑,存储成本很低。以下是一个简单的日志抛出机制:

// 定义事件
event Log(address indexed sender, string message);
event AnotherLog();

// 抛出事件
emit Log(msg.sender, "Hello World!");
emit Log(msg.sender, "Hello EVM!");
emit AnotherLog();

定义事件时可以传入 indexed 属性,但最多三个,加了后可以对这个属性的参数进行过滤,var event = myContract.transfer({value: ["99","100","101"]});

错误处理

链上错误处理也是合约编写的重要环节。Solidity 可以通过以下几种方式抛出错误。

require 都是在执行前验证条件,不满足则抛出异常。

function testRequire(uint _i) public pure {
    require(_i > 10, "Input must be greater than 10");
}

revert 用来标记错误与进行回滚。

function testRevert(uint _i) public pure {
    if (_i <= 10) {
        revert("Input must be greater than 10");
    }
}

assert 要求一定要满足条件。

function testAssert() public view {
    assert(num == 0);
}

注意,在 Solidity 中,当出现错误时会回滚交易中发生的所有状态改变,包括所有的资产,账户,合约等。

try / catch 也可以捕捉错误,但只能捕捉来自外部函数调用和合约创建的错误。

event Log(string message);
event LogBytes(bytes data);

function tryCatchNewContract(address _owner) public {
    try new Foo(_owner) returns (Foo foo) {
        emit Log("Foo created");
    } catch Error(string memory reason) {
        emit Log(reason);
    } catch (bytes memory reason) {
        emit LogBytes(reason);
    }
}

payable 关键字

我们可以通过声明 payable 关键字设置方法可从合约中接收 ether

// 地址类型可以声明 payable
address payable public owner;

constructor() payable {
    owner = payable(msg.sender);
}

// 方法声明 payable 来接收 Ether
function deposit() public payable {}

Ether 交互

Ether 交互是智能合约的重要应用场景,主要分为发送和接收两部分,分别有不同的方法实现。

发送

主要通过 transfersendcall 方法实现,其中 call 优化了对重入攻击的防范,在实际应用场景中建议使用(但一般不用来调用其他函数)。

contract SendEther {
  function sendViaCall(address payable _to) public payable {
    (bool sent, bytes memory data) = _to.call{value: msg.value}("");
    require(sent, "Failed to send Ether");
  }
}

而如果需要调用另一个函数,则一般使用 delegatecall

contract B {
    uint public num;
    address public sender;
    uint public value;

    function setVars(uint _num) public payable {
        num = _num;
        sender = msg.sender;
        value = msg.value;
    }
}

contract A {
    uint public num;
    address public sender;
    uint public value;

    function setVars(address _contract, uint _num) public payable {
        (bool success, bytes memory data) = _contract.delegatecall(
            abi.encodeWithSignature("setVars(uint256)", _num)
        );
    }
}

接收

接收 Ether 主要用 receive() external payablefallback() external payable 两种。

当一个不接受任何参数也不返回任何参数的函数、当 Ether 被发送至某个合约但 receive() 方法未实现或 msg.data 非空时,会调用 fallback() 方法。

contract ReceiveEther {
    // 当 msg.data 为空时
    receive() external payable {}

    // 当 msg.data 非空时
    fallback() external payable {}

    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}

Gas 费

在 EVM 中执行交易需要耗费 gas 费,gas spent 表示需要多少 gas 量,gas price 为 gas 的单位价格,EtherWei 是价格单位,1 ether == 1e18 wei。

合约会对 Gas 进行限制,gas limit 由发起交易的用户设置,最多花多少 gas,block gas limit,由区块链网络决定,这个区块中最多允许多少 gas。

我们在合约开发中要尤其考虑尽量节约 gas 费,有以下几个常用技巧:

  1. 使用 calldata 来替换 memory
  2. 将状态变量载入内存
  3. 使用 i++ 而不是 ++i
  4. 缓存数组元素
function sumIfEvenAndLessThan99(uint[] calldata nums) external {
    uint _total = total;
    uint len = nums.length;

    for (uint i = 0; i < len; ++i) {
        uint num = nums[i];
        if (num % 2 == 0 && num < 99) {
            _total += num;
        }
    }

    total = _total;
}

总结

以上就是我们系列第一篇,Solidity 基础知识,后续文章会对其常见应用和实用编码技巧进行学习总结,欢迎大家持续关注。

参考资料

  1. Solidity by Example
  2. Ethereum 區塊鏈!智能合約(Smart Contract)與分散式網頁應用(dApp)入門
  3. 区块链入门指南
  4. Uright - 区块链音乐版权管理ÐApp