- 编程语言
- 编译器
- 区块链
【免费下载链接】vyper
Pythonic Smart Contract Language for the EVM
本文以 Vyper 官方文档中的 Simple Open Auction(简单公开拍卖)示例为主线,完整拆解一个可运行的 Vyper 拍卖合约:从public状态变量与自动生成的 getter、@deploy构造函数,到@payable出价、withdrawal 取款模式,再到endAuction的"检查—执行—交互"三阶段收尾流程。读完后,你将掌握用 Vyper 编写带状态管理、ETH 转移与再入防护的合约的基本套路,并能对照仓库中的源码与测试理解每个机制背后的实现。
原文档明确提示:This is example code for learning purposes. Do not use in production without thorough review and testing.(这是学习用途的示例代码,未经充分审查和测试请勿用于生产环境。)
完整合约源码见 examples/auctions/simple_open_auction.vy,本文所有行号均基于该文件。
1. 合约总体结构与业务模型
示例属于 Vyper by Example 系列的开篇。业务模型很直接:参与者在限定的时间窗口内提交出价(bid),拍卖结束后,由部署时指定的受益人(beneficiary)收到最高出价的 ETH。
从代码结构看,整个合约只有 4 个函数:1 个构造函数和 3 个可外部调用的方法(bid、withdraw、endAuction),加上 7 个状态变量。Vyper 语法与 Python 相似,但它是独立语言,有自己的类型系统、装饰器(如@deploy、@external)和关键字。文件首行通过#pragma version >0.3.10声明了所需的编译器版本范围,这一行也说明了当前仓库版本对示例的兼容性前提。
# Open Auction # Auction params # Beneficiary receives money from the highest bidder beneficiary: public(address) auctionStart: public(uint256) auctionEnd: public(uint256) # Current state of auction highestBidder: public(address) highestBid: public(uint256) # Set to true at the end, disallows any change ended: public(bool) # Keep track of refunded bids so we can follow the withdraw pattern pendingReturns: public(HashMap[address, uint256])7 个状态变量按职责分为两组:
| 变量 | 类型 | 职责 |
|---|---|---|
beneficiary | address | 最高出价款的接收方 |
auctionStart/auctionEnd | uint256 | 以块时间戳(秒)表示的出价窗口起止 |
highestBidder/highestBid | address/uint256 | 当前最高出价人与出价(wei) |
ended | bool | 标记拍卖是否已正式结束,之后禁止任何变更 |
pendingReturns | HashMap[address, uint256] | 键值对记录每位被超越者的待退款金额,用于执行 withdrawal 模式 |
public标注与 getter 自动生成
所有变量都写成public(类型)的形式。原文档解释:public让变量可被外部合约调用,并自动生成一个 getter 函数(如contract.beneficiary());不加public则为私有声明,仅同合约内的方法可访问。
从源码看,getter 的生成发生在语义分析阶段:vyper/semantics/analysis/getters.py 中的generate_public_variable_getters会遍历 AST 中所有is_public的VariableDecl节点,按变量的getter_signature构造一个同名的external+view函数(返回self.<var>,HashMap/DynArray变量还会为每个下标生成带参数arg0、arg1… 的查询函数),并挂到模块命名空间。这解释了为何测试中可以直接调用auction_contract.beneficiary()、auction_contract.pendingReturns(k1)等无参或带下标的只读方法——它们并不是手写方法,而是编译器合成的 getter。
2. 构造函数:@deploy与block.timestamp
@deploy def __init__(_beneficiary: address, _auction_start: uint256, _bidding_time: uint256): self.beneficiary = _beneficiary self.auctionStart = _auction_start # auction start time can be in the past, present or future self.auctionEnd = self.auctionStart + _bidding_time assert block.timestamp < self.auctionEnd # auction end time should be in the future- 构造参数为
_beneficiary: address、_auction_start: uint256、_bidding_time: uint256,其中_bidding_time是出价窗口时长(秒),而不是结束时间本身。 - 先存下受益人与开始时间,再由
self.auctionStart + _bidding_time计算self.auctionEnd;注意开始时间允许是过去、现在或未来。 - 末尾的
assert block.timestamp < self.auctionEnd强制要求结束时点必须在未来,部署时若传入非法参数,整笔部署交易会 revert。 block是 Vyper 内置对象,提供当前块信息(时间戳、编号、hash 等);另一个内置对象msg提供调用方信息,后文的bid()会用到。
3.bid():@payable出价与assert校验
@external @payable def bid(): # Check if bidding period has started. assert block.timestamp >= self.auctionStart # Check if bidding period is over. assert block.timestamp < self.auctionEnd # Check if bid is high enough assert msg.value > self.highestBid # Track the refund for the previous high bidder self.pendingReturns[self.highestBidder] += self.highestBid # Track new high bid self.highestBidder = msg.sender self.highestBid = msg.value关键点:
@payable装饰器允许调用者在方法调用之外随交易附带 ETH。出价者调用bid()时随交易发送的金额即为出价本身(不含 gas 费)。在类型层面,@payable将函数可变性标记为StateMutability.PAYABLE,见 vyper/semantics/types/function.py 的is_payable属性;反向地,向非 payable 函数传value会在语义分析期抛出CallViolation("Cannot send ether to nonpayable function")(同文件约 L784-L787)。msg.sender/msg.value:msg内置变量分别给出调用者地址与随交易发送的 ETH 数量(wei)。- 三道
assert顺序执行:窗口未开始、窗口已结束、新出价未严格大于当前最高出价,任一不成立即抛出错误并回滚整笔交易。这也意味着 0 wei 出价、以及"追平"当前最高价(equal bid)都会失败——仓库测试 tests/functional/examples/auctions/test_simple_open_auction.py 的test_bid正是用tx_failed()验证了这两种情况(value=0被拒、value=1之后再次value=1被拒),并验证更高出价会覆盖highestBidder/highestBid。 - 退款记账:通过校验后,先把前最高出价人的金额累加进
self.pendingReturns[self.highestBidder](遵循安全的取款模式),再更新highestBidder和highestBid。测试中对应断言:被超越者调用withdraw()前pendingReturns余额增加、取款后归零且账户余额上升。
4.withdraw():withdrawal 取款模式
@external def withdraw(): pending_amount: uint256 = self.pendingReturns[msg.sender] self.pendingReturns[msg.sender] = 0 send(msg.sender, pending_amount)源码注释(合约第 50-53 行)解释了为什么不在bid()里直接退款:如果在bid()中向被超越者直接转账,一个恶意的出价合约可以拒收/阻塞这笔退款,从而卡死后续所有更高出价。采用 withdrawal 模式后,每个出价者自己拉取(pull)自己的退款:读出待退金额、先清零(防止重入时二次退款)、再发送资金。测试test_bid末尾验证了该闭环:withdraw(sender=k1)后账户余额增加且pendingReturns(k1) == 0。
从编译器实现看,send是一个内置函数:vyper/builtins/functions.py 中的Send类将其生成 IR["assert", ["call", gas, to, value, 0, 0, 0, 0]](默认 gas stipend 为 0),即send()在转账失败时会使整笔交易 revert,而不是静默吞掉失败——这是 Vyper 与传统 EVM 脚本中"send 不检查返回值"写法的区别。
5.endAuction():三阶段模式收尾
@external def endAuction(): # 1. Conditions assert block.timestamp >= self.auctionEnd assert not self.ended # 2. Effects self.ended = True # 3. Interaction send(self.beneficiary, self.highestBid)合约内的大段注释(第 60-75 行)给出了一条重要工程准则:与外部合约交互(调用函数或发送 ETH)的函数应拆成三个阶段——
- Conditions(检查):断言当前时间已过
auctionEnd;断言self.ended尚未置位。后者防止重复调用——若拍卖已结束,任何再次调用都可能被恶意利用,必须显式拦截。 - Effects(执行):将
self.ended置为True,正式关闭拍卖。 - Interaction(交互):
send(self.beneficiary, self.highestBid)把最高出价转给受益人。
注释进一步说明:三个阶段若混杂,外部合约可能回调(re-entrancy)进入本合约修改状态或重复触发副作用(例如重复打款);内部函数若包含外部交互,也应视为交互阶段的一部分。本示例中send恰好是最后一个语句,天然满足该模式。
对应测试test_end_auction验证了完整生命周期:未到期时endAuction()失败 → 时间推进EXPIRY秒后调用成功且受益人余额精确增加最高出价 → 结束后再bid()失败、再次endAuction()也失败(不能重复结束)。
6. 编译与运行验证
示例文件可直接用 Vyper 编译器编译(命令行入口见 vyper/cli/vyper_compile.py 对应的vyper compile命令,需先通过pip install vyper安装编译器;#pragma version >0.3.10决定当前安装版本是否满足该示例的编译前提):
vyper compile examples/auctions/simple_open_auction.vy --show-source-map --show-method-ids行为层面的正确性由功能测试套件覆盖:tests/functional/examples/auctions/test_simple_open_auction.py 使用仓库内置的模拟链环境(tests/evm_backends 提供 pyevm 等后端),三个测试函数分别覆盖:
test_initial_state:部署后受益人、起止时间(auctionEnd == auctionStart + EXPIRY)、ended is False、最高出价为 0 等初始状态;test_bid:0 出价/等额出价被拒、多次抬价、pendingReturns累计与withdraw()取款;test_end_auction:到期前不可结束、到期后受益人收款、结束后不可再出价且不可重复结束。
7. 小结与扩展方向
这个示例用不到百行代码展示了 Vyper 合约的核心骨架:public状态变量 + 自动 getter、@deploy构造器、@external @payable收币入口、msg.sender/msg.value/block.timestamp内置变量、assert校验回滚、HashMap状态记账,以及 withdrawal 模式与"检查—执行—交互"三阶段两大安全模式。原文档也指出,这是一个功能最简(barebones)的实现,可以在此基础上继续改进。
若希望在此基础上深入学习,仓库提供了直接的同系列进阶案例:docs/vyper-by-example/blind-auction.rst(盲拍,涉及keccak256哈希密封与HashMap多键存储)以及 examples/auctions/blind_auction.vy 的完整实现;其余示例(代币、众筹、治理投票、DeFi 等)的入口在 docs/vyper-by-example/index.rst。
- 编程语言
- 编译器
- 区块链
【免费下载链接】vyper
Pythonic Smart Contract Language for the EVM
相关推荐
Pot-Desktop:一款划词翻译与截图 OCR 工具,5 分钟装好就能用
Pot Desktop:一款划词翻译与截图 OCR 工具,5 分钟装好就能用 如果你经常读外文资料,还常遇到图片里的文字选不中、复制不了,那么一个同时做划词翻译
桌面应用AI 应用Solidity智能合约开发:盲拍与公开拍卖的实现详解
Solidity智能合约开发:盲拍与公开拍卖的实现详解 公开拍卖合约基础 在区块链上实现拍卖系统是智能合约的经典应用场景。我们先从最简单的公开拍卖合约开始讲解,
编程语言编译器区块链draw.io Desktop:离线桌面画图工具,数据不出机器
draw.io Desktop:离线桌面画图工具,数据不出机器 画一张内部网络拓扑,要先把草图传到在线编辑器才敢动笔?导出的图贴进文档,连线歪了还得逐根手调?d
大数据数据湖数据工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考