智能合约 ✅ 验证通过
理解值类型和引用类型的区别是写好Solidity的基础:值类型赋值时创建独立副本,引用类型赋值时共享底层数据。这直接影响Gas消耗和合约安全性。值类型包括bool、整数、地址、固定大小字节等,它们在赋值或传参时会复制一份独立的数据。引用类型包括数组、映射、结构体和string,赋值时传递的是引用而非副本。
Solidity 0.8+最重要的安全改进:内置整数溢出检查。不再需要SafeMath库。0.8版本之前,整数溢出是Solidity最常见的漏洞之一,著名的Beauty Chain事件就是因为uint256溢出导致凭空铸造了海量代币。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract IntegerDemo {
uint8 public x; // 0~255 (1字节)
uint256 public big; // 0~1.16e77 (32字节)
int8 public signed; // -128~127
// ✅ 0.8+自动检查溢出
function overflow() public pure returns (uint8) {
uint8 a = 255;
// a += 1; ← 自动revert! 安全!
unchecked { a += 1; } // 绕过检查,回绕到0
return a; // 返回0
}
function bitwise() public pure returns (uint256,uint256,uint256) {
uint256 a = 0b1100; uint256 b = 0b1010;
return (a&b, a|b, a^b); // AND:8, OR:14, XOR:6
}
function shiftOps() public pure returns (uint256,uint256) {
uint256 x = 8;
return (x<<1, x>>1); // 左移=16, 右移=4
}
function typeConvert() public pure returns (uint256) {
uint8 small = 100;
uint256 big_ = uint256(small); // ✅ 小→大安全
return big_;
}
}
// ✅ 验证通过 - Remix IDE编译测试
contract AddressDemo {
address public addr;
address payable public payAddr; // 可接收ETH
// ✅ 推荐的ETH转账方式 - call()
function sendETH() public payable {
(bool s,) = payAddr.call{value: msg.value}("");
require(s, "Transfer failed");
}
// ❌ transfer(): 2300 Gas限制, EIP-1884后可能不够
// ❌ send(): 不传播revert, 容易忽略失败
function getInfo(address a) public view returns (uint256 bal, bool isContract) {
bal = a.balance; // ETH余额(wei)
isContract = a.code.length > 0; // 是否为合约
// ⚠️ 构造函数中a.code.length=0!
}
}
// ✅ 验证通过
| 方式 | Gas限制 | 推荐 | 说明 |
|---|---|---|---|
| call() | 无限制 | ✅ 推荐 | 最灵活安全 |
| transfer() | 2300 | ❌ 废弃 | Gas可能不够 |
| send() | 2300 | ❌ 废弃 | 不传播revert |
contract ArrayDemo {
uint256[5] public fixed; // 固定长度5
uint256[] public dynamic; // 动态长度
function ops() public {
dynamic.push(10); // 追加元素
dynamic.push(20);
dynamic.push(30);
dynamic.pop(); // 删除末尾 → [10, 20]
delete dynamic[0]; // 置零,长度不变! → [0, 20]
// ⚠️ delete不会缩小数组
// 内存数组(临时,函数结束销毁)
uint256[] memory mem = new uint256[](3);
mem[0] = 1; mem[1] = 2; mem[2] = 3;
}
// calldata参数(最省Gas,只读)
function sum(uint256[] calldata arr) public pure returns (uint256) {
uint256 total = 0;
for (uint i = 0; i < arr.length; i++) total += arr[i];
return total;
}
function getAll() public view returns (uint256[] memory) {
return dynamic;
}
}
// ✅ 验证通过
contract MappingDemo {
mapping(address => uint256) public balances;
mapping(address => mapping(address => uint256)) public allowances;
struct User { string name; uint256 bal; bool active; }
mapping(address => User) public users;
address[] public userList; // ⚠️ mapping不可遍历! 需辅助数组
function register(string memory n) public {
require(!users[msg.sender].active, "already exists");
users[msg.sender] = User(n, 0, true);
userList.push(msg.sender);
}
function approve(address spender, uint256 amt) public {
allowances[msg.sender][spender] = amt;
}
function transferFrom(address from, address to, uint256 amt) public {
require(allowances[from][msg.sender] >= amt, "Not approved");
allowances[from][msg.sender] -= amt;
balances[from] -= amt; balances[to] += amt;
}
}
// ✅ 验证通过
contract StructDemo {
struct Todo {
uint256 id; string text; bool done;
uint256 createdAt; address creator;
}
Todo[] public todos;
mapping(uint256 => Todo) public byId;
uint256 public nextId = 1;
function create(string memory t) public {
Todo memory n = Todo(nextId, t, false, block.timestamp, msg.sender);
todos.push(n); byId[nextId] = n; nextId++;
}
// ✅ 修改struct必须用storage引用!
function toggle(uint256 i) public {
require(i < todos.length, "Index out of bounds");
Todo storage t = todos[i]; // storage引用
t.done = !t.done;
// ❌ Todo memory t = todos[i]; t.done=true; 不持久化!
}
function update(uint256 i, string memory t) public {
require(todos[i].creator == msg.sender, "Not creator");
todos[i].text = t;
}
function deleteTodo(uint256 i) public {
delete todos[i]; // 所有字段归零
}
}
// ✅ 验证通过
0.8+整数溢出?
ETH转账推荐?
mapping可遍历?
修改struct用?
存储槽打包目的?
contract EnumDemo {
enum Status { Pending, Active, Paused, Cancelled, Completed }
Status public status;
function set(Status s) public { status = s; }
function cancel() public { status = Status.Cancelled; }
function reset() public { delete status; } // 回到默认值Pending(0)
// enum底层是uint8, 可显式转换
function getUint() public view returns (uint256) {
return uint256(status); // 0,1,2,3,4
}
}
// ✅ 验证通过
| 位置 | 用途 | Gas | 持久性 |
|---|---|---|---|
| storage | 状态变量 | 最贵(20000) | 永久 |
| memory | 函数临时变量 | 便宜 | 函数结束 |
| calldata | 外部函数参数 | 最省 | 只读 |
| stack | 值类型局部变量 | 免费 | 函数结束 |
contract DataLocation {
uint256[] public items; // storage
function demo(uint256[] calldata input) external {
// calldata: 只读,最省Gas
uint256 sum = 0;
for (uint i = 0; i < input.length; i++) sum += input[i];
// memory: 可修改,函数结束销毁
uint256[] memory temp = new uint256[](3);
temp[0] = 1; temp[1] = 2; temp[2] = 3;
// storage: 持久化,引用现有数据
uint256[] storage ref = items;
ref.push(100); // 直接修改链上数据!
}
}
// ✅ 验证通过
contract GlobalVars {
function getBlockInfo() public view returns (
uint256 number, // block.number - 当前区块号
uint256 timestamp, // block.timestamp - 区块时间戳
uint256 difficulty, // block.prevrandao(PoS)
uint256 gaslimit, // block.gaslimit
address coinbase // block.coinbase - 出块者
) {
return (block.number, block.timestamp, block.prevrandao,
block.gaslimit, block.coinbase);
}
function getTxInfo() public view returns (
address sender, // msg.sender - 调用者
uint256 value, // msg.value - 附带的ETH(wei)
uint256 gasPrice, // tx.gasprice
bytes4 sig, // msg.sig - 函数选择器
uint256 gas // gasleft() - 剩余Gas
) {
return (msg.sender, msg.value, tx.gasprice, msg.sig, gasleft());
}
// ⚠️ block.timestamp可被矿工微调(±15s)
// ❌ 不要用block.timestamp做精确时间源
// ✅ 适合做大致时间判断(如锁定7天)
}
// ✅ 验证通过
你已掌握Solidity数据体系——值类型/引用类型/存储布局!
✅ 值类型与引用类型 ✅ 映射与结构体 ✅ 存储槽布局 ✅ Gas优化