常见的address(this),tx.origin 和 msg.sender 语句解释

  • Dapplink
  • 发布于 2025-05-04 14:33
  • 阅读 8

在 Solidity 中,address(this)、tx.origin 和 msg.sender 是三个与合约地址和调用者相关的重要概念,理解它们的作用对于编写安全、高效的智能合约至关重要。

<!--StartFragment-->

1. address(this)

定义

  • address(this) 返回当前合约的地址
  • 它是一个 address 类型的值,可以用来与其他合约交互或接收 ETH。

特性

  • 始终指向当前合约的地址。
  • 主要用于从合约自身获取地址,或在合约内部发送 ETH 给自己。

用途:

  • 获取合约地址
  • 发送以太币
  • 调用合约内部函数
  • 检查余额

示例:

pragma solidity ^0.8.0;

contract MyContract {
    function getContractAddress() public view returns (address) {
        return address(this);
    }

    function getContractBalance() public view returns (uint256) {
        return address(this).balance; 
    }

    function sendToSelf() public payable {
        address(this).call{value: msg.value}(""); 
    }
}
  • 示例2: 发送以太币: 使用 address(this).balance 获取合约的以太币余额,然后通过 address(this).transfer 或 address(this).call 向其他地址发送以太币
pragma solidity ^0.8.0;

contract MyContract {
    // 接收以太币的函数
    receive() external payable {}

    // 向指定地址发送以太币
    function sendEther(address payable recipient, uint256 amount) public {
        require(address(this).balance >= amount, "Insufficient balance");
        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Transfer failed.");
    }

    // 获取合约余额
    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }
}
  • 示例3: 调用合约内部函数: address(this) 可以用于调用合约内部函数,特别是需要通过 call 进行动态调用时
pragma solidity ^0.8.0;

contract MyContract {
    uint256 public value;

    function setValue(uint256 _value) public {
        value = _value;
    }

    function callSetValue(uint256 _value) public {
        (bool success, ) = address(this).call(
            abi.encodeWithSignature("setValue(uint256)", _value)
        );
        require(success, "Internal call failed.");
    }
}
  • 示例:检查余额: 通过 address(this).balance 获取当前合约的以太币余额
pragma solidity ^0.8.0;

contract MyContract {
    // 接收以太币的函数
    receive() external payable {}

    // 获取合约余额
    function getContractBalance() public view returns (uint256) {
        return address(this).balance;
    }
}
  • 简单总结
    • 获取合约地址address(this) 返回当前合约的地址
    • 发送以太币address(this).balance 返回合约的以太币余额,通过 call 方法发送以太币
    • 调用合约内部函数:使用 address(this).call 可以进行动态函数调用
    • 检查余额:通过 address(this).balance 获取当前合约的以太币余额

这些功能使得 address(this) 成为 Solidity 合约开发中的一个重要工具,能够帮助开发者实现各种合约内的操作和交互

2. tx.origin

  • 定义
    • tx.origin 返回当前交易的发起者地址
    • 它是整个调用链中最初的外部账户地址(EOA)
  • 特性
    • 始终是交易发起者的地址(EOA)
    • 无论中间经过了多少次合约调用,tx.origin 不会改变。
  • 用途
    • 用于跟踪交易的最初发起者
    • 不推荐用于权限验证,容易受到中间人攻击
  • tx.origin 的示例代码
    • 简单示例:在这个例子中,getTxOrigin 函数返回最初发起交易的外部账户地址,而 getMsgSender 函数返回当前调用者的地址
  pragma solidity ^0.8.0;

  contract OriginExample {
      function getTxOrigin() public view returns (address) {
          return tx.origin;
      }

      function getMsgSender() public view returns (address) {
          return msg.sender;
      }
  }
  • 使用 tx.origin 进行访问控制:在这个例子中,只有最初发起交易的账户(即部署合约的账户)才能调用 secureFunction
  pragma solidity ^0.8.0;

  contract AccessControl {
      address public owner;

      constructor() {
          owner = tx.origin; // 将合约部署者设置为 owner
      }

      modifier onlyOwner() {
          require(tx.origin == owner, "Not the owner");
          _;
      }

      function secureFunction() public onlyOwner {
          // 只有最初的交易发起者(owner)才能调用这个函数
      }
  }
  • 安全性问题: tx.origin 可能引发中间人攻击
    • 恶意合约可以诱导用户调用该合约,从而伪装成交易的初始发起者。
    • 解决方案:使用 msg.sender 代替 tx.origin 进行权限控制。
contract Victim {
    address public owner;

    constructor() {
        owner = tx.origin;
    }

    function transferOwnership() public {
        require(tx.origin == owner, "Not owner");
        owner = msg.sender;
    }
}

contract Attacker {
    function attack(address victim) public {
        Victim(victim).transferOwnership(); // tx.origin 是攻击目标的所有者地址
    }
}

3. msg.sender

  • 定义
  • msg.sender 返回当前调用的直接发起者地址。
  • 如果是合约调用,则返回调用该合约的合约地址;如果是外部账户调用,则返回 EOA 的地址。
  • 特性
    • 每一级调用都会更新为当前调用者。
    • 更适合用作权限验证。
  • 用途
    • 验证调用者权限。
    • 与外部合约交互。
  • 示例 1:
contract Example {
    address public owner;

    constructor() {
        owner = msg.sender; // 初始化时,设置部署者为所有者
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    function updateOwner(address newOwner) public onlyOwner {
        owner = newOwner; // 只有当前所有者可以更新
    }
}
  • 示例 2: 权限控制:使用 msg.sender 实现简单的所有者权限控制。
pragma solidity ^0.8.0;

contract Ownable {
    address public owner;

    constructor() {
        owner = msg.sender; // 部署合约的账户成为所有者
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Not the owner");
        _;
    }

    function changeOwner(address newOwner) public onlyOwner {
        owner = newOwner;
    }
}
  • 示例: 支付功能:使用 msg.sender 实现一个简单的支付功能。
pragma solidity ^0.8.0;

contract Payment {
    event PaymentReceived(address from, uint256 amount);

    function pay() public payable {
        require(msg.value > 0, "No ether sent");
        emit PaymentReceived(msg.sender, msg.value);
    }
}
  • 示例:交互合约:两个合约之间的交互,展示如何使用 msg.sender 传递调用者信息。
pragma solidity ^0.8.0;

contract TargetContract {
    address public lastCaller;

    function updateCaller() public {
        lastCaller = msg.sender;
    }
}

contract CallerContract {
    TargetContract target;

    constructor(address targetAddress) {
        target = TargetContract(targetAddress);
    }

    function callTarget() public {
        target.updateCaller();
    }
}
  • 安全性和最佳实践
    • 权限控制:使用 msg.sender 进行权限控制时,确保合约正确地设置和验证权限。例如,使用修饰符(modifier)来封装权限检查逻辑。
    • 防范重入攻击:在涉及以太币转账和状态修改的函数中,使用 msg.sender 时需要特别注意重入攻击风险。通过使用“检查-效果-交互”模式来防止此类攻击。
    • 避免钓鱼攻击:不要将关键逻辑基于 tx.origin,而是使用 msg.sender 进行权限验证。

4. 小结

96a0df12a0e00a606bde9dc233ff301d.png

  • address(this):始终是当前合约的地址,适合获取合约自身的信息或调用自身的逻辑
  • tx.origin:反映整个交易链的发起者,使用场景有限,且不适合用于权限验证
  • msg.sender:当前调用的直接发起者,是权限验证和调用者识别的首选

<!--EndFragment-->

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Dapplink
Dapplink
0xBdcb...f214
首个模块化、可组合的Layer3协议。