智能合约设计

智能合约设计原理
- 更少的代码 — 严格管理存储 Storage 变量
- 严格的 For 循环
- 对输入做边界处理
- 处理所有可能出现的情况
- Stablecoin depeg
- Insolvent Liquidation
- 并行数据结构
- Consider if necessary
外部调用
重入攻击
- 某个状态/变量在代码执行后,但该状态/变量没有立即更新;
- 这个过时的状态,给了攻击者可乘之机
- 如何防止重入攻击
- 遵循 CEI
- Checks -> Effects -> Interactions
- 检查外部输入 -> 更新状态 -> 进行外部调用或交互
- 遵循 CEI
典型例子
- 被外部攻击递归
// SPDX-Licence-Identifier: MIT
pragma solidity ^0.8.20;
contract Vault{
error NativeTokenTransferError();
mapping(address => uint256) balances;
function deposit() payable external{
balances[msg.sender] = msg.value;
}
function withdraw external{
(bool sent, )= payable( .sender).call{value: balance[msg.sender]}("");
if(!sent) revert NativeTokenTransferError();
delete balances[msg.sender];
}
}
跨函数重入攻击
跨合约重入攻击
只读重入攻击
DEX 安全
- 价格操纵
- 漏洞原理
- 攻击关键步骤
跨链桥安全
- 源链必须可以 锁定/销毁 用户资产
- 目标链必须执行转移资产
- 验证器必须正确的监听、解析、传递消息
- 验证器私钥必须安全
NFT 安全
小技巧:在进行 摘要时,为了防止歧义,更建议使用 abi.encode
,而不使用 abi.encodePacked
区别
1. abi.encode
-
使用标准的ABI编码规则。
-
会对参数进行填充(padding)以使其成为32字节的倍数。
-
编码结果包括长度信息,并且每个参数都是32字节对齐的。
-
通常用于函数调用参数的编码,或者需要与其他合约交互时使用。
-
编码的结果是确定性的,并且可以安全地用于哈希,但由于填充的存在,可能会产生较长的字节序列。
2. abi.encodePacked
- 使用紧凑的编码方式(不进行填充)。
- 直接将参数紧密地打包在一起,没有填充,也没有长度信息(对于动态类型,如string、bytes,会直接将其字节序列打包,而不记录长度)。
- 编码结果更短,节省Gas。
- 但是,由于没有填充和长度信息,当参数包含动态类型时,可能会有歧义(比如两个string连在一起,无法知道第一个字符串在哪里结束,第二个从哪里开始)。
- 通常用于生成签名、哈希(如keccak256)等场景,因为可以节省空间,但需要确保不会出现歧义。