remix编译运行solidity3
calldata相关的问题
这两天学习了一些小合约,尝试了calldata,但还没有用过storage和memory。在deployed contracts中,callbyinterface的下方有calldata,点击copy calldata to clipboard,就得到了0x469883d10000000000000000000000003328358128832a260c76a4141e19e2a943cd4b6d。但我不知道这个calldata是什么意思
下面我给你一个专业、体系化、逐字段拆解的解释,让你完全理解:
- 什么是 calldata
- 为什么 Remix 会生成这串十六进制
- 这串 calldata 每一部分代表什么
- 什么是“4 字节函数选择器 + ABI 编码参数”
你未来做 Auditor 必须吃透这一点。
一、你看到的这串是什么?
你复制到的这段十六进制:
1 | |
这就是:
传入给函数 callByInterface(address) 的 ABI-encoded calldata(函数选择器 + 参数编码)
也就是说:
这是 EVM 在进行一次跨合约调用时“实际发送给合约的原始字节消息”。
Solidity(以及 Remix)把它用十六进制(hex)表示出来。
二、为什么会有这段 calldata?
当你按下 “callByInterface” 按钮时,Remix 必须以 “低级格式” 把真正的数据发送给合约。
你看到的就是:
接口函数调用的底层表现
这就是“你使用 interface 调用,但底层仍然是以 calldata 字节形式发送 CALL”。
这正是为什么 interface 是“高层调用”,但本质仍是低级 CALL。
三、我们来逐字段拆解你的 calldata
你这段 calldata:
1 | |
我们拆成两部分:
第一部分(前 4 字节):函数选择器(function selector)
1 | |
这是:
1 | |
EVM 用这 4 个字节决定“我要调用哪个函数”。
你可以自己验证:
如果你在 Remix console 输入:
1 | |
会得到:
1 | |
前 4 字节就是:
1 | |
第二部分(32 字节):参数 address 的 ABI 编码
Solidity ABI 规定:
- 所有参数都必须占 32 字节(256 bit)
- address 20 字节 → 左侧补 12 字节 0
你传入的地址是:
1 | |
ABI 编码为:
1 | |
组合起来就是完整的 calldata:
1 | |
你现在完全能看懂了。
四、为什么 storage / memory / calldata 重要?
这里你第一次看到“真实的 calldata”。
在 Solidity 中:
| 名称 | 存储位置 | 生命周期 | 用途 |
| storage | 永久存储(链上) | 交易之间持久存在 | 状态变量 |
| memory | 临时(函数执行期间) | 调用结束后释放 | 局部变量、数组 |
| calldata | 外部调用输入数据 | 只读、不可修改 | 函数参数(external) |
你看到的十六进制就是:
callByInterface(address) 的 calldata 区域内容
这些数据从外部发送到合约,被 EVM 加载到 calldata。
五、你现在应该掌握的核心概念(Auditor 必备)
你在学习合约早期阶段,看见 calldata 十六进制是非常好的信号,说明你开始深入 EVM 层。
请记住这几个重点:
- 所有跨合约调用最终都以 calldata 字节方式在 EVM 中执行
- 函数选择器是 keccak256 的前 4 字节
- 所有参数都编码成 32 字节(address 也要 padding)
- interface 调用只是语法糖,底层仍是这种字节消息
- call 调用需要手动写 abi.encode → 就是生成这样的字节
当你看到 calldata,你就能“读懂”调用发生了什么,这就是未来审计的核心能力之一。
mapping相关的问题
下面我给你一份专业、清晰、完全从 Solidity 设计哲学 + 业务场景 + 安全需求出发的解释,让你彻底理解:
- 为什么 Solidity 需要 mapping
- 它为什么类似 Python 字典但又完全不同
- 为什么智能合约大量使用“单映射”和“双重映射”
- 它的底层结构是什么
这部分是你成为 Junior Auditor / Developer 的基础核心能力。
一、mapping 为什么看起来像 Python 字典?
从表面上看:
1 | |
就像 Python:
1 | |
你看到的“像字典”是正常反应,因为两者都表达:
用某个 key 查找某个 value 的数据结构。
但是重点来了:
mapping 的行为、限制、底层结构与 Python 字典完全不同。
mapping 不是字典,而是 区块链上为“定址查找”专门设计的哈希结构。
二、为什么 Solidity 需要 mapping?(核心原因)
因为链上有几个独特需求:
1)区块链无法遍历状态 —— mapping 完美符合这一点
链上状态必须:
- 明确定址(通过 key 直接找到数据)
- 无法遍历(增加安全性和可扩展性)
- 永远以 O(1) 查找
mapping 恰好满足:
1 | |
没有数组那种越界风险,也不会出现 Python 字典那种 rehash 行为。
mapping = 区块链上最安全、可预测、可扩展的键值结构。
2)mapping 不可遍历(by design),非常重要
新手会觉得这是缺点:
“为什么 mapping 不能遍历?好奇怪。”
但这是区块链的必然设计:
- 遍历 = 线性扫描 = 随着用户数量增长,gas 会爆炸
- 链上合约必须保证 gas 可控
- 所以 EVM 禁止“自动列出所有 key”
这就是为什么:
mapping 是无边界结构,理论上可以放无限用户,而 gas 始终稳定。
3)mapping 更安全
如果链上允许地址遍历,会导致严重隐私泄漏与攻击可能:
- 所有用户地址
- 所有用户余额
- 所有用户状态
- 全部可被恶意代码扫描
mapping 不可遍历 → 避免这种风险。
4)mapping 是 EVM 原生数据结构(gas 最低)
mapping 的存储模型是:
1 | |
这是 EVM 原生支持的模式,读取和写入都极快。
相比之下:
- array[0], array[1] 需要边界检查
- struct 需要固定 slot 排布
- dictionary(Python)有大量 runtime 逻辑
mapping 是 EVM 为智能合约量身定做的键值结构。
三、为什么要用 mapping 来做“一重映射”和“双重映射”?
初学者一般在 NFT、Token、权限管理时会看到这种写法:
1 | |
你可能会想:
“为什么这么复杂?真的需要吗?”
答案是:
链上业务天然需要映射关系。
我们看常见场景。
1)Token 余额(ERC20)
1 | |
每个用户都有余额,这就是典型的“一对一关系”:
1 | |
2)授权关系(ERC20 allowance)
1 | |
意思是:
1 | |
这是“双重映射”。
为什么不是数组?
因为:
- 每个用户可能授权给多个 DEX、钱包
- 关系不可能预先固定
- 也无法遍历所有用户
- 必须 O(1) 查找
ERC20、ERC721、ERC1155 都依赖双重映射。
3)关注关系 / 好友关系(你做过的小合约)
1 | |
表示:
1 | |
社交、GameFi、DAO 里非常常见。
4)访问权限(AccessControl)
1 | |
安全控制必须做到:
- O(1) 判断权限
- 不可被遍历攻击
- 不可被猜测结构
mapping 是最安全方法。
四、mapping 与 Python 字典的本质区别(审计特别重要)
| 项目 | Python dict | Solidity mapping |
| 存储位置 | 内存 | 链上永久存储 |
| 是否可遍历 | 是 | 否 |
| 是否能删除 key | 可以删除 | delete 不会删除 key,只置 0 |
| 是否有长度 | 有 len() | 无 length |
| 是否可判断 key 是否存在 | 可以 | 无法判断(默认值 = 0) |
| hash 是否可见 | 不可见 | slot = keccak(key, slot) |
| gas 是否增长 | 不相关 | 必须可预期稳定 |
结论:
mapping 不是“字典”,而是“不可遍历的链上键值数据库”。
五、你未来会遇到三类常见 mapping 模式(学习重点)
1)简单映射
1 | |
2)双重映射
1 | |
3)结构体映射
1 | |
这是最常见的 Web3 数据模式。
六、你什么时候需要 array?什么时候 mapping?
| 数据结构 | 用途 |
| array | 有顺序、要遍历、高维度数据结构 |
| mapping | 用户数量不定、无法遍历、安全要求高、O(1) 查找 |
例子:
- NFT 全部 tokenId:array
- 用户余额:mapping
- DAO 成员列表:array + mapping(组合)
- 谁授权了谁:mapping
mapping 是链上业务的核心。
七、总结(面试级别回答)
你可以这样总结:
Solidity 的 mapping 不是 Python 字典,而是链上为不可遍历、O(1) 查找设计的特殊键值存储结构。它无法遍历、没有长度、所有 key 都有默认值,因此非常安全、节省 gas,适合表示用户余额、授权、权限、关注关系等不可预知规模的数据。双重映射允许表达 owner→spender、user→user 的复杂关系,是智能合约中最常见的数据结构之一。
如果你愿意,我可以继续教你:
- mapping 在 EVM 的底层存储结构(slot 计算)
- 写一个双映射的练习合约
- 写一个 mapping + array 的组合模式(最常见)
- 编写一个访问控制系统(AccessControl)
你想继续哪个方向?
白名单钱包
whitelistWallet.sol
1 | |
remix中测试
第一步,点copy account,复制eth地址0x617F2E2fD72FD9D5503197092aC168c91465E7f2
第二步,把eth地址 0x617F2E2fD72FD9D5503197092aC168c91465E7f2 添加到白名单,如下图所示

第三步,试下transfer to功能
上图的意思,就是往这个地址转账
第四步,balance 查看这个地址有多少存款
第五步,试下非白名单地址
1 | |
为什么 Solidity 里叫 modifier(修饰器)?它到底类似什么?
因为你会 Python / JavaScript,所以我用它们的语法帮你建立“类比概念”,让你 100% 理解 modifier 的作用与设计哲学。
一句话本质总结(非常准确)
Solidity 的 modifier 就相当于:“在函数执行前后,自动插入一段通用逻辑的语法糖”。
它不是函数、不是结构体、不是类,而是:
一个可以包裹函数的“逻辑包装器(wrapper)”。
你可以把它理解为下面三者的混合体:
- Python 的装饰器(@decorator)
- Java 的 AOP(面向切面编程)
- Web 中间件(middleware)
但又不像它们那么自由,范围更窄,只用于函数前置/后置检查。
二、你理解不了,是因为 JS / Python 没有完全等价的词
Solidity 的 modifier 是 Solidity 语言特有的语法结构,其他语言确实没有完全对应的。
为了帮助你理解,我用三层比喻:
三、比喻 1:modifier = “门卫”
你进公司需要刷卡。
函数执行之前,要先判断“你有没有权限”。
例如:
1 | |
这是门卫逻辑。_ 表示:检查通过后,允许你进入(继续执行函数主体)。
四、比喻 2:modifier = “函数入口处插入一段代码”
有这样一个函数:
1 | |
你想让所有 sensitive 函数都“先检查权限”,那你得写:
1 | |
每个地方都写一遍,可读性非常差。
Solidity 让你把通用逻辑封装成 modifier:
1 | |
然后你可以这样用:
1 | |
你看到的 onlyOwner 就像对函数加了一个“前置检查”。
五、比喻 3:modifier = Python 装饰器 @decorator
你可能见过:
1 | |
这里的 @check_login 就是在“修饰”函数。
Solidity 的 modifier 作用完全一样,只是写法不同。
六、比喻 4:modifier = Web 中间件(Middleware)
比如 Express.js:
1 | |
这里 authMiddleware 会在请求进入路由之前执行身份验证。
Solidity 的 modifier 作用一样:
1 | |
onlyOwner 就像请求进入 adminTask 前必须经过一层“检查”。
七、为什么叫 modifier?
因为它字面含义就是 “修改函数的执行方式”。
- 它不执行真正的业务逻辑
- 它只在函数执行前(或后)插入一些检查
- 它修饰了这个函数的行为
所以叫 修饰器(modifier)。
八、你要理解的关键点(专业解释)
modifier 的结构:
1 | |
_ 表示:
把“函数主体”替换到这里。
也就是:
_之前的代码 = 函数前置逻辑_之后的代码 = 函数后置逻辑
绝大多数 modifier 只有前置逻辑(例如权限检查)。
九、最经典例子(你今天写的白名单)
你写了:
1 | |
翻译成自然语言:
“在执行 transferTo 之前,先做一个白名单检查。如果不在白名单,直接 revert;如果在白名单,继续执行。”
这完全符合“门卫”、“装饰器”、“中间件”的概念。
十、总结一句你能记住的话
modifier 本质上是一个“函数前置/后置检查模板”,它会把自己的代码插入到目标函数中,用来写权限控制、输入验证、状态检查等逻辑,是 Solidity 独有的轻量化 AOP 机制。
如果你愿意,我可以继续帮你:
A)写一个可视化示例:把 modifier 展开成编译后的完整函数
B)写 “多个 modifier 组合” 的例子
C)告诉你 modifier 使用不当会产生哪些安全漏洞
D)让你练习自己写几个小 modifier(非常有用)
你想继续哪一个?