ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

WTF-Solidity 极简入门:使用 transfer、send 与 call 三种方法向合约发送 ETH

WTF-Solidity 极简入门:使用 transfer、send 与 call 三种方法向合约发送 ETH WTF-Solidity 极简入门使用 transfer、send 与 call 三种方法向合约发送 ETH【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity导读在 Solidity 开发中向其他合约或地址发送 ETH 是最高频的底层操作之一而transfer()、send()、call()三种内置方法在 gas 限制、失败处理与安全性上差异显著直接决定交易是否会回滚、是否容易被重入攻击。本讲WTF-Solidity 第 20 讲将基于仓库中的 SendETH.sol 与 ReceiveETH.sol 完整源码逐一拆解三种发送方式的使用语法、gas 行为与返回值处理并给出可复现的 Remix 实操流程。读完本文你将能在自己的合约中正确地选择发送 ETH 的方式写出既灵活又安全的转账逻辑。一、三种发送 ETH 方法一览Solidity 向其他合约发送 ETH 主要有三种方法transfer()用法为接收方地址.transfer(发送ETH数额)send()用法为接收方地址.send(发送ETH数额)call()用法为接收方地址.call{value: 发送ETH数额}()。其中call()是当前被鼓励使用的方案也是现代 DeFi 合约中实际应用最广的方式。仓库源码 SendETH.sol 开头就用注释精炼总结了三者的核心差异// transfer: 2300 gas, revert // send: 2300 gas, return bool // call: all gas, return (bool, data)二、先写一个接收 ETH 的合约ReceiveETH要验证“发送”是否成功必须先有一个能接收 ETH 的合约。仓库中的ReceiveETH合约见 SendETH.sol结构非常简单却覆盖了接收 ETH 的两个关键点receive()回调与余额查询。contract ReceiveETH { // 收到eth事件记录amount和gas event Log(uint amount, uint gas); // receive方法接收eth时被触发 receive() external payable{ emit Log(msg.value, gasleft()); } // 返回合约ETH余额 function getBalance() view public returns(uint) { return address(this).balance; } }receive() external payable是合约接收原生 ETH 时被自动调用的特殊回调函数任何地址向该合约直接转账时都会触发它事件Log记录了两项关键数据msg.value本次收到的 ETH 数量单位 wei和gasleft()触发时剩余的 gas用于观测转账的 gas 消耗getBalance()通过address(this).balance返回合约当前持有的 ETH 余额。部署ReceiveETH合约后调用getBalance()可以看到当前合约余额为0这是后续所有转账实验的基准状态关于receive()的触发机制当一笔交易携带msg.data为空且携带 ETH 时优先执行receive()若合约未定义receive()则回退到fallback()。这一调度流程在仓库的 Fallback.sol 中有完整的注释图解可作为本讲的延伸阅读。三、再写一个发送 ETH 的合约SendETH接下来实现发送方合约SendETH。为了让测试完整先给它加上payable构造函数和receive()使合约在部署时和部署后都能接收 ETH后续实验中的value就是预先转入该合约的 ETHcontract SendETH { // 构造函数payable使得部署的时候可以转eth进去 constructor() payable{} // receive方法接收eth时被触发 receive() external payable{} }在此基础上SendETH合约实现了三个发送函数transferETH、sendETH、callETH并预先定义了对应的错误类型见 SendETH.solerror SendFailed(); // 用send发送ETH失败error error CallFailed(); // 用call发送ETH失败error四、transfer最简单但最不灵活语法与特性用法接收方地址.transfer(发送ETH数额)transfer()的 gas 限制为2300足够完成一次普通转账但对方合约的fallback()或receive()内不能实现太复杂的逻辑否则会因 gas 不足而失败转账失败时transfer()会自动revert回滚整个交易。代码实现// 用transfer()发送ETH function transferETH(address payable _to, uint256 amount) external payable{ _to.transfer(amount); }注意两个参数_to填ReceiveETH合约地址amount为本次转账的 ETH 数额单位 wei。实验验证部署SendETH合约后对ReceiveETH合约执行transferETH当amount为10、value为0时amountvalue即转账数额超过了合约余额交易失败并revert当amount为10、value为10时amountvalue转账成功此时回到ReceiveETH合约调用getBalance()可以看到其余额变为10wei验证 ETH 已成功入账。五、send返回 bool 但不会自动回滚语法与特性用法接收方地址.send(发送ETH数额)send()的 gas 限制同样是2300复杂逻辑同样可能因 gas 不足而失败与transfer()最大的不同转账失败时不会自动revertsend()的返回值是bool代表转账成功或失败需要开发者自己检查并处理。代码实现error SendFailed(); // 用send发送ETH失败error // send()发送ETH function sendETH(address payable _to, uint256 amount) external payable{ // 处理下send的返回值如果失败revert交易并发送error bool success _to.send(amount); if(!success){ revert SendFailed(); } }因为send()失败不会回滚交易所以必须像上面这样用bool success接收返回值并显式revert否则失败的转账会被“静默吞掉”调用方完全无感知。实验验证amount为10、value为0时转账失败由于代码显式检查了返回值交易同样发生revertamount为10、value为11时amountvalue转账成功。六、call无 gas 限制的推荐方案语法与特性用法接收方地址.call{value: 发送ETH数额}()call()没有 gas 限制可以支持对方合约fallback()或receive()实现复杂逻辑转账失败时不会自动revert返回值为(bool, bytes)bool表示成功与否bytes为对方返回的 data需要额外代码处理。代码实现error CallFailed(); // 用call发送ETH失败error // call()发送ETH function callETH(address payable _to, uint256 amount) external payable{ // 处理下call的返回值如果失败revert交易并发送error (bool success,) _to.call{value: amount}(); if(!success){ revert CallFailed(); } }用元组解构(bool success,)接收返回值忽略第二位的bytes数据失败时同样显式revert CallFailed()。实验验证amount为10、value为0时转账失败因检查了返回值而revertamount为10、value为11时转账成功。下方为使用callETH成功转账的 Remix 结果交易状态为成功并返回了完整的 gas 消耗数据七、三种方法横向对比方法gas 限制失败是否自动 revert返回值是否推荐transfer()2300是无次优选择send()2300否bool几乎没人用call()无限制否(bool, bytes)最提倡结论与 SendETH.sol 的注释一致call()无 gas 限制、最为灵活是最提倡的方法transfer()有 2300 gas 限制但失败会自动 revert 交易是次优选择send()有 2300 gas 限制且失败不会自动 revert几乎没有人使用。八、源码级延伸这些方法在真实合约中如何被使用理解了三种发送方式后可以结合本仓库其他章节的源码看看它们在实际场景中的应用以加深印象。1. call 不只是转账带 calldata 调用目标函数call()的真正威力在于它不仅能发 ETH还能同时携带调用数据。仓库 Call.sol 展示了如何在一次调用中同时执行对方函数并附带 ETH(bool success, bytes memory data) _addr.call{value: msg.value}( abi.encodeWithSignature(setX(uint256), x) );这里通过abi.encodeWithSignature编码函数选择器call{value: msg.value}在调用setX的同时把msg.value一并转给对方。这正是许多合约交互的标准姿势也是本讲callETH中空 calldata的进阶形态。2. receive/fallback 在接收端的实战WETH 的存款入口receive()与fallback()并非只能发个事件。仓库 WETH.sol 中用户直接向 WETH 合约转 ETH 时fallback()/receive()会自动触发deposit()铸造等量 WETH实现“转账即存款”fallback() external payable { deposit(); } receive() external payable { deposit(); }这说明接收方的回调函数越复杂对发送方 gas 的要求就越高——这也是call()无 gas 限制反而更安全的原因之一。3. transfer 的 2300 gas 限制与提款场景在 WETH.sol 的withdraw()中可以看到transfer的应用销毁 WETH 后向用户地址转回 ETH。因为目标通常是普通 EOA 地址而非复杂合约2300 gas 足够。若目标可能是复杂合约就需要评估改用call()。4. 一个常被混淆的概念ERC20 的 transfer 不是 ETH 转账需要注意区分ERC20 代币的transfer如 Faucet.sol 中token.transfer(msg.sender, amountAllowed)操作的是代币账本mapping与本讲发送原生 ETH 的transfer()完全是两回事前者不涉及 2300 gas 限制。九、注意事项与安全提示send/call 必须处理返回值两者失败都不会自动回滚若忽略返回值会导致资金转移失败却不报错是常见的隐蔽 bug重入风险call()无 gas 限制意味着对方回调可以执行任意复杂逻辑若接收方是恶意合约可能在转账回调中发起重入攻击。当合约状态更新与外部调用顺序不当时尤其危险——重入攻击与“检查-生效-交互”模式的完整攻防演示可参考仓库 S01_ReentrancyAttack2300 gas 的历史背景transfer/send的 2300 gas 上限源于早期链上SLOAD/SSTORE成本较低的设计后续 EIP 调整存储操作成本后依赖固定 gas 上限的转账方式对接收方回调的兼容性进一步变差这也是业界转向call()的原因之一。从代码结构看本仓库后续章节如闪电贷、DEX、多签钱包等的 ETH 相关操作也普遍采用call{value: ...}模式例如 UniswapV2Flashloan.sol 中对借贷方偿还 ETH 的处理。总结本讲围绕 SendETH.sol 与ReceiveETH合约完整演示了 Solidity 发送 ETH 的三种方式transfer2300 gas、失败自动回滚、send2300 gas、返回 bool 需自行检查与call无 gas 限制、返回(bool, bytes)。在实际开发中call()因灵活性和对复杂接收逻辑的兼容性成为主流选择但使用它时必须自行检查返回值并时刻警惕重入风险而transfer()因其失败自动回滚的特性在简单提款场景中仍是次优选择。掌握这三种方法的差异与适用边界是写出健壮、安全 Solidity 合约的基础功。【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表