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

推荐订阅源

Recent Announcements
Recent Announcements
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
MongoDB | Blog
MongoDB | Blog
H
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
人人都是产品经理
人人都是产品经理
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
V
V2EX
Microsoft Security Blog
Microsoft Security Blog
V
Visual Studio Blog
A
About on SuperTechFans
博客园_首页
L
LangChain Blog
量子位
雷峰网
雷峰网
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Jina AI
Jina AI
月光博客
月光博客
阮一峰的网络日志
阮一峰的网络日志
博客园 - 聂微东
Microsoft Azure Blog
Microsoft Azure Blog
M
MIT News - Artificial intelligence
N
Netflix TechBlog - Medium

秋码分享

我是如何解决将 c++ 编译成可以在 node.js 中使用的 *.node,中间出现的一大堆问题的(指纹浏览器基石篇) eSIM Plus 爱沙尼亚手机号彻底翻车?“永久有效”悄然变成了一年! 接码平台 SMS-Activate 余额可以转移到新平台使用,截止日期:2026年1月29日 是时候将 hugo-theme-kiwi 主题提交到 themes.gohugo.io 站点上了 Flux2 刚开源就凉了?Z-Image 本地部署狠狠打了个样 声音的未来:Chatterbox —— 用「夸张度旋钮」提升表现力的开源 TTS 向导 还以为那只是换个背景?Qwen-Image-Edit 在 ComfyUI 中能做到更离谱的事 Windows 结合最新版 ComfyUI 部署阿里最新开源的 Qwen-Image 图像大模型 从零样本到跨场景:Seed-VC语音转换技术的革命性突破 大语音模型轻量化革命:MegaTTS3 如何重新定义文本生成语音的技术边界(windows篇) 竞赛级编程大模型OlympicCoder-7B之本地部署(Windows篇) 阿里开源了端到端全模态大模型Qwen-2.5-Omini-7B之本地部署(windows篇) 语音识别之whisper本地部署(实时语音之开篇) 甭管是个人还是企业都能部署的Mistral-Small3.1,远超同级别的模型 文生音乐开源项目DiffRhythm,8G显存本地部署之Windows篇 阿里QwQ-32B本地部署指南:用Ollama轻松运行320亿参数大模型 基于Qwen2.5大模型的Spark-TTS,零样本语音克隆,CPU可运行之本地部署(Windows篇) 智谱开源了文生图CogView4-6B模型,支持中文提示词之本地部署(Windows篇) 基于歌词生成整首歌的开源AI音乐模型,支持中、英、日、韩等多种语言,本地化部署YuE(windows篇) 阿里云开源的文生视频万相 Wan2.1之本地部署Wan2.1-T2V-1.3B模型 互动式开源AI图像编辑神器,Windows11本地部署 MagicQuill 本地部署Qwen2.5-VL-7B-Instruct多模态视觉大模型(Windows篇) 保持角色一致性的绘本生成AI开源项目之Story-Adapter本地部署Windows篇 本地部署 Stable Diffusion 3.5(最新 ComfyUI记录篇) 谁说Win7安装不了Node.js最新版的呢?都2025年,还不更新系统到Win11 vs code远程调试Linux服务器上的php代码 浏览器定制 | Windows11 编译 Chromium 133.0.6885.0(截稿前Chromium最新版之编译篇[一]) 不说是彻底搞懂,至少让你不再惧怕c/c++指针,以及各种奇葩指针变种 解决windows下php8.x及以上版本,在Apache2.4中无法加载CURL扩展的问题 在 Windows8.1 下编译 Chromium (103.0.5060.68 之三)
玩以太坊链上项目的必备技能(内联汇编 [inline assembly]-So...
一个游离于山间之上的江湖漫行者。A jungle wanderer who wanders above the mountain · 2022-12-28 · via 秋码分享

在 Solidity 中使用 Assembly 的主要好处之一是节省 gas。 让我们尝试通过创建一个将 2 个值 x 和 y 相加并返回结果的函数来比较 Solidity 和 Assembly 之间的 gas 成本。

概要

大抵是讲到汇编,身为编程开发者的我们脑瓜子早就嗡嗡作响了。看那晦涩难懂的低级汇编代码,敢断言,那一行不是我写的,其他行也不是我写的。

自从C语言问世,而后类C语言犹如雨后春笋般地搅动着IT界,而这些语言有别于汇编语言那样。它们就是更贴切自然语言的高级编程语言,可这些高级编程语言最终还是要编译成机器语言(汇编语言)。

EVM(Ethereum Virtual Machine)是一种栈(Stack)结构,我们知道是一种先进后出(LIFO)的数据结构。

那为什么要用汇编来编写呢?

借您所问,既然 Solidity 可以编写出优秀的智能合约,那为什么还要使用低级地汇编语言呢?

在回答这个问题之前,我们来看看每个新的编程语言诞生都是为了解决当前编程语言无法解决,或者说使用当前编程语言解决起来比较麻烦,那么,新的编程语言就在这样的环境下应运而生,当然咯,并不是所有新编程语言都是为了解决当前编程语言不能解决的问题,才被开发出来,而是……(此处不便说出缘由,毕竟它也不是本文的重点)。

细粒度控制

Assembly允许您执行一些仅仅靠 Solidity 无法实现的逻辑,比如,指向特定的内存插槽(Memory Slot)

当我们在编写库(library)时,细粒度控制特别有用,因为它们会被重复使用。

节省 gas

在 Solidity 中使用 Assembly 的主要好处之一是节省 gas。 让我们尝试通过创建一个将 2 个值 x 和 y 相加并返回结果的函数来比较 Solidity 和 Assembly 之间的 gas 成本。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract AssemblyExample {

    function addAssembly(uint x, uint y) public pure returns (uint) {
        assembly {
            let result := add(x, y)
            mstore(0x0, result)
            return(0x0, 32)
        }
    }

    function addSolidity(uint x, uint y) public pure returns (uint) {
        return x + y;
    }

}

img

Solidity 中两种方式实现 Assembly

  • 1、 内联汇编:也可以在 Solidity 代码中使用。
  • 2、 独立程序集:无需编写 Solidity 代码即可使用。

怎么使用 Assembly?

正如上面的例子那样,汇编代码运行在assembly { ...}汇编块中的。

汇编代码是使用YUL语言来编写的!

内联汇编块不共享命名空间,即不能在一个汇编块调用另一个汇编块中定义的变量。

assembly {
   // some assembly code here
}

img

以下是一个简单的示例,函数接受两个参数,并将它们的和作为返回值,看看使用 Assembly是怎么实现的?了解它们在 EVM的工作方式。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract AssemblyExample {

   function addition(uint x, uint y) public pure returns (uint) {

    assembly {

        //声明一个 result 变量,并将 x,y之和赋值给它
        let result := add(x, y)   // x + y

        //使用 mstore 操作码将 result存在 memory 中,地址是 0x0
        mstore(0x0, result)       // store result in memory

        //返回 32 字节的 memory 地址
        return(0x0, 32)          

    }
}

}

img

数据存储

让我们来看看一个简单的例子。我们将数据存放在storage(存储)中,然后再去调用它。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract StorageDataExample {

    function setData(uint256 newValue) public {
        assembly {
            sstore(0, newValue)
        }
    }

    function getData() public view returns(uint256) {
        assembly {
            let v := sload(0)
            mstore(0x80, v)
            return(0x80, 32)
        }
    }
}

img

setData函数使用了sstore操作码将变量newValue写入storage(存储)中。

getData函数先是用了sload操作码来加载storage(存储)中的数据,它并不能从storage中直接返回。所以才需要mstore操作码将其写入memory(内存)中,最后我们返回引用memory(内存)中存放数据的地址和 32 字节长度的数据。

https://docs.soliditylang.org/en/latest/yul.html

指令解释
stop()-F停止执行,与return(0, 0)相同
add(x, y)Fx + y
sub(x, y)Fx - y
mul(x, y)Fx * y
div(x, y)Fx / y 或 如果 y == 0,则为 0
sdiv(x, y)Fx / y,对于有符号的二进制补数,如果 y == 0,则为 0
mod(x, y)Fx % y, 如果 y == 0,则为 0
smod(x, y)Fx % y, 对于有符号的二进制补数, 如果 y == 0,则为 0
exp(x, y)Fx的y次方
not(x)Fx的位 “非”(x的每一个位都被否定)
lt(x, y)F如果 x < y,则为1,否则为0
gt(x, y)F如果 x > y,则为1,否则为0
slt(x, y)F如果 x < y,则为1,否则为0,适用于有符号的二进制数
sgt(x, y)F如果 x > y,则为1,否则为0,适用于有符号的二进制补数
eq(x, y)F如果 x == y,则为1,否则为0
iszero(x)F如果 x == 0,则为1,否则为0
and(x, y)Fx 和 y 的按位 “与”
or(x, y)Fx 和 y 的按位 “或”
xor(x, y)Fx 和 y 的按位 “异或”
byte(n, x)Fx的第n个字节,其中最重要的字节是第0个字节
shl(x, y)C将 y 逻辑左移 x 位
shr(x, y)C将 y 逻辑右移 x 位
sar(x, y)C将 y 算术右移 x 位
addmod(x, y, m)F(x + y) % m,采用任意精度算术,如果m == 0则为0
mulmod(x, y, m)F(x * y) % m,采用任意精度算术,如果m == 0则为0
signextend(i, x)F从第 (i*8+7) 位开始进行符号扩展,从最低符号位开始计算
keccak256(p, n)Fkeccak(mem[p…(p+n)))
pc()F代码中的当前位置
pop(x)-F丢弃值 x
mload(p)Fmem[p…(p+32))
mstore(p, v)-Fmem[p…(p+32)) := v
mstore8(p, v)-Fmem[p] := v & 0xff ((只修改了一个字节))
sload(p)Fstorage[p]
sstore(p, v)-Fstorage[p] := v
msize()F内存的大小,即最大的访问内存索引
gas()F仍可以执行的气体值
address()F当前合约/执行环境的地址
balance(a)F地址为A的余额,以wei为单位
selfbalance()I相当于balance(address()),但更便宜
caller()F消息调用者(不包括 delegatecall 调用)。
callvalue()F与当前调用一起发送的wei的数量
calldataload(p)F从位置p开始的调用数据(32字节)
calldatasize()F调用数据的大小,以字节为单位
calldatacopy(t, f, s)-F从位置f的calldata复制s字节到位置t的内存中
codesize()F当前合约/执行环境的代码大小
codecopy(t, f, s)-F从位置f的code中复制s字节到位置t的内存中
extcodesize(a)F地址为a的代码的大小
extcodecopy(a, t, f, s)-F像codecopy(t, f, s)一样,但在地址a处取代码
returndatasize()B最后返回数据的大小
returndatacopy(t, f, s)-B从位置f的returndata复制s字节到位置t的内存中
extcodehash(a)C地址a的代码哈希值
create(v, p, n)F用代码mem[p…(p+n))创建新的合约,发送v数量的wei并返回新地址; 错误时返回0
create2(v, p, n, s)C在keccak256(0xff . this . s . keccak256(mem[p…(p+n)))地址处 创建代码为mem[p…(p+n)]的新合约 并发送v 数量个wei和返回新地址, 其中 0xff 是一个1字节的值, this 是当前合约的地址, 是一个20字节的值, s 是一个256位的大端的值; 错误时返回0
call(g, a, v, in, insize, out, outsize)F调用地址 a 上的合约,以 mem[in..(in+insize)) 作为输入 一并发送 g 数量的 gas 和 v 数量的 wei, 以 mem[out..(out+outsize)) 作为输出空间。 若错误,返回 0 (比如,gas 用光) 若成功,返回 1
callcode(g, a, v, in, insize, out, outsize)F相当于 call 但仅仅使用地址 a 上的代码, 执行时留在当前合约的上下文当中
delegatecall(g, a, in, insize, out, outsize)H相当于 callcode, 但同时保留 callercallvalue
staticcall(g, a, in, insize, out, outsize)B相当于 call(g, a, 0, in, insize, out, outsize) 但不允许状态变量的修改
return(p, s)-F终止执行,返回 mem[p..(p+s)) 上的数据
revert(p, s)-B终止执行,恢复状态变更,返回 mem[p..(p+s)) 上的数据
selfdestruct(a)-F终止执行,销毁当前合约,并且将余额发送到地址 a
invalid()-F以无效指令终止执行
log0(p, s)-F用 mem[p..(p+s)] 上的数据产生日志,但没有 topic
log1(p, s, t1)-F用 mem[p..(p+s)] 上的数据和 topic t1 产生日志
log2(p, s, t1, t2)-F用 mem[p..(p+s)] 上的数据和 topic t1,t2 产生日志
log3(p, s, t1, t2, t3)-F用 mem[p..(p+s)] 上的数据和 topic t1,t2,t3 产生日志
log4(p, s, t1, t2, t3, t4)-F用 mem[p..(p+s)] 上的数据和 topic t1,t2,t3,t4 产生日志
chainid()I执行链的ID(EIP-1344)
basefee()L当前区块的基本费用(EIP-3198和EIP-1559)
origin()F交易发送者
gasprice()F交易的气体价格n
blockhash(b)F区块编号b的哈希值—只针对最近的256个区块,不包括当前区块。
coinbase()F目前的挖矿的受益者
timestamp()F自 epoch 开始的,当前块的时间戳,以秒为单位
number()F当前区块号
difficulty()F当前区块的难度
gaslimit()F当前区块的区块 gas 限制