消息与交易 #

一、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