Solidity合约编程基础知识
目录
- 合约结构(contract)
- 交易属性
- msg全局变量
- 数值
- 字符串 & require校验
- mapping结构
- 数组
- 阅读投票合约
合约结构(contract)
https://docs.soliditylang.org/zh-cn/latest/structure-of-a-contract.html#
交易属性
区块和交易属性
block.blockhash(uint blockNumber) returns (bytes32):指定区块的区块哈希——仅可用于最新的 256 个区块且不包括当前区块;而 blocks 从 0.4.22 版本开始已经不推荐使用,由 blockhash(uint blockNumber) 代替
block.coinbase (address): 挖出当前区块的矿工地址
block.difficulty (uint): 当前区块难度
block.gaslimit (uint): 当前区块 gas 限额
block.number (uint): 当前区块号
block.timestamp (uint): 自 unix epoch 起始当前区块以秒计的时间戳
gasleft() returns (uint256):剩余的 gas
msg.data (bytes): 完整的 calldata
msg.gas (uint): 剩余 gas - 自 0.4.21 版本开始已经不推荐使用,由 gesleft() 代替
msg.sender (address): 消息发送者(当前调用)
msg.sig (bytes4): calldata 的前 4 字节(也就是函数标识符)
msg.value (uint): 随消息发送的 wei 的数量
now (uint): 目前区块时间戳(block.timestamp)
tx.gasprice (uint): 交易的 gas 价格
tx.origin (address): 交易发起者(完全的调用链)
msg全局变量
在 Solidity 中,msg 是一个 全局对象(全局变量),它包含了与当前 外部函数调用(transaction 或 call) 相关的关键信息。它是智能合约与外部世界(如用户、钱包、其他合约)交互时最重要的数据来源之一。
属性 | 类型 | 说明 |
---|---|---|
msg.sender | address | 调用者地址(当前函数的直接调用者) |
msg.value | uint | 随交易发送的 以太币数量(单位:wei) |
msg.data | bytes calldata | 完整的调用数据(包括函数签名和参数) |
msg.sig | bytes4 | 函数签名的哈希(前 4 字节) |
msg.gas | uint | 剩余的 gas(⚠️ Solidity 0.8.0+ 已废弃) |
数值
类型 | 位数 | 最大值 | 表达式 |
---|---|---|---|
uint8 | 8 | 255 | 2**8 - 1 |
uint16 | 16 | 65,535 | 2**16 - 1 |
uint32 | 32 | 4,294,967,295 | 2**32 - 1 |
uint64 | 64 | 18,446,744,073,709,551,615 | 2**64 - 1 |
uint256 | 256 | 2**256 - 1(极大) | Solidity 默认整数类型 |
例如不符合数值范围的输入,remix部署报错如下:
字符串 & require校验
/*** 拼接两个字符串,要求总长度 ≤ 16,否则 revert*/function concatRequireLimit(string memory str1, string memory str2)publicpurereturns (string memory){bytes memory b1 = bytes(str1);bytes memory b2 = bytes(str2);require(b1.length + b2.length <= 16, "Total length exceeds 16");bytes memory result = new bytes(b1.length + b2.length);uint256 k = 0;for (uint256 i = 0; i < b1.length; i++) {result[k++] = b1[i];}for (uint256 i = 0; i < b2.length; i++) {result[k++] = b2[i];}return string(result);}
- 正常例子
- 异常例子
mapping结构
-
它无法迭代keys,因为它只保存键的哈希,而不保存键值,如果想迭代,可以用开源的可迭代哈希类库
-
如果一个key未被保存在mapping中,一样可以正常读取到对应value,只是value是空值(字节全为0)。所以它也不需要put、get等操作,用户直接去操作它即可。
eg1: 简单映射:地址 → 余额
pragma solidity ^0.8.0;contract SimpleMapping {// 每个地址对应一个余额(单位:wei)mapping(address => uint256) public balances;function setBalance(address _addr, uint256 _amount) public {balances[_addr] = _amount;}// public 会自动生成 getter 函数// 调用 balances[0x...] 可查询
}
eg2: 映射 + 结构体:用户信息管理
struct User {string name;uint256 age;bool active;
}// 用户地址 → 用户信息
mapping(address => User) public users;function setUser(string memory _name, uint256 _age) public {users[msg.sender] = User({name: _name,age: _age,active: true});
}
eg3: 映射 + 数组:一对多关系
// 每个用户可以有多个订单 ID
mapping(address => uint256[]) public userOrders;function addOrder(uint256 orderId) public {userOrders[msg.sender].push(orderId);
}
数组
contract Sample{
string[] private arr;
function arraySample() public view {arr.push("Hello");uint len = arr.length;//should be 1string value = arr[0];//should be Hello}
}
局部变量数组
function arraySample() public view returns(uint){//create an empty array of length 2uint[] memory p = new uint[](2);p[3] = 1;//THIS WILL THROW EXCEPTION return p.length;
}
Copy to clipboard
阅读投票合约
https://docs.soliditylang.org/zh-cn/latest/solidity-by-example.html#voting
- 初始化,提案投票都是0
/// 为 `proposalNames` 中的每个提案,创建一个新的(投票)表决constructor(bytes32[] memory proposalNames) {chairperson = msg.sender;voters[chairperson].weight = 1;// 对于提供的每个提案名称,// 创建一个新的 Proposal 对象并把它添加到数组的末尾。for (uint i = 0; i < proposalNames.length; i++) {// `Proposal({...})` 创建一个临时 Proposal 对象// `proposals.push(...)` 将其添加到 `proposals` 的末尾proposals.push(Proposal({name: proposalNames[i],voteCount: 0}));}}
- 投票: 提案投票数加上调用者即投票人的权重
/// 把您的票(包括委托给您的票),/// 投给提案 `proposals[proposal].name`。function vote(uint proposal) external {Voter storage sender = voters[msg.sender];require(sender.weight != 0, "Has no right to vote");require(!sender.voted, "Already voted.");sender.voted = true;sender.vote = proposal;// 如果 `proposal` 超过了数组的范围,// 则会自动抛出异常,并恢复所有的改动。proposals[proposal].voteCount += sender.weight;}
- 统计投票: 取投票数最多的提案
/// @dev 结合之前所有投票的情况下,计算出获胜的提案。
function winningProposal() public viewreturns (uint winningProposal_)
{uint winningVoteCount = 0;for (uint p = 0; p < proposals.length; p++) {if (proposals[p].voteCount > winningVoteCount) {winningVoteCount = proposals[p].voteCount;winningProposal_ = p;}}
}// 调用 `winningProposal()` 函数以获取提案数组中获胜者的索引,
// 并以此返回获胜者的名称。
function winnerName() external viewreturns (bytes32 winnerName_)
{winnerName_ = proposals[winningProposal()].name;
}