把状态保存、业务入口和公共工具分开,是阅读题目和补全合约的起点。本篇保留不同版本的学习示例,不把它们拼成一个工程。
本文由个人备赛旧笔记整理,整理日期为 2026-11-03。命令、输出和测试记录属于当时的实验环境;本文未重新搭建整套环境验证。
阅读前先确认
链上历史可追溯不代表状态变量不能修改。extcodesize 也不能作为完整的可信身份判断。示例中缺少的外部接口和 Ownable 等依赖需要单独准备。
存储合约设计
存储合约设计
基本要求
数据持久化
- 数据(永存概念) 存储在区块链
- 仅可修改状态不可篡改
状态变量存储
uint256 public data; // 简单数值
mapping(address => uint) balances; // 映射结构
string[] public records; // 动态数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| - Gas成本考虑
- 邮费昂贵
基本结构
- 存储层 - 业务逻辑层 - 权限控制层
```solidity pragma solidity ^0.4.25;
contract SimpleStorage { // 合约所有者 address public owner;
// 存储的数据 uint256 private storedData;
// 修改数据的事件日志 event DataStored(uint256 newValue, address indexed setBy);
// 构造函数,设置合约部署者为所有者 constructor() public { owner = msg.sender; }
// 权限修饰符:仅所有者可调用 modifier onlyOwner() { require(msg.sender == owner, "Only owner can call this function"); _; }
// 存储数据函数(仅所有者) function set(uint256 data) public onlyOwner { storedData = data; emit DataStored(data, msg.sender); }
// 读取数据函数(公开) function get() public view returns (uint256) { return storedData; }
// 转移所有权(仅所有者) function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0), "Invalid address"); owner = newOwner; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| ## 接口合约设计
### 接口合约设计
接口合约是只包含函数声明而不包含实现的抽象合约,用于定义合约之间的交互标准。
设计原则:
- 强制标准化:过接口约束外部合约的调用规范,例如代币协议(ERC20/ERC721)必须实现标准函数。 - 无状态性:接口内禁止定义状态变量或构造函数,仅声明函数签名。 - 全外部可见性:**外部可见性**:所有函数必须为 `external`;实现合约中可以用 public 函数实现对应的 external 接口。
- 明确职责分离 - 保持接口最小化
---
接口类型:
- 代币标准接口 - Defi协议接口 - Oracle 预言机接口
- 错误处理接口 - 跨链桥接口 - 多继承接口 - 治理接口 - 实用工具接口 - 跨链桥接口
接口代码示例
```solidity // 支持多继承的Difi组合接口 interface IAvancedDefi is IERC20, IUniswapV2Pair { // 闪电贷功能(代币和NFT混合抵押) function flashLoan( address asset, uint256 amount, bytes calldata data ) external payable returns (bool);
// 跨链调用扩展 (需要预言机) function crossChainCall( uint256 targetChainId, address targetContract, bytes calldata payload ) external payable; }
|
ERC20标准合约
1 2 3 4 5 6 7 8 9 10 11
| // SPDX-License-Identifier: MIT pragma solidity ^0.8.0;
// ERC20标准合约示例 interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address recipient, uint256 amount) external returns(bool);
}
|
接口伪装攻击防御:
在实现合约中验证调用者接口的完整性:
1 2 3 4 5 6 7
| require(
IInterface(target).supportsInterface(type(IStandard).interfaceId),
"Invalid interface"
);
|
工具合约解析
工具合约解析
WeBASE-Front 工具箱中自带有工具合约
以下是代码解析:
Address
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| pragma solidity ^0.4.24;
library Address {
// 检查地址是否为合约 function isContract(address addr) internal view returns(bool) { uint256 size; assembly { size := extcodesize(addr) } // 这里使用了 assembly 对 EVM 进行字节码级操作 // extcodesize是 EVM 内置函数,返回指定地址的合约代码大小 // 如果 size > 0 那说明这个地址的确是个合约 return size > 0; }
function isEmptyAddress(address addr) internal pure returns(bool){ return addr == address(0); // 一目了然 检查合约是不是空地址 } }
|
注意一点,使用这个函数时,在构造函数中调用会返回false,因为合约还未部署到链上。
LibString 这个library 有 690行 太长了,只挑几个解析
1 2 3 4 5 6 7
| function bytesToString(bytes memory _bytes) internal pure returns (string memory){ bytes memory bytesArray = new bytes(_bytes.length); for (uint256 i; i < _bytes.length; i++) { bytesArray[i] = _bytes[i]; } return string(_bytes); // ❌ 这里应该是return string(bytesArray); }
|
有点难绷了,为啥第一个函数就出现了错误,我有点不信任这个工具合约了
fix
去除了 bytesToString() 中不必要的部分
修正了 bytes32ToString() 并对逻辑进行重写
原代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function bytesToString(bytes memory _bytes) internal pure returns (string memory){ bytes memory bytesArray = new bytes(_bytes.length); // 无用的复制 for (uint256 i; i < _bytes.length; i++) { bytesArray[i] = _bytes[i]; } return string(_bytes); } function bytes32ToString(bytes32 _bytes32) internal pure returns(string memory){ bytes memory bytesString = new bytes(32); uint charCount = 0 ; for(uint i = 0 ; i<32;i++){ byte char = byte(bytes32(uint(_bytes32) *2 **(8*i))); //错误的位操作** (8 * i))) if(char !=0){ bytesString[charCount] = char; charCount++; } } bytes memory bytesStringTrimmed = new bytes(charCount); for(uint j=0;j<charCount;j++){ bytesStringTrimmed[j]=bytesString[j]; } return string(bytesStringTrimmed); }
|
修正代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function bytesToString(bytes memory _bytes) internal pure returns (string memory){ return string(_bytes); } function bytes32ToString(bytes32 _bytes32) public pure returns (string) { uint8 length; while (length < 32 && _bytes32[length] != 0) { length++; } bytes memory bytesString = new bytes(length); for (uint i = 0; i < length; i++) { bytesString[i] = _bytes32[i]; } return string(bytesString); }
|
已进行的测试:
1 2 3 4 5 6 7
| function testLetterA() public pure returns (string result, string correct, bool isEqual) { bytes32 input = bytes32(uint(65)); // 'A' result = bytes32ToString(input); correct = bytes32ToStringCorrect(input); isEqual = keccak256(result) == keccak256(correct); }
|
output:
核对资料
接口检测只说明对方声称支持某个接口,不能替代授权检查;原笔记的“接口伪装攻击防御”片段仅可作为接口探测思路。
复现时的自查
- 记录自己实际使用的版本、配置和缺失依赖。
- 对照本篇关键步骤,保存命令或调用结果,并解释结果代表什么。
- 对失败或与原记录不一致的情况保留错误信息,不把历史输出当成自己的验证结果。
返回系列导读 · 上一篇 · 下一篇