solidity
内存
Solidity 和 EVM 使用小端存储(Little Endian),数据的高位字节会被存储在内存的高地址处,而低位字节则被存储在内存低地址处.
// 0x12345678
[0x78 0x56 0x34 0x12]
内存对齐与填充
EVM中基本的存储单位是 slot, 以32字节对齐.
内存对齐是指数据类型在内存中的存储位置必须满足一定的字节对齐规则,通常是类型大小的倍数.
struct 中的成员类型尺寸不同且内存对齐要求不同(eg, uint256 和 uint8), Solidity 会为小尺寸的成员后面填充空白字节.这种填充行为会导致空间浪费,进而增加 Gas 消耗.
基于以上理论,将较大的数据类型放在前面可节约内存
// 33 bytes
struct Example {
uint256 largeData; // 32 bytes
uint8 smallData; // 1 byte
}
// 64 bytes
struct Example {
uint8 smallData; // 1 byte(填充31个空白字节)
uint256 largeData; // 32 bytes
}
存储状态
-
memory: 临时数据存储区. 主要用于存储临时变量,比如函数参数和局部变量.数据存在于函数调用期间,调用结束后会被销毁.
-
storage: 长期存储区. 主要用于存储合约的状态变量,这些变量的值会保存在区块链上,并且合约的生命周期内一直存在. 是最贵的存储类型,因为每次修改存储都会涉及到写入区块链的操作,需要消耗较多的 Gas.
storage 的 array 是动态数据类型(dynamic),其元素用哈希函数指定存储位置(eg keccak256(slot)),本身占据一个solt,存储array length,如下图所示:
-
stack: 主要用于存储局部变量. 这些变量通常用于操作简单的计算,并且是非常快速的. 栈上的操作不需要消耗 Gas,但通常只能存储非常小的数据.
-
calldata(在某些场景下): 只读数据存储区,用于存储外部调用传递的参数. 是只读的,不能修改数据,因此不能用于存储合约的状态变量.
常见类型的存储位置
- const: 编译时硬编码在程序的字节码中;
- immutable: 只初始化一次.存储在
storage中,操作消耗gas.
Event
Event 是日志机制,专门用于将合约中的信息记录到区块链的日志中,打包在区块中(logging data structure).
- 存储位置: 链上日志(LOG 指令),而不是合约的存储空间;
- 不会改变链上状态,仅用于链外程序(如前端或离线工具)监听和查询;
- 查询时更便宜,因为事件存储在日志数据结构中,不会占用合约存储.
Event参数:
- Indexed Parameters = Topics, searchable, allows us to filter events in the logs
- Non Indexed Parameters = Data, need decode
event RaffleEnter(address indexed player); // indexed -> allows us to filter events in the logs
Function
- external: 只能在合约的外部调用,外部调用包括以下两种方式:
- 其他合约调用
- 外部账户(EOA,Externally Owned Account)调用,例如通过
Metamask
- 可以使用
this关键字在合约内部间接调用,但会额外消耗GAS
- internal: 只能在当前合约及其继承的子合约中访问,不能被外部账户(EOA)或外部合约直接访问. 可视性类似于其他编程语言中的
protected访问控制
abstract contract Parent {
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal virtual;
}
contract Sun is Parent {
function fulfillRandomWords(
uint256 _requestId,
uint256[] calldata _randomWords
) internal override {}
}