消息与交易 #
一、msg变量 #
1.1 msg概述 #
msg变量包含当前调用的信息:
| 属性 | 类型 | 说明 |
|---|---|---|
| msg.sender | address | 调用者地址 |
| msg.value | uint256 | 发送的ETH数量(wei) |
| msg.data | bytes | 完整的调用数据 |
| msg.sig | bytes4 | 函数选择器(前4字节) |
1.2 msg.sender #
solidity
contract MsgSender {
address public owner;
mapping(address => uint256) public balances;
constructor() {
owner = msg.sender; // 部署者地址
}
function deposit() public payable {
balances[msg.sender] += msg.value; // 调用者地址
}
function getCaller() public view returns (address) {
return msg.sender; // 返回调用者
}
}
1.3 msg.value #
solidity
contract MsgValue {
event Deposit(address indexed sender, uint256 value);
function deposit() public payable {
require(msg.value > 0, "Must send ETH");
emit Deposit(msg.sender, msg.value);
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
// 检查发送的ETH数量
function requireMinAmount(uint256 minAmount) public payable {
require(msg.value >= minAmount, "Insufficient ETH");
}
}
1.4 msg.data和msg.sig #
solidity
contract MsgData {
event CallData(bytes data, bytes4 selector);
function logCallData() public {
emit CallData(msg.data, msg.sig);
}
function getSelector() public pure returns (bytes4) {
return this.getSelector.selector; // 0x...
}
fallback() external {
// 访问原始调用数据
bytes memory data = msg.data;
bytes4 selector = msg.sig;
}
}
二、block变量 #
2.1 block概述 #
block变量包含当前区块的信息:
| 属性 | 类型 | 说明 |
|---|---|---|
| block.timestamp | uint256 | 区块时间戳(秒) |
| block.number | uint256 | 区块号 |
| block.difficulty | uint256 | 区块难度 |
| block.gaslimit | uint256 | 区块Gas限制 |
| block.coinbase | address | 矿工地址 |
| block.chainid | uint256 | 链ID |
2.2 时间相关 #
solidity
contract BlockTime {
uint256 public createdAt;
uint256 public constant DURATION = 7 days;
constructor() {
createdAt = block.timestamp;
}
function isExpired() public view returns (bool) {
return block.timestamp > createdAt + DURATION;
}
function getRemainingTime() public view returns (uint256) {
uint256 endTime = createdAt + DURATION;
if (block.timestamp >= endTime) {
return 0;
}
return endTime - block.timestamp;
}
function getCurrentTime() public view returns (uint256) {
return block.timestamp;
}
}
2.3 区块号 #
solidity
contract BlockNumber {
uint256 public startBlock;
constructor() {
startBlock = block.number;
}
function getBlocksPassed() public view returns (uint256) {
return block.number - startBlock;
}
function getCurrentBlock() public view returns (uint256) {
return block.number;
}
}
2.4 链ID #
solidity
contract ChainId {
function getChainId() public view returns (uint256) {
return block.chainid;
}
// 常见链ID
// 1: Ethereum Mainnet
// 5: Goerli Testnet
// 11155111: Sepolia Testnet
// 137: Polygon
// 56: BSC
}
三、tx变量 #
3.1 tx概述 #
tx变量包含当前交易的信息:
| 属性 | 类型 | 说明 |
|---|---|---|
| tx.origin | address | 交易发起者地址 |
| tx.gasprice | uint256 | Gas价格 |
3.2 tx.origin #
solidity
contract TxOrigin {
address public owner;
constructor() {
owner = msg.sender;
}
// 注意:tx.origin是交易的原始发起者
// msg.sender是直接调用者
function getOrigin() public view returns (address) {
return tx.origin;
}
// 安全警告:不要用tx.origin做权限检查
// 可能导致钓鱼攻击
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
// 不安全的做法
// modifier unsafeOnlyOwner() {
// require(tx.origin == owner, "Not owner"); // 危险!
// _;
// }
}
3.3 tx.gasprice #
solidity
contract TxGasPrice {
function getGasPrice() public view returns (uint256) {
return tx.gasprice;
}
function getEstimatedCost(uint256 gasUsed) public view returns (uint256) {
return gasUsed * tx.gasprice;
}
}
四、实际应用示例 #
4.1 时间锁合约 #
solidity
contract Timelock {
uint256 public constant MIN_DELAY = 1 days;
uint256 public constant MAX_DELAY = 30 days;
struct Transaction {
bytes32 id;
address target;
uint256 value;
bytes data;
uint256 executedTime;
bool executed;
}
mapping(bytes32 => Transaction) public transactions;
event Queued(bytes32 indexed id, uint256 executeTime);
event Executed(bytes32 indexed id);
function queue(
address target,
uint256 value,
bytes memory data,
uint256 delay
) public returns (bytes32) {
require(delay >= MIN_DELAY, "Delay too short");
require(delay <= MAX_DELAY, "Delay too long");
bytes32 id = keccak256(abi.encode(target, value, data));
uint256 executeTime = block.timestamp + delay;
transactions[id] = Transaction({
id: id,
target: target,
value: value,
data: data,
executedTime: executeTime,
executed: false
});
emit Queued(id, executeTime);
return id;
}
function execute(bytes32 id) public returns (bytes memory) {
Transaction storage txn = transactions[id];
require(!txn.executed, "Already executed");
require(block.timestamp >= txn.executedTime, "Too early");
txn.executed = true;
(bool success, bytes memory result) = txn.target.call{value: txn.value}(txn.data);
require(success, "Execution failed");
emit Executed(id);
return result;
}
}
4.2 签名验证 #
solidity
contract SignatureVerifier {
function verify(
bytes32 hash,
bytes memory signature
) public pure returns (address) {
require(signature.length == 65, "Invalid signature length");
bytes32 r;
bytes32 s;
uint8 v;
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
if (v < 27) {
v += 27;
}
require(v == 27 || v == 28, "Invalid signature v");
return ecrecover(hash, v, r, s);
}
function getMessageHash(
address to,
uint256 amount,
uint256 nonce
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(to, amount, nonce));
}
function getEthSignedMessageHash(bytes32 hash) public pure returns (bytes32) {
return keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)
);
}
}
五、安全注意事项 #
5.1 tx.origin攻击 #
solidity
// 攻击示例
contract Attacker {
address public target;
constructor(address _target) {
target = _target;
}
receive() external payable {
// 如果目标合约使用tx.origin检查权限
// 这里可以调用目标合约的特权函数
// 因为tx.origin是受害者地址
}
}
// 防御:使用msg.sender而不是tx.origin
contract Safe {
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not owner"); // 安全
_;
}
}
5.2 时间戳依赖 #
solidity
contract TimestampSafety {
// 注意:矿工可以在一定范围内操纵时间戳
// 不安全:完全依赖时间戳
function unsafeLottery() public view returns (bool) {
return block.timestamp % 2 == 0; // 可被操纵
}
// 安全:使用区块号
function safeLottery() public view returns (bool) {
return block.number % 2 == 0; // 更难操纵
}
}
六、总结 #
全局变量要点:
| 变量 | 说明 |
|---|---|
| msg.sender | 调用者地址 |
| msg.value | 发送的ETH |
| block.timestamp | 区块时间戳 |
| block.number | 区块号 |
| tx.origin | 交易发起者 |
使用建议:
- 使用msg.sender做权限检查
- 避免使用tx.origin
- 注意时间戳可被操纵
- 使用block.number替代时间戳
接下来,让我们学习合约交互!
最后更新:2026-03-27