Skip to main content

Solidity 初级面试题

以下是 1–12 题 的简要答案汇总,供快速回顾:


1. 私有、内部、公共和外部函数之间的区别?

可见性当前合约派生合约外部(合约/账户)备注
private仅当前合约
internal默认可见性
public自动生成 getter
external❌*❌*内部需用 this.f() 调用

2. view 和 pure 函数有什么区别?

  • view:可读取状态变量,但不能修改。
  • pure:既不读取也不修改状态,仅依赖参数或常量。

3. 1 ether 相当于多少个 wei,多少个 gwei?

  • 1 ether = 10¹⁸ wei
  • 1 ether = 10⁹ gwei

4. Solidity 0.8.0 版本对算术运算的重大变化?

默认启用算术溢出检查。加、减、乘、除、自增、自减等溢出时直接 revert(不再静默环绕)。需要旧版环绕行为需写在 unchecked { ... } 块中。


5. 实现 allowlist 用映射还是数组更好?

映射 (mapping(address => bool)) 更好:

  • 成员检查 O(1),数组需要遍历 O(n)
  • 增删方便且 gas 可预测
  • 若需遍历所有地址,可额外维护数组 + 映射组合。

6. 以太坊主要使用什么哈希函数?

Keccak‑256(常被误称为 SHA‑3)。


7. assert 和 require 有什么区别?

requireassert
用途输入验证、条件检查内部错误、不变式检查
失败后 Gas返还剩余 gas消耗所有剩余 gas
错误消息支持自定义字符串不支持
适用场景可预期的拒绝不应发生的 bug

8. 智能合约最大大小?

24,576 字节(24 KB),由 EIP‑170 规定。


9. 为什么 Solidity 废弃了 years 关键字?

因为 1 years 被固定为 365 天,无法处理闰年,容易误导时间计算。改用 365 days 显式表达。


10. Solidity 提供哪些关键字来测量时间?

时间单位:secondsminuteshoursdaysweeks
全局变量:block.timestamp(秒为单位的时间戳)。


11. 存储 -1 的 int256 变量十六进制如何表示?

0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

(64 个 f,所有位为 1)


12. uint256 最大值及获取方式?

最大值 = 2²⁵⁶ − 1 =

115792089237316195423570985008687907853269984665640564039457584007913129639935

获取:type(uint256).max(Solidity ≥0.8.0)

13. uint8、uint32、uint64、uint128、uint256 都是有效的 uint 大小,还有其他的吗?

。Solidity 支持任何以 8 为步长、从 8 到 256 位的无符号整数:
uint8, uint16, uint24, uint32, uint40, …, uint256
但开发中常用的是 uint8, uint16, uint32, uint64, uint128, uint256,其余较少见。


14. 为什么 Solidity 不支持浮点数运算?

因为以太坊要求所有节点计算结果完全一致(确定性)。浮点数标准(如 IEEE 754)在不同平台或编译器优化下可能产生微小差异,导致共识失败。Solidity 采用定点数(整数)和自定义小数位来模拟精度。


15. fallback 和 receive 之间有什么区别?

函数触发条件
receive()调用数据为空(msg.data.length == 0)且合约收到纯以太转账。
fallback()任何其他不匹配函数签名的情况(包括有数据的调用,或没有 receive 时的空数据转账)。

两者都需标记 external payable。如果合约只写了 fallback 而没有 receive,空数据转账仍会走 fallback


16. Solidity 中的修饰符 modifier 有什么作用?

代码复用和前置/后置检查。常用于:

  • 访问控制(如 onlyOwner
  • 输入验证
  • 重入锁(nonReentrant
  • 函数执行前后的统一操作

修饰符可以接收参数,并按顺序叠加使用。


17. 有哪些方式可以向智能合约中存入以太币?

  1. 直接调用 receive()fallback()(通过转账)
  2. 调用 payable 函数 并附带以太币
  3. 使用 transferaddress.transfer(amount),2300 gas,失败 revert)
  4. 使用 send(类似 transfer,但返回 bool,不推荐)
  5. 使用 call{value: amount}("")(推荐,灵活控制 gas)
  6. selfdestruct(强制向指定地址发送余额,非自愿存款)
  7. 挖矿奖励(仅对矿工地址)

18. Solidity 访问控制有哪些,有什么用?

常见模式:

  • onlyOwner:限制只有合约所有者调用(使用 msg.sender 检查)
  • 基于角色(如 OpenZeppelin AccessControl):DEFAULT_ADMIN_ROLEMINTER_ROLE
  • 时间锁TimelockController
  • 修饰符 + require 自定义逻辑

作用:防止未授权调用,提升安全性。


19. 运行以太坊独立验证节点所需的最小以太数量是多少?

32 ETH(以太坊 PoS 质押的最低要求)。只有质押 32 ETH 才能激活自己的验证节点(独立节点)。


20. 以太坊什么机制阻止了无限循环的永远运行?

Gas 机制。每笔交易有 gasLimit,每条 EVM 指令消耗 Gas。无限循环会持续消耗 Gas 直到用尽,交易失败并回滚,但不会永远运行。


21. 在 EIP-1559 之前,如何计算以太坊交易的美元成本?

公式:

交易成本 (USD) = gasPrice (Gwei) × gasUsed × 1e-9 × ETH/USD 价格

其中 gasPrice 由用户竞价(第一价格拍卖),gasUsed 由交易复杂度决定。


22. 上海升级后,每个区块的 gas 限制是多少?

没有硬性固定值,由验证者动态投票调整。当前主流值约为 30,000,000 gas(三千万)。上海升级本身没有改变 gas 限制的调整机制。


23. 在一个智能合约中调用另一个智能合约时可以转发多少 gas?

默认情况下,<address>.call{value: 0}("") 会转发当前剩余的所有 gas(除了少量内部消耗)。也可以手动指定 {gas: 50000} 限制。
最大不能超过交易剩余的 gas 减去调用开销。


24. ERC20 合约中的 transfer 和 transferFrom 有什么区别?

函数调用者移动谁的代币需要授权
transfer代币持有者本人调用者自己无需授权(直接操作自己的余额)
transferFrom第三方(或本人)from 地址转给 to需要 from 先批准额度给调用者

25. 在区块链上如何使用随机数?

区块链本身是确定性的,没有真正的随机数。常用方法:

  • 预言机随机数(如 Chainlink VRF)—— 推荐,安全可靠
  • 区块哈希 + 工作量证明(如 blockhash(block.number-1),但可被矿工操纵)
  • 承诺-揭示(Commit-Reveal)方案
  • RANDAO(以太坊信标链内置)

绝对避免:block.timestampblock.difficulty 等可被矿工影响的值做关键随机数。


26. 什么是检查-效果-交互模式(Checks-Effects-Interactions)?

一种防止重入攻击的编程模式:

  1. 检查(Checks):先用 require 验证条件。
  2. 效果(Effects):更新合约内部状态(如减少余额)。
  3. 交互(Interactions):最后进行外部调用(如转账)。

这个顺序确保在外部调用发起前,所有状态已修改,即使攻击者再入,也无法重复利用旧的余额。


27. create 和 create2 之间的区别?

特性createcreate2
地址计算keccak256(deployer_address, nonce)keccak256(0xff, deployer_address, salt, init_code_hash)
确定性依赖 nonce,不可预测(nonce 递增变化)完全确定性,只要参数相同,地址不变
主要用途普通部署反事实部署、工厂模式预测地址、无交易部署

28. 代理合约需要哪种特殊的 call 才能工作?

delegatecall
代理合约使用 delegatecall 将函数调用转发给逻辑合约,同时保留代理自己的存储(msg.sendermsg.value 也保留)。实现逻辑与存储分离。


29. tx.origin 和 msg.sender 有什么区别?

  • msg.sender直接调用者(可能是外部账户或另一个合约)。
  • tx.origin原始发起交易的外部账户(总是 EOA)。

示例:用户 A → 合约 B → 合约 C。
在合约 C 中,msg.sender 是合约 B,tx.origin 是用户 A。

安全警示:切勿使用 tx.origin 做身份验证,容易受钓鱼攻击。


30. abi.encode 和 abi.encodePacked 有什么区别?

  • abi.encode:将所有参数按 ABI 规范填充为 32 字节的倍数,末尾填充零,适合标准解码(abi.decode)。
  • abi.encodePacked:紧密打包,不填充,更省空间但不唯一,可能产生哈希碰撞(例如 encodePacked("a","bc")encodePacked("ab","c") 相同)。

使用场景:encodePacked 适合做 keccak256 哈希时节省 gas,但需注意碰撞风险。


31. 根据 Solidity 编程风格,函数应该如何排序?

推荐顺序(按可见性和类型分组):

  1. 构造函数 (constructor)
  2. receive 函数
  3. fallback 函数
  4. 外部函数 (external)
  5. 公共函数 (public)
  6. 内部函数 (internal)
  7. 私有函数 (private) 在每个组内可再按逻辑或字母顺序排列。

32. 根据 Solidity 编程风格,函数修饰符应该如何排序?

修饰符的顺序就是执行顺序(从左到右)。
例如:

function withdraw() public onlyOwner nonReentrant { ... }

先执行 onlyOwner,再执行 nonReentrant,最后执行函数体。
风格建议:将更通用的修饰符放前面,更具体的放后面


33. Solidity 中整数除法是不是遵循四舍五入?

不是。Solidity 整数除法向零截断floor 对于正数,ceil 对于负数,但通常只考虑无符号整数)。
例如:

  • 5 / 2 = 2(不是 2.5,也不是四舍五入的 3)
  • 7 / 3 = 2
    没有内置的四舍五入,需自己用公式 (x + y/2) / y 模拟。