Smart Contract
研究对象————Etheremu
Etheremu基础知识
账户
- 外部账户(EOA)
- 外部账户是由人创建的,可以存储以太币,是由公钥和私钥控制的账户。每个外部账户拥有一对公私钥,这对密钥用于签署交易,它的地址由公钥决定。外部账户不能包含以太坊虚拟机(EVM)代码。
- 一个外部账户有以下特性:
- 拥有一定的Ether
- 可以发送交易,由私钥控制
- 没有相关联的代码
- 合约账户
- 合约账户是由外部账户创建的账户,包含合约代码。合约账户的地址是由合约创建时合约创建者的地址,以及该地址发出的交易共同计算得出的。
- 一个合约账户有以下特性
- 拥有一定的Ether
- 有关联代码,代码通过交易或其他合约发来的调用激活
- 当合约被执行时,只能操作合约账户的特定存储
- 在Etheremu中,这两种账户统称为“状态对象”,其中外部账户存储以太币余额状态,而合约账户除了余额还有智能合约及其变量的状态。通过交易的执行,这些状态对象发生变化,而 Merkle 树用于索引和验证状态对象的更新。一个以太坊的账户包含 4 个部分
- nonce: 已执行交易总量
- balance: 帐持币数量
- storageRoot: 存储区哈希值
- codeHash: 代码区哈希值
- 两个外部账户之间的交易只是一个价值转移;而外部账户和合约账户之间的交易会激活合约账户的代码,允许进行各种操作
交易
- 交易指的是外部账户发送到另一账户的的消息的签名数据包
- 交易内容
- from: 交易发送者地址
- to: 交易接收者地址,如果为空代表创建或调用智能合约
- value: 转移的以太币数量
- data: 数据字段,如果存在,说明是一个创建或调用智能合约的交易
- gaslimit: 交易允许消耗的最大gas数量
- gasprice: 愿意发送给gas矿工的单价
- nonce: 区分同一账户的不容交易的标记
- hash: 以上信息生成的散列值
- r,s,v: 签名信息
- 交易类型:
- 执行转账的交易
- 创建智能合约的交易
- 调用智能合约的交易
RPC
- JSON-RPC是一种无状态、轻量级的远程过程调用(RPC)协议。它定义了几种数据结构及处理规则。用于实现软件应用程序与Etheremu区块链的交互
转账
- 操作过程:
- 生成一个交易,使用私钥签名
- 被签名的交易被广播到P2P网络
- 矿工将交易包含在一个块中
- 确认资金转账
燃料(Gas)
- 需要设置Gas的原因: 处理停机问题(无限循环)
- Gas limit: 用户单次交易的gas上限
- Gas price: Gas的当前单价,在交易前由用户设置,以Wei为单位
- 交易费用: Gas*Gas_price
- Gas消耗:
- 对于一般交易,消耗为21000
- 对于智能合约,取决于消耗的资源————执行的命令和使用的存储
EVM
- 每个Etheremu节点都包含一个虚拟机,该虚拟机被称为EVM,发挥执行智能合约代码和更改并广播全局状态的作用
- 特性:
- 图灵完备性(存在Gas限制)
- 无浮点数
- 无系统时钟
- 核心设计目标:
- 确定性: 保证相同的输入必定有相同的输出
- 隔离性: 合约在沙盒环境中运行,不直接访问主机系统
- 可终止性: 通过Gas限制执行步骤
- 结构:
- 基于堆栈
- 注: 栈式架构特点
- 所有计算依赖操作数栈
- 没有通用寄存器
- 指令隐式操作栈
- 注: 栈式架构特点
- 基于堆栈
- 内存模型:
- 栈
- 结构
- 2字节,最深1024层
- 易失性
- 结构
- 内存
- 结构
- 按字寻址的字节数组,可动态扩展
- 易失性
- 操作指令
mload(offset): 从内存偏移量处读32字节mstore(offset,value): 将32字节value写入偏移量offset处
- Gas成本: 初始免费,扩容时按每32字节支付Gas
- 结构
- 存储
- 结构
- 每个合约有独立的持久化键值存储
- 映射规则: 2^256个键,每个键对应一个32字节的值
- 特性
- 持久化: 数据永久写入区块链状态
- 高Gas成本: 写入消耗成千乃至上万Gas
- 操作指令
sstore(key,value): 从栈上依次弹出value和key,将value存入存储中key对应的槽位sload(key): 从栈顶弹出key,将存储中key对应的槽位的数据压入栈
- 结构
- 栈
代币合约
ERC-20代币合约
- ERC-20是一种通用的智能合约规范,特点是每一个代币都和其他代币完全相等。它是资产通证化的最广泛使用标准
- 包含API方法和事件
- totalSupply: 定义token总供应量
- balancdOf: 返回钱包地址包含的token余额
- transfer: 从总供应中转移一定数量token并发给用户
- transferFrom: 在用户之间传输token
- approve: 验证是否允许在考虑总供应量的情况下分配一定的token
- allowance: 检查是否有余额向另一个账户发送token
Uniswap
- Uniswap是一个完成不同代币间的交易的自动化流动协议
- 注:自动化流动协议定义
- 自动化流动性协议是一种利用预定义的数学公式(如恒定乘积公式)和部署在区块链上的智能合约,自动管理用户贡献的资产池(流动性池),并为用户提供无需许可、去中心化、自动定价和执行的代币交换服务的系统。它完全消除了对传统订单簿和专业做市商的依赖,通过算法和社区提供的流动性实现市场功能。
- 每个(或对)Uniswap智能合约管理着一个由两个ERC-20代币储备组成的流动池
- 任何人都可以成为池的流动性提供者(LP),即存入基础代币来换取池代币
- 在池中维持价格:套利
- 货币对充当市商的角色,根据恒定乘积公式提供替换服务
- 恒定乘积公式可以简单地表示为$x * y = k$,说明交易不能改变一对储备余额的乘积
- $k$通常被称为不变量。这个公式对规模较大的交易的执行速度比小的要慢得多
- 在实践中Uniswap对交易收取0.30%的费用,这笔费用被存入储备中
ERC-777代币合约
- 被ERC-20类似,ERC-777也是一种可替换代币标准,交易时允许更复杂的交互
- 它的最重要功能是接收hook
Etheremu安全漏洞和攻击方式
重进入攻击
- 核心漏洞:“先提款后记账”
- 具体实现:在提款函数(withdraw)中递归调用,在记录的存款变化前反复提取存款
- 防御方式:
- 检查-效果-交互模式: 按照执行检查、改变状态变量、执行与其他合同的交互的顺序运行
- 使用修饰符锁定(互斥锁): 即设置一个标识符,当发生与其他合同交互时设置标识符,标识符重置1前无法再进行交互
调用与委托调用攻击
基本概念:调用与委托调用
- 调用: 调用另一个智能合约中的函数
- 委托调用: 执行来自另一个智能合约的函数,使用调用者的存储和上下文
UUPS(通用可升级代理标准)
-
架构: 代理合约拆分
- 示意图
- 代理合约(Proxy)
- 永久储存所有状态变量
- 持有逻辑合约地址
- 通过
fallback函数将所有调用用delegatecall转发给逻辑合约
- 逻辑合约(Logic)
- 包含实际业务代码
- 无状态
- 可被替换(升级)
UUPS漏洞: 未初始化
- 如果UUPS合同未初始化,那么攻击者可以调用
initialize()函数,实现“攻击者成为所有者” - 攻击步骤
- 攻击者成为所有者
- 部署恶意合约
- 劫持升级过程
- 执行恶意代码