[{"id":"overview","href":"/docs","title":"Vyper 文档概览","group":"概览","description":"从安装、语法、模块化到编译流程，快速建立一套适合中文阅读的 Vyper 学习路径。","content":"从安装、语法、模块化到编译流程，快速建立一套适合中文阅读的 Vyper 学习路径。 Vyper 是一门面向 EVM 的 Pythonic 智能合约语言。它优先考虑安全性、简洁性和可审计性， 用更少的语言特性换来更稳定的合约边界和更可预测的运行行为。","order":0},{"id":"overview#why-vyper","href":"/docs#why-vyper","title":"Vyper 文档概览","group":"概览","description":"从安装、语法、模块化到编译流程，快速建立一套适合中文阅读的 Vyper 学习路径。","content":"为什么是 Vyper Vyper 的定位很明确：它不是为了“语法炫技”，而是为了让合约在审计、 维护和链上执行时都更容易推理。 默认开启溢出与边界检查，减少常见漏洞面。 使用显式的外部调用关键字，例如 extcall 和 staticcall，让危险边界一眼可见。 放弃继承、修饰器、内联汇编和无限循环，换取更直观的控制流。 保持与 Python 接近的阅读体验，让代码更适合人工审查。 适合谁阅读 如果你已经写过 Solidity，这套文档更适合作为“迁移手册”；如果你第一次接触 EVM 合约， 则建议按左侧顺序从安装和快速开始一路读到模块与编译。","order":0,"section":"为什么是 Vyper"},{"id":"overview#learning-path","href":"/docs#learning-path","title":"Vyper 文档概览","group":"概览","description":"从安装、语法、模块化到编译流程，快速建立一套适合中文阅读的 Vyper 学习路径。","content":"推荐学习路径 建议按下面的节奏阅读： 先把本地环境跑通，确认你能安装编译器并生成字节码。 用 Moccasin 初始化一个最小项目，把编译和测试命令先跑通。 理解合约文件的组织方式，再看类型系统和函数控制流。 开始接触模块、内建函数和编译参数等进阶主题。 最后再回头理解 Vyper 与 Solidity 的设计差异，建立自己的编码习惯。","order":0,"section":"推荐学习路径"},{"id":"overview#core-principles","href":"/docs#core-principles","title":"Vyper 文档概览","group":"概览","description":"从安装、语法、模块化到编译流程，快速建立一套适合中文阅读的 Vyper 学习路径。","content":"核心设计原则 原则 实际含义 --- --- 安全性 语言层面主动排除高风险能力，例如递归、无限循环和内联汇编。 简洁性 代码路径尽量线性，函数读起来像普通 Python，而不是依赖隐式魔法。 可审计性 写代码时优先照顾读者和审计者，而不是只照顾作者的书写便利。 这也是为什么 Vyper 的很多“限制”其实是刻意的产品设计。它希望你在合约开发时少依赖技巧，多依赖明确结构。","order":0,"section":"核心设计原则"},{"id":"overview#documentation-map","href":"/docs#documentation-map","title":"Vyper 文档概览","group":"概览","description":"从安装、语法、模块化到编译流程，快速建立一套适合中文阅读的 Vyper 学习路径。","content":"文档地图 安装 Vyper 选择 uv、pipx、pip 或 Docker，并确认本地编译器可用。 5 分钟快速开始 用 Moccasin 创建第一个项目，跑通编译和测试闭环。 合约结构 理解 pragma、导入、状态变量、事件和接口的组织方式。 类型系统 掌握整数、十进制定点数、数组、映射和显式转换。 模块与组合 使用 initializes、uses 和 exports 组织可复用合约。 编译与产物 理解 CLI 输出、EVM 版本和优化模式。","order":0,"section":"文档地图"},{"id":"installing-vyper","href":"/docs/installing-vyper","title":"安装 Vyper","group":"入门","description":"选择合适的安装方式，并确认本地编译器环境可用。","content":"选择合适的安装方式，并确认本地编译器环境可用。 安装 Vyper 的关键不是“能不能装上”，而是“装完之后能否稳定地复用在你的项目流程里”。 如果你只是想立即开始，优先选择 uv tool 或 pipx 这类隔离安装方式。","order":1},{"id":"installing-vyper#choose-path","href":"/docs/installing-vyper#choose-path","title":"安装 Vyper","group":"入门","description":"选择合适的安装方式，并确认本地编译器环境可用。","content":"如何选择安装路径 场景 推荐方式 说明 --- --- --- 本地直接使用 CLI uv tool install vyper 最省心，隔离环境，升级简单。 偏好 Python CLI 管理 pipx install vyper 与 uv tool 类似，也适合单独安装。 在 Python 项目里作为依赖使用 pip install vyper 或 uv pip install vyper 适合已有虚拟环境的项目。 CI / 一次性编译环境 Docker 不依赖宿主机 Python 细节。 前置条件 Vyper 目前要求 Python 3.11 或更高版本。开始安装前先执行 python3 --version，不要等到依赖编译失败后再回头排查版本问题。","order":1,"section":"如何选择安装路径"},{"id":"installing-vyper#standalone","href":"/docs/installing-vyper#standalone","title":"安装 Vyper","group":"入门","description":"选择合适的安装方式，并确认本地编译器环境可用。","content":"独立安装 如果你只是需要一个稳定的 vyper 命令，优先使用隔离安装： uv tool install vyper 或者： pipx install vyper 安装完成后，先验证命令是否可用： vyper --version 如果 shell 提示找不到命令，通常不是 Vyper 本身的问题，而是你的 PATH 还没有刷新。","order":1,"section":"独立安装"},{"id":"installing-vyper#python-project","href":"/docs/installing-vyper#python-project","title":"安装 Vyper","group":"入门","description":"选择合适的安装方式，并确认本地编译器环境可用。","content":"Python 项目内安装 如果你把 Vyper 当成 Python 项目依赖，先创建虚拟环境，再安装： python3 -m venv .venv source .venv/bin/activate pip install vyper 或者用 uv： uv venv source .venv/bin/activate uv pip install vyper 这种方式更适合你要把编译流程、测试工具和其他 Python 依赖放在同一个项目里统一管理。","order":1,"section":"Python 项目内安装"},{"id":"installing-vyper#containers","href":"/docs/installing-vyper#containers","title":"安装 Vyper","group":"入门","description":"选择合适的安装方式，并确认本地编译器环境可用。","content":"Docker 与其他选项 如果你更在意环境一致性，可以直接使用官方 Docker 镜像： docker pull vyperlang/vyper docker run -v $(pwd):/code vyperlang/vyper /code/Counter.vy 除此之外，还可以考虑这些安装选择： GitHub Releases 提供预编译二进制。 Nix 用户可以直接通过 nixpkgs 安装。 只想临时试验语法时，可以先用在线环境，例如 Try Vyper 或 Remix。 实践建议 如果你的目标是开始写合约，不要一上来就纠结所有安装方式。先选一种最简单的方案跑通 vyper --version， 再进入快速开始章节。","order":1,"section":"Docker 与其他选项"},{"id":"quickstart","href":"/docs/quickstart","title":"5 分钟快速开始","group":"入门","description":"使用 Moccasin 初始化、编译和测试第一个 Vyper 项目。","content":"使用 Moccasin 初始化、编译和测试第一个 Vyper 项目。 Vyper 官方推荐新项目默认使用 Moccasin。它围绕 Vyper 工作流设计， 能把脚手架、编译、测试和部署都统一在一套命令里。","order":2},{"id":"quickstart#recommended-stack","href":"/docs/quickstart#recommended-stack","title":"5 分钟快速开始","group":"入门","description":"使用 Moccasin 初始化、编译和测试第一个 Vyper 项目。","content":"推荐工具链 如果你刚开始接触 Vyper，不要直接沿用 Solidity 时代的习惯去搭 Foundry 或 Hardhat。 Vyper 官方文档更推荐： 用 Moccasin 建项目。 用 Titanoboa / pytest 跑测试。 用 Vyper 原生工具链理解语言特性，而不是先绕进兼容层。 安装 Moccasin： uv tool install moccasin mox --version 何时不选 Moccasin 如果你已经有成熟的多语言仓库，或者必须接入既有 JS 构建系统，可以考虑 Ape 或 Hardhat 插件。 但对于新手和纯 Vyper 项目，Moccasin 的路径更短。","order":2,"section":"推荐工具链"},{"id":"quickstart#project-bootstrap","href":"/docs/quickstart#project-bootstrap","title":"5 分钟快速开始","group":"入门","description":"使用 Moccasin 初始化、编译和测试第一个 Vyper 项目。","content":"初始化项目 创建项目： mox init my_project cd my_project 你会得到一个类似这样的目录： my_project/ ├── src/ ├── tests/ ├── script/ └── moccasin.toml 这个结构已经覆盖了最常见的合约开发流程：源代码、测试和部署脚本分开管理。","order":2,"section":"初始化项目"},{"id":"quickstart#first-contract","href":"/docs/quickstart#first-contract","title":"5 分钟快速开始","group":"入门","description":"使用 Moccasin 初始化、编译和测试第一个 Vyper 项目。","content":"第一个合约 一个最小的 Vyper 合约通常长这样： #pragma version ^0.4.1 number: public(uint256) @external def setnumber(newnumber: uint256): self.number = new_number @external def increment(): self.number += 1 这个示例同时展示了三件最重要的事： #pragma version 用来锁定编译器版本范围。 public(uint256) 会生成状态变量和自动 getter。 @external 让函数进入对外 ABI。","order":2,"section":"第一个合约"},{"id":"quickstart#compile-and-test","href":"/docs/quickstart#compile-and-test","title":"5 分钟快速开始","group":"入门","description":"使用 Moccasin 初始化、编译和测试第一个 Vyper 项目。","content":"编译与测试 编译项目： mox compile 运行测试： mox test 正常情况下你会看到 pytest 风格的通过输出。接下来建议立刻做两件事： 打开 src/ 里的示例合约，手动改一个状态变量和一个函数参数类型。 写一个最小测试，验证 getter 和状态更新是否符合预期。 这样你会比单纯阅读语法说明更快建立“Vyper 合约是怎么被编译和调用的”直觉。 常见问题 如果 mox 命令不可用，优先检查 shell 是否刷新；如果是 Python 版本不对， 先修复 Python 环境，再重新安装 Moccasin。","order":2,"section":"编译与测试"},{"id":"contract-structure","href":"/docs/contract-structure","title":"合约结构","group":"语言基础","description":"理解 pragma、导入、状态变量、函数、事件和接口的组织方式。","content":"理解 pragma、导入、状态变量、函数、事件和接口的组织方式。 Vyper 的一个重要特点是“文件结构即语义结构”。一份合约文件通常只描述一个合约， 并把版本约束、导入、状态、函数和事件都放在清晰可扫描的位置。","order":3},{"id":"contract-structure#pragmas","href":"/docs/contract-structure#pragmas","title":"合约结构","group":"语言基础","description":"理解 pragma、导入、状态变量、函数、事件和接口的组织方式。","content":"Pragma 与编译指令 把 pragma 放在最前面，是一个很好的习惯。 常见指令包括： #pragma version ^0.4.0 #pragma optimize gas #pragma evm-version prague version 控制编译器版本范围，帮助复现实验结果。 optimize 决定更偏向 gas 还是 codesize。 evm-version 把产物对齐到目标链的分叉规则。 如果你在源码里写了 pragma，同时又在 CLI 里传了冲突参数，编译会直接报错，而不是帮你“猜”最终意图。","order":3,"section":"Pragma 与编译指令"},{"id":"contract-structure#imports","href":"/docs/contract-structure#imports","title":"合约结构","group":"语言基础","description":"理解 pragma、导入、状态变量、函数、事件和接口的组织方式。","content":"导入与搜索路径 Vyper 支持 import 和 from ... import 两种形式： from . import math_utils from ..interfaces import IERC20 搜索导入文件时，编译器会先从当前合约所在目录开始，再看命令行传入的搜索路径。 如果你用 CLI 编译复杂项目，通常会显式传 -p： vyper -p contracts contracts/Vault.vy","order":3,"section":"导入与搜索路径"},{"id":"contract-structure#state-and-functions","href":"/docs/contract-structure#state-and-functions","title":"合约结构","group":"语言基础","description":"理解 pragma、导入、状态变量、函数、事件和接口的组织方式。","content":"状态与函数 状态变量定义在函数外部： owner: address stored_value: uint256 在函数内部通过 self 访问： @external def setvalue(nextvalue: uint256): self.storedvalue = nextvalue Vyper 不鼓励把逻辑拆散到太多隐式层级里，所以大多数行为最终都会落在可直接阅读的函数体中。","order":3,"section":"状态与函数"},{"id":"contract-structure#events-and-interfaces","href":"/docs/contract-structure#events-and-interfaces","title":"合约结构","group":"语言基础","description":"理解 pragma、导入、状态变量、函数、事件和接口的组织方式。","content":"事件与接口 事件写法非常直接： event Payment: amount: uint256 sender: indexed(address) @external @payable def pay(): log Payment(msg.value, msg.sender) 接口既可以单独放在 .vyi 文件，也可以通过模块和接口导入获得： from .interfaces import IERC20 @external def sweep(token: IERC20, to: address, amount: uint256): extcall token.transfer(to, amount) 阅读建议 当你审一份 Vyper 合约时，先看 pragma，再看 import，然后从状态变量扫到外部函数。 这种顺序通常能最快让你建立“这份合约控制了什么状态、暴露了什么能力”的整体图景。","order":3,"section":"事件与接口"},{"id":"types","href":"/docs/types","title":"类型系统","group":"语言基础","description":"掌握 Vyper 的静态类型、数值类型、数组、映射和显式转换规则。","content":"掌握 Vyper 的静态类型、数值类型、数组、映射和显式转换规则。 Vyper 是静态类型语言。变量、参数和返回值的类型必须在编译期明确， 这使得很多模糊行为在进入链上之前就会被拦住。值在赋值和传参时总是按值复制， 调用方永远不需要担心被调用方修改了传入的数据结构。","order":4},{"id":"types#type-discipline","href":"/docs/types#type-discipline","title":"类型系统","group":"语言基础","description":"掌握 Vyper 的静态类型、数值类型、数组、映射和显式转换规则。","content":"静态类型约束 Vyper 没有\"模糊子类型层级\"。类型之间不存在隐式继承关系， 每个值都必须通过显式的 convert() 来进行类型转换。 核心原则： 类型越明确，调用边界越清晰。 编译器知道得越多，运行时越不容易出现意外行为。 显式转换优先于隐式魔法。 关于可变性 内部函数的参数和局部变量可以被重新赋值（同类型），数组和结构体也支持就地修改成员。 但外部函数的参数是不可变的——既不能重新赋值，也不能修改成员。 Boolean 关键字： bool 布尔值只有 True 和 False 两个取值。 运算符 说明 --- --- not x 逻辑取反 x and y 逻辑与 x or y 逻辑或 x == y 相等 x != y 不等 and 和 or 遵循短路求值，与 Python 行为一致。 有符号整数（intN） 关键字： intN（例如 int128） 可存储正数和负数。N 是 8 到 256 之间的 8 的倍数。 取值范围为 -2^(N-1) 到 2^(N-1) - 1。 delta: int128 = -5 big: int256 = -999999999 比较运算符： <、<=、==、!=、>=、>（两端必须类型相同） 算术运算符： 运算符 说明 --- --- x + y 加法 x - y 减法 -x 取负 x * y 乘法 x // y 整数除法 x y 指数运算 x % y 取模 位运算符： &、 、^（两端必须类型相同） 移位运算符： <<、>>（仅对 int256 可用，y 为无符号整数。int256 的右移会编译为 EVM 的 SAR 有符号右移指令） 整数除法的舍入方向 Vyper 的整数除法向零舍入，这与 Python 不同（Python 向负无穷舍入）。 例如 -1 // 2 在 Vyper 中返回 0，在 Python 中返回 -1。 这一设计保证了 (x // y) * y + (x % y) == x 恒成立。 无符号整数（uintN） 关键字： uintN（例如 uint256、uint8） 只能存储非负整数。N 是 8 到 256 之间的 8 的倍数。 取值范围为 0 到 2^N - 1。 counter: uint256 = 0 small: uint8 = 255 运算符与有符号整数相同，额外支持 ~x（按位取反，目前仅 uint256 可用）。 移位运算仅对 uint256 可用，右移编译为 EVM 的 SHR 无符号右移指令。 字面量的默认类型 整数字面量默认被解释为 int256。当赋值目标类型明确时（例如 x: uint8 = 1）， 编译器会自动适配。如需显式指定，使用 convert(literal, uint8)。 Decimal（十进制定点数） 关键字： decimal 从 v0.4.0 起，使用 decimal 需要通过 CLI 标志 --enable-decimals 显式启用。 精度为 10 位小数。ABI 类型为 int168。 字面量必须包含小数点才能被解释为 decimal。 price: decimal = 0.1 rate: decimal = 3.14 算术运算符： +、-、-x（取负）、*、/（注意是十进制除法，不是整数除法）、% 比较运算符与整数类型一致。 Address 关键字： address 存储一个 20 字节的以太坊地址。地址字面量必须使用 0x 前缀的十六进制格式，并通过 EIP-55 校验和验证。 owner: address = 0x1234567890123456789012345678901234567890 地址成员 成员 类型 说明 --- --- --- balance uint256 地址余额 codehash bytes32 地址上代码的 keccak 哈希（无合约时返回特定常量值） codesize uint256 部署代码的字节大小 is_contract bool 地址上是否部署了合约 code Bytes 合约字节码 访问方式：address.balance、address.codesize 等。 注意 SELFDESTRUCT 和 CREATE2 可以移除或替换某个地址上的字节码。 不要假设地址成员值永远不变。_address.code 需要配合 slice() 使用来截取特定片段。 固定字节数组（bytesM） 关键字： bytesM（例如 bytes32、bytes4） M 字节宽的固定大小字节数组。在 ABI 层表示为 bytesM。 hash: bytes32 somemethodid: bytes4 = 0x01abcdef 常用操作包括 keccak256(x)、concat(x, ...)、slice(x, start, length)。 动态字节数组（Bytes） 关键字： Bytes 语法为 Bytes[maxLen]，其中 maxLen 是最大字节数。ABI 层表示为 bytes。 bytes_string: Bytes[100] = b\"\\x01\" hex_bytes: Bytes[100] = x\"01\" 字符串（String） 关键字： String 固定最大长度的字符串类型。实际内容可以短于最大长度。ABI 层表示为 string。 example_str: String[100] = \"Test String\" Flag（标志枚举） 关键字： flag 自定义枚举类型，至少 1 个成员，最多 256 个。 成员值为 uint256，形式为 2^n，其中 n 为成员在 0 到 255 范围内的索引。 flag Roles: ADMIN USER role: Roles = Roles.ADMIN 比较运算符： ==、!=、in、not in 位运算符： &、 、^、~ 成员组合可以通过位运算操作。in 和 not in 可以检查成员是否存在于某个组合中： flag Roles: MANAGER ADMIN USER @external def foo(a: Roles) -> bool: return a in (Roles.MANAGER Roles.USER) in 与 == 的区别 in 检查两个 flag 对象是否有任何共同设置的位，而 == 检查两个 flag 对象是否逐位完全相同。 位运算还可用于添加和撤销权限： @external def add_user(a: Roles) -> Roles: ret: Roles = a ret = Roles.USER # 设置 USER 位为 1 return ret @external def revoke_user(a: Roles) -> Roles: ret: Roles = a ret &= ~Roles.USER # 设置 USER 位为 0 return ret","order":4,"section":"静态类型约束"},{"id":"types#scalar-types","href":"/docs/types#scalar-types","title":"类型系统","group":"语言基础","description":"掌握 Vyper 的静态类型、数值类型、数组、映射和显式转换规则。","content":"标量类型速查 以下是所有基础标量类型的快速参考： 类型 说明 默认值 --- --- --- bool 布尔值，True 或 False False intN 有符号整数，N 为 8~256 的 8 倍数 0 uintN 无符号整数，N 为 8~256 的 8 倍数 0 decimal 十进制定点数，10 位精度 0.0 address 20 字节以太坊地址 0x000...000 bytesM M 字节固定字节数组（M 为 1~32） 全零 Bytes[N] 最大 N 字节的动态字节数组 全零 String[N] 最大 N 字符的字符串 空","order":4,"section":"标量类型"},{"id":"types#collections","href":"/docs/types#collections","title":"类型系统","group":"语言基础","description":"掌握 Vyper 的静态类型、数值类型、数组、映射和显式转换规则。","content":"集合类型 固定长度数组 语法为 name: ValueType[_Integer]（不支持 Bytes[N]、String[N] 和 flag 作为元素类型）。 exampleList: int128[3] 赋值 exampleList = [10, 11, 12] exampleList[2] = 42 访问 return exampleList[0] 多维数组的声明顺序与访问顺序是反过来的： # 声明：2 行 5 列 exampleList2D: int128[5][2] = empty(int128[5][2]) 访问：[行索引][列索引] exampleList2D[0][4] = 42 安全提示 在存储中定义大小远超 2^64 的数组可能因溢出风险导致安全漏洞。 动态数组（DynArray） 运行时可变长度的有界数组，声明语法为 DynArray[Type, Integer]。 exampleList: DynArray[int128, 3] exampleList = [] exampleList.append(42) # 长度变为 1 exampleList.append(120) # 长度变为 2 exampleList.append(356) # 长度变为 3 exampleList.append(1) # 会 revert！已满 myValue: int128 = exampleList.pop() # myValue == 356，长度变为 2 关键限制： 越界访问、对空数组 pop() 或对满数组 append() 都会触发 REVERT。 迭代数组时不能修改数组内容。 ABI 表示为 _Type[]，例如 DynArray[int128, 3] 表示为 int128[]。 Struct（结构体） 自定义复合类型，可组合多个字段。结构体可嵌套数组和其他结构体，但不能包含映射。 struct MyStruct: value1: int128 value2: decimal exampleStruct: MyStruct = MyStruct(value1=1, value2=2.0) exampleStruct.value1 = 1 HashMap（映射） 哈希表类型，虚拟初始化为所有可能的键都映射到类型的零值。键的数据本身不存储， 只用其 keccak256 哈希来查找值。 exampleMapping: HashMap[int128, decimal] exampleMapping[0] = 10.1 _KeyType 可以是任何基础类型或字节类型，不支持映射、数组或结构体作为键。 _ValueType 可以是任何类型，包括映射（嵌套映射）。 映射只能作为状态变量声明。 映射没有\"长度\"概念，不能被迭代。 初始值 Vyper 没有 null 概念。每种类型都有默认的零值。 检查变量是否为空需要与对应类型的默认值比较。 使用内建的 empty() 函数可以将变量重置为默认值。 类型 默认值 --- --- address 0x0000000000000000000000000000000000000000 bool False bytes32 0x00...00（64 个零） decimal 0.0 uint8 0 int128 0 int256 0 uint256 0 内存变量必须初始化 内存变量在声明时必须赋初始值。引用类型的所有成员会被递归初始化为各自的默认值。","order":4,"section":"集合类型"},{"id":"types#conversion-rules","href":"/docs/types#conversion-rules","title":"类型系统","group":"语言基础","description":"掌握 Vyper 的静态类型、数值类型、数组、映射和显式转换规则。","content":"转换规则 Vyper 的所有类型转换必须通过 convert(a, btype) 显式完成。 转换被设计为安全且直观的——所有转换都会检查输入是否在输出类型的有效范围内。 x: uint256 = 100 y: int256 = convert(x, int256) who: address = 0x1234567890123456789012345678901234567890 whoasnum: uint160 = convert(who, uint160) 转换原则 核心规则总结： 规则 说明 --- --- 位保留 除涉及 decimal 和 bool 的转换外，输入的位表示被原样保留 Bool 转换 所有非零输入映射为 True（1） Decimal → 整数 向零截断 Address 处理 地址被视为 uint160，但不允许与有符号整数或 decimal 互转 右填充 ↔ 左填充 bytes/Bytes/String（右填充）与左填充类型之间的转换会旋转字节 有符号 ↔ 无符号 输入为负数时会 revert 窄化转换 例如 int256 → int128 会检查输入是否在目标范围内 字节 → 有符号整数 会进行符号扩展，例如 bytes1 的 0xff 转为 int8 返回 -1 跨宽度字节/整数转换 先经过最近的整数类型，例如 bytes1 → int16 等同于 bytes1 → int8 → int16 Flag 转换 只能与 uint256 互相转换 实践建议 对不会损失精度的放宽转换（例如 uint8 → uint256）通常可以自动完成。 只要涉及地址、符号位、精度或截断风险，就显式写 convert()。 这虽然多敲几个字，但能显著降低审计时的歧义。","order":4,"section":"转换规则"},{"id":"constants-and-vars","href":"/docs/constants-and-vars","title":"环境变量与常量","group":"语言基础","description":"掌握区块链上下文变量、self 引用，以及全局常量的定义方式。","content":"掌握区块链上下文变量、self 引用，以及全局常量的定义方式。 这一页定义了 Vyper 里最常用的上下文信息来源： 区块链环境变量、self 引用，以及在模块级定义的自定义常量。","order":5},{"id":"constants-and-vars#environment-variables","href":"/docs/constants-and-vars#environment-variables","title":"环境变量与常量","group":"语言基础","description":"掌握区块链上下文变量、self 引用，以及全局常量的定义方式。","content":"环境变量 环境变量始终存在于命名空间中，主要用于暴露当前区块、交易和调用上下文的信息。 它们不需要声明，也不能被重写。 区块与交易属性 名称 类型 含义 --- --- --- block.coinbase address 当前区块出块者地址 block.difficulty uint256 当前区块难度 block.prevrandao bytes32 Beacon 链提供的随机性信标 block.number uint256 当前区块号 block.gaslimit uint256 当前区块 gas 上限 block.basefee uint256 当前区块的 base fee block.blobbasefee uint256 当前区块 blob gas 的 base fee block.prevhash bytes32 等价于 blockhash(block.number - 1) block.timestamp uint256 当前区块的 Unix 时间戳 chain.id uint256 当前链的 Chain ID msg.data Bytes 调用携带的 calldata msg.gas uint256 当前剩余 gas msg.mana uint256 msg.gas 的别名 msg.sender address 当前调用的直接发送者 msg.value uint256 随消息发送的 wei 数量 tx.origin address 整条调用链的原始交易发送者 tx.gasprice uint256 当前交易的 gas price（wei） 关于 block.prevrandao block.prevrandao 是 block.difficulty 操作码的别名。根据 EIP-4399，在 2022 年 9 月 15 日 以太坊完成 Paris 升级（The Merge）之后，block.difficulty 已被视为弃用语义， 新代码应优先使用 block.prevrandao。 msg.data 需要配合 slice() 显式截取 calldata 片段。如果截取范围超出边界会直接抛错， 长度则可以通过 len(msg.data) 判断。 @external def firstfourbytes() -> Bytes[4]: assert len(msg.data) >= 4, \"calldata too short\" return slice(msg.data, 0, 4)","order":5,"section":"环境变量"},{"id":"constants-and-vars#self-variable","href":"/docs/constants-and-vars#self-variable","title":"环境变量与常量","group":"语言基础","description":"掌握区块链上下文变量、self 引用，以及全局常量的定义方式。","content":"self 变量 self 是一个特殊环境变量，用来在合约内部引用当前合约自身。它既能表示当前合约地址， 也能访问状态变量和内部函数。 名称 类型 含义 --- --- --- self address 当前合约地址 self.balance uint256 当前合约余额 访问状态变量 在 Vyper 中，函数内部访问状态变量必须显式写成 self. ： stored_value: uint256 @external def set_value(value: uint256) -> bool: self.stored_value = value return True @external @view def get_value() -> uint256: return self.stored_value 调用内部函数 self 也用于调用内部函数，这能明确区分“当前合约内部逻辑”与“外部合约调用”： @internal def timestwo(amount: uint256) -> uint256: return amount * 2 @external def calculate(amount: uint256) -> uint256: return self.timestwo(amount)","order":5,"section":"self 变量"},{"id":"constants-and-vars#custom-constants","href":"/docs/constants-and-vars#custom-constants","title":"环境变量与常量","group":"语言基础","description":"掌握区块链上下文变量、self 引用，以及全局常量的定义方式。","content":"自定义常量 Vyper 允许在模块级定义自定义常量。常量使用 constant(...) 关键字声明， 一旦定义就不能被修改。 TOTALSUPPLY: constant(uint256) = 10000_000 total_supply: public(uint256) @deploy def init(): self.totalsupply = TOTALSUPPLY 常量适合保存以下内容： 协议固定参数，例如费率分母、最大供应量、精度缩放因子。 与链无关、部署后不会变化的配置。 希望在编译期就被内联并固定下来的值。 如果某个值是在部署时确定，但部署后不再变化，应优先考虑 immutable。 它的约束与写法在作用域与声明里单独展开。","order":5,"section":"自定义常量"},{"id":"constants-and-vars#practical-guidelines","href":"/docs/constants-and-vars#practical-guidelines","title":"环境变量与常量","group":"语言基础","description":"掌握区块链上下文变量、self 引用，以及全局常量的定义方式。","content":"使用建议 对随机性或区块属性敏感的逻辑，优先先判断输入边界，再读取环境变量。 除了读地址和余额，self 最重要的用途其实是明确“这是状态访问”或“这是内部调用”。 对不会变化的协议参数使用 constant，对部署时确定的配置使用 immutable，不要把两者混用。 谨慎使用 tx.origin。它暴露的是整条调用链最初的发送者，通常不适合作为权限控制依据。","order":5,"section":"使用建议"},{"id":"statements","href":"/docs/statements","title":"语句与异常处理","group":"语言基础","description":"理解 break、continue、return、log、raise 和 assert 的语义边界。","content":"理解 break、continue、return、log、raise 和 assert 的语义边界。 这一页主要补齐了 Vyper 的基础语句模型：循环控制、事件日志、 以及 raise 和 assert 触发的异常语义。","order":6},{"id":"statements#control-flow","href":"/docs/statements#control-flow","title":"语句与异常处理","group":"语言基础","description":"理解 break、continue、return、log、raise 和 assert 的语义边界。","content":"控制流语句 Vyper 的语句风格和 Python 很接近，但会更强调“可静态分析”和“不会留下隐藏路径”。 break break 会终止最近一层 for 循环： for i: uint256 in [1, 2, 3, 4, 5]: if i == a: break 当 i == a 时，循环立即结束。 continue continue 会直接开始最近一层 for 循环的下一次迭代： for i: uint256 in [1, 2, 3, 4, 5]: if i != a: continue ... 当条件不满足时，当前轮次后续逻辑不会执行。 pass pass 是空操作，占位但不执行任何代码： @external def foo(): pass 它适合在语法上需要语句、但实现暂时留空的地方使用。 return return 会结束当前函数调用，并把表达式列表作为返回值返回： return return_value 没有返回类型的函数可以省略 return。 有返回类型的函数必须以 return 或其他终止动作（例如 raise）结束。 return 之后不能再写不可达语句，编译器会直接拒绝。","order":6,"section":"控制流语句"},{"id":"statements#log-statement","href":"/docs/statements#log-statement","title":"语句与异常处理","group":"语言基础","description":"理解 break、continue、return、log、raise 和 assert 的语义边界。","content":"log 语句 Vyper 使用 log 记录事件： log MyEvent(...) 对应事件必须先声明。一个最小示例： event Deposit: sender: indexed(address) amount: uint256 @external @payable def deposit(): log Deposit(msg.sender, msg.value) 参数求值顺序 传给 log 的参数求值顺序是未定义的。编译器可以按任意顺序计算这些表达式。 如果参数带有副作用，应先在独立语句中算好，再传给 log，避免行为依赖求值顺序。","order":6,"section":"log 语句"},{"id":"statements#exceptions","href":"/docs/statements#exceptions","title":"语句与异常处理","group":"语言基础","description":"理解 break、continue、return、log、raise 和 assert 的语义边界。","content":"异常机制 Vyper 使用会回滚状态的异常来处理错误。异常会触发 REVERT 操作码（0xFD）， 并把提供的 reason string 作为错误消息。 当异常发生时： 当前调用立即停止。 合约状态回滚到交易执行前。 剩余 gas 返还给调用者。 如果错误发生在子调用里，会自动向上冒泡。 有一个特殊例外：如果 reason string 被设置为 UNREACHABLE，编译器会使用 INVALID 操作码（0xFE）而不是 REVERT。这种做法通常不建议在日常开发里使用， 它主要用于兼容某些依赖 INVALID 做动态分析的工具链。","order":6,"section":"异常机制"},{"id":"statements#assert-and-raise","href":"/docs/statements#assert-and-raise","title":"语句与异常处理","group":"语言基础","description":"理解 break、continue、return、log、raise 和 assert 的语义边界。","content":"assert 与 raise raise raise 会主动抛出异常并回滚当前调用： raise \"something went wrong\" 错误字符串不是必填项；如果提供，长度上限为 1024 字节。 assert assert 用于声明某个条件必须成立，否则交易回滚： assert x > 5, \"value too low\" 错误字符串同样可选，长度上限也是 1024 字节。 它的语义等价于： if not cond: raise \"reason\" 实践上可以这样区分： 用 assert 写必须成立的不变量、输入校验和权限前置条件。 用 raise 表达“直接终止并返回错误”的显式分支。 只在确实需要时才写错误字符串，保持错误信息稳定、可读且简短。","order":6,"section":"assert 与 raise"},{"id":"control-structures","href":"/docs/control-structures","title":"函数与控制流","group":"语言基础","description":"理解可见性、可变性、构造函数、循环、断言和重入保护。","content":"理解可见性、可变性、构造函数、循环、断言和重入保护。 在 Vyper 里，函数定义和控制流几乎就是安全边界本身。可见性、可变性、循环上界和重入保护， 都直接体现在语法层里，而不是藏在惯例或辅助库中。","order":7},{"id":"control-structures#visibility","href":"/docs/control-structures#visibility","title":"函数与控制流","group":"语言基础","description":"理解可见性、可变性、构造函数、循环、断言和重入保护。","content":"函数可见性 Vyper 有三种可见性级别： 标记 含义 --- --- @external 进入 ABI 选择器表，可被外部交易或其他合约调用 @internal（默认） 只在合约内部可用，外部调用者不可访问 @deploy 构造阶段执行，目前仅用于 init() 外部函数 外部函数是合约接口的一部分，只能通过交易或外部合约调用。 @external def add_seven(a: int128) -> int128: return a + 7 @external def addwithdefault(a: uint256, b: uint256 = 3) -> uint256: return a + b 一个 Vyper 合约不能在两个外部函数之间直接互调。如果确实需要，可以通过接口实现。 默认参数与 ABI 对于带默认参数的外部函数（如 def my_func(x: uint256, b: uint256 = 1)）， 编译器会基于 N 个默认参数生成 N+1 个重载函数选择器。 例如 withdraw(uint256,address,address) 和 withdraw(uint256) 是两个不同的选择器。 内部函数 内部函数通过 self 对象调用： def timestwo(amount: uint256) -> uint256: return amount * 2 @external def calculate(amount: uint256) -> uint256: return self.timestwo(amount) 从导入模块调用内部函数时，使用模块名前缀： @external def calculate(amount: uint256) -> uint256: return calculatorlibrary.times_two(amount) 标记内部函数为 @payable 表示它可以访问 msg.value。一个 @nonpayable 的内部函数可以被外部 @payable 函数调用，但它自身无法访问 msg.value。 从 v0.4.0 起，@internal 装饰器是可选的——没有可见性装饰器的函数默认为内部函数。 构造函数（init） init() 是特殊的初始化函数，仅在部署时调用一次。必须使用 @deploy 装饰器： owner: address @deploy def init(): self.owner = msg.sender 常见用途： 初始化 owner 或管理员地址 设置 immutable 变量（immutable 变量只能在构造函数中赋值） 调用已初始化模块的构造逻辑","order":7,"section":"函数可见性"},{"id":"control-structures#mutability","href":"/docs/control-structures#mutability","title":"函数与控制流","group":"语言基础","description":"理解可见性、可变性、构造函数、循环、断言和重入保护。","content":"函数可变性 可变性标记描述函数与状态和 ETH 的交互方式： 标记 含义 --- --- @pure 不读合约状态，也不读环境变量 @view 可读状态，不改状态 @nonpayable（默认） 可读写状态，但不能接收 ETH @payable 可读写状态，可接收和访问 msg.value @view @external def readonly(): # 不能写状态 ... @payable @external def sendmemoney(): # 可以接收 ETH ... @view 函数不能调用可变（@payable 或 @nonpayable）函数。所有外部调用使用 STATICCALL 操作码。 @pure 函数不能调用非 @pure 函数。 函数默认为 @nonpayable。 内部函数的 nonpayable 行为 @nonpayable 在内部函数上不是严格强制的。外部 @payable 函数可以调用内部 @nonpayable 函数， 但该内部函数无法访问 msg.value。","order":7,"section":"函数可变性"},{"id":"control-structures#constructor","href":"/docs/control-structures#constructor","title":"函数与控制流","group":"语言基础","description":"理解可见性、可变性、构造函数、循环、断言和重入保护。","content":"重入保护与装饰器 @nonreentrant @nonreentrant 装饰器为函数设置全局重入锁。当任何 @nonreentrant 函数正在执行时， 外部合约回调到同合约的任何其他 @nonreentrant 函数都会导致交易回滚。 @external @nonreentrant def makeacall(_addr: address): # 此函数受重入保护 ... 工作原理：在函数入口将特定存储槽设为\"锁定\"值，在出口设为\"解锁\"值。 入口检测到\"锁定\"状态时直接 revert。 使用限制： 不能放在 @pure 函数上 可以放在 @view 函数上（仅检查锁状态，不修改） 可以放在 default 函数上（但会导致合约拒绝来自回调的 ETH 转账） 不允许从一个 @nonreentrant 函数调用另一个 @nonreentrant 函数 使用 vyper -f layout 可以查看重入锁在存储中的物理位置。默认分配在 slot 0。 重入锁的 gas 成本 解锁值为 3，锁定值为 2。使用非零值是为了利用 Berlin 硬分叉后的 net gas metering， 重入锁的净成本约为 2300 gas。0.3.4 之前的解锁/锁定值是 0 和 1。 nonreentrancy pragma 从 0.4.2 起，#pragma nonreentrancy on 可以为文件中所有外部函数和公共 getter 自动启用重入保护 （constant 和 immutable 的 getter 除外）。 # pragma nonreentrancy on x: public(uint256) # 受重入保护 y: public(reentrant(uint256)) # 不受保护 @external def makeacall(addr: address): # 自动受重入保护 ... @external @reentrant def callback(addr: address): # 显式允许重入 ... 默认为 #pragma nonreentrancy off pragma 的作用域限于当前文件 导入的文件不受当前文件 pragma 的影响 装饰器完整参考 装饰器 说明 --- --- @external 函数可被外部调用，进入运行时选择器表 @internal 函数只能在当前合约内调用 @deploy 仅在部署时调用 @pure 不读合约状态或环境变量 @view 不修改合约状态 @payable 可接收 ETH @nonreentrant 不能在外部调用期间被回调 @raw_return 返回原始字节，不做 ABI 编码（仅 @external 函数） @raw_return @raw_return 装饰器让函数直接返回原始字节，跳过 ABI 编码。适用于代理合约和需要原样转发返回数据的场景。 @external @payable @raw_return def forward_call(target: address) -> Bytes[1024]: return rawcall(target, msg.data, maxoutsize=1024, value=msg.value, isdelegatecall=True) 限制： 只能用在 @external 函数上 返回类型必须是 Bytes[N] 不能用在构造函数上（但可以用在 default() 上） 不能用在 @internal 函数上 不能在接口定义（.vyi 文件）中使用 注意 调用 @rawreturn 函数时应使用 rawcall 而非接口调用，因为返回数据不是 ABI 编码的。 default 函数 默认函数在没有匹配的函数选择器时执行（包括直接发送 ETH 的情况）。 相当于 Solidity 中的 fallback 和 receive 的组合。 event Payment: amount: uint256 sender: indexed(address) @external @payable def default(): log Payment(msg.value, msg.sender) 注意事项： 必须标记为 @external，不能接受参数 如果标记为 @payable，合约可以接收纯 ETH 转账 如果没有定义 default，编译器会生成一个 REVERT 版本 send 调用只附带 2300 gas stipend，能做的事很有限（写存储、创建合约、外部调用都超过这个限额） 虽然不接受参数，但可以访问 msg.sender、msg.value、msg.gas","order":7,"section":"重入保护与装饰器"},{"id":"control-structures#loops-and-guards","href":"/docs/control-structures#loops-and-guards","title":"函数与控制流","group":"语言基础","description":"理解可见性、可变性、构造函数、循环、断言和重入保护。","content":"循环与控制流 if 语句 if CONDITION: ... elif OTHER_CONDITION: ... else: ... 与 Python 不同，Vyper 不允许非布尔类型在 if 条件中隐式转换。if 1: pass 会编译失败。 for 循环 Vyper 的循环必须有编译期可知的上界： 数组迭代 foo: int128[3] = [4, 23, 42] for i: int128 in foo: ... 也可以迭代字面量数组 for i: int128 in [4, 23, 42]: ... 限制：不能迭代多维数组；迭代期间不能修改被迭代的数组。 range 迭代 # 固定上界 for i: uint256 in range(100): ... 变量上界 + 编译期 bound for i: uint256 in range(stop, bound=100): ... 起止范围（固定字面量） for i: uint256 in range(10, 20): ... 运行时起止 + 编译期 bound for i: uint256 in range(start, end, bound=100): ... 当 stop 可能小于 bound 时，使用 range(min(stop, N), bound=N) 来避免运行时 revert。 这对将大数组操作分块到多个交易中特别有用。 assert 和 raise @external @nonreentrant def withdraw(): assert msg.sender == self.owner, \"Not owner\" ... @external def restricted(): raise \"Operation not allowed\" assert：条件为 false 时回滚，可附带错误消息 raise：直接回滚 print（调试） x: uint256 = 42 print(x, \"hello\") print 通过静态调用 console 地址 (0x000000000000000000636F6E736F6C652E6C6F67) 实现。 默认模式与 titanoboa 兼容，使用 hardhat_compat=True 可适配 Hardhat。 控制流的审计视角 当你读一段 Vyper 代码时，优先找 @external、@payable、@nonreentrant 和 extcall。 这些标记几乎总能快速暴露资金流和风险边界。","order":7,"section":"循环与控制流"},{"id":"scoping-and-declarations","href":"/docs/scoping-and-declarations","title":"作用域与声明","group":"语言基础","description":"理解变量声明、public 与 immutable、存储布局，以及模块和块级作用域规则。","content":"理解变量声明、public 与 immutable、存储布局，以及模块和块级作用域规则。 这一页把 Vyper 的声明模型讲得很完整： 变量在哪声明、何时必须初始化、哪些名字不能重影，以及存储槽如何稳定布局。","order":8},{"id":"scoping-and-declarations#variable-declaration","href":"/docs/scoping-and-declarations#variable-declaration","title":"作用域与声明","group":"语言基础","description":"理解变量声明、public 与 immutable、存储布局，以及模块和块级作用域规则。","content":"变量声明 变量第一次被引用之前，必须先声明它的类型： data: int128 不同作用域下，初始化规则不同： 存储变量：声明在模块作用域，不能在声明时直接赋初值。 内存变量：声明在函数内部，必须在声明时赋初值。 calldata 变量：作为函数参数出现时，可以提供默认值。 元组赋值 Vyper 不能直接声明“元组类型”，但在某些赋值场景里可以使用字面量元组， 最常见的是接收多返回值： @internal def foo() -> (int128, int128): return 2, 3 @external def bar(): a: int128 = 0 b: int128 = 0 (a, b) = self.foo() a, b = self.foo()","order":8,"section":"变量声明"},{"id":"scoping-and-declarations#public-and-immutable","href":"/docs/scoping-and-declarations#public-and-immutable","title":"作用域与声明","group":"语言基础","description":"理解变量声明、public 与 immutable、存储布局，以及模块和块级作用域规则。","content":"public 与 immutable public 存储变量声明时可以标记为 public： data: public(int128) 编译器会自动为它生成 getter。上面的写法等价于“声明一个状态变量 data， 并额外生成一个返回 int128 的外部函数 data()”。 对于公共数组，自动 getter 只能读取单个元素，而不能一次性返回整个数组， 这样可以避免返回整数组带来的高 gas 成本： values: public(uint256[3]) 这里自动 getter 的调用形式类似 values(0)。 immutable 变量也可以声明为 immutable： OWNER: immutable(address) @deploy def init(owner: address): OWNER = owner immutable 与常量很像，但它的值是在构造阶段写入，而不是在源码里直接固定。 约束是： 必须在构造期间赋值。 部署完成后不能再次赋值。 它适合“部署时决定、部署后不变”的配置。 编译器生成创建代码时，会在返回运行时代码之前，把所有 immutable 的值追加到运行时代码中。 因此，如果你拿编译器输出的 runtime bytecode 与链上实际 bytecode 做逐字节比较， 需要把这一步差异考虑进去。","order":8,"section":"public 与 immutable"},{"id":"scoping-and-declarations#storage-layout","href":"/docs/scoping-and-declarations#storage-layout","title":"作用域与声明","group":"语言基础","description":"理解变量声明、public 与 immutable、存储布局，以及模块和块级作用域规则。","content":"存储布局 Vyper 会把存储变量分配到确定的 storage slot 中。默认情况下，第一项从 slot 0 开始， 后续变量按顺序继续分配。 升级合约时，经常需要显式覆盖这个布局，确保旧合约和新合约把同一个变量放在同一个 slot。 Vyper 通过 --storage-layout-file 支持这一点。 旧合约： # old_contract.vy owner: public(address) balanceOf: public(HashMap[address, uint256]) 新合约： # new_contract.vy owner: public(address) minter: public(address) balanceOf: public(HashMap[address, uint256]) 如果直接按默认顺序编译，balanceOf 会从旧合约的 slot 1 变成新合约的 slot 2， 从而破坏升级兼容性。 可以通过下面的方式固定布局： vyper newcontract.vy --storage-layout-file newcontract_storage.json { \"owner\": { \"type\": \"address\", \"n_slots\": 1, \"slot\": 0 }, \"minter\": { \"type\": \"address\", \"n_slots\": 1, \"slot\": 2 }, \"balanceOf\": { \"type\": \"HashMap[address, uint256]\", \"n_slots\": 1, \"slot\": 1 } } 这里的 n_slots 表示从给定 slot 偏移开始，应该为该变量预留多少个 32 字节槽位。","order":8,"section":"存储布局"},{"id":"scoping-and-declarations#scoping-rules","href":"/docs/scoping-and-declarations#scoping-rules","title":"作用域与声明","group":"语言基础","description":"理解变量声明、public 与 immutable、存储布局，以及模块和块级作用域规则。","content":"作用域规则 Vyper 采用 C99 风格作用域：变量从声明之后开始可见，直到包含该声明的最小代码块结束。 模块作用域 在代码块之外声明的内容，例如状态变量、函数、常量、事件和结构体， 即使在源码里写在后面，也可以在前面被引用。 函数内部访问模块作用域中的状态变量和函数时，要通过 self： a: int128 @internal def foo() -> int128: return 42 @external def bar() -> int128: b: int128 = self.foo() return self.a + b 名称遮蔽 内存变量和 calldata 变量不能与 constant 或 immutable 同名。 下面两种情况都不会通过编译： a: constant(bool) = True @external def foo() -> bool: a: bool = False return a a: immutable(bool) @deploy def init(): a = True @external def foo(a: bool) -> bool: return a 函数作用域 函数内声明的变量、以及函数参数，只在当前函数体中可见。 不同函数里重复使用同名参数是允许的： @external def foo(a: int128): pass @external def bar(a: uint256): pass @external def baz(): a: bool = True 下面这些写法则会失败： @external def foo(a: int128): a: int128 = 21 @external def foo(a: int128): a = 4 @external def bar(): a += 12 块级作用域 if 和 for 创建的逻辑块也有各自的作用域。 @external def foo(a: bool) -> int128: if a: x: int128 = 3 else: x: bool = False for 的目标变量只在循环内部存在： @external def foo(a: bool) -> int128: for i: int128 in [1, 2, 3]: pass i: bool = False 下面的例子不会通过编译，因为循环里声明的 a 在循环外不可见： @external def foo(a: bool) -> int128: for i: int128 in [1, 2, 3]: a: int128 = i a += 3","order":8,"section":"作用域规则"},{"id":"built-ins","href":"/docs/built-ins","title":"内建函数与底层能力","group":"进阶","description":"理解 raw_call、raw_create 与 proxy / blueprint 等底层原语的边界。","content":"理解 raw_call、raw_create 与 proxy / blueprint 等底层原语的边界。 Vyper 没有把底层能力藏起来，它只是要求你通过显式内建函数去使用这些能力。 本页按功能分类列出所有内建函数，覆盖密码学、数据操作、数学运算、 合约创建、底层调用和实用工具。","order":9},{"id":"built-ins#bitwise-and-deprecations","href":"/docs/built-ins#bitwise-and-deprecations","title":"内建函数与底层能力","group":"进阶","description":"理解 raw_call、raw_create 与 proxy / blueprint 等底层原语的边界。","content":"位运算与弃用项 从 0.3.8 起，shift() 已弃用，改用 << 和 >> 运算符。 从 0.3.4 起，bitwiseand、bitwiseor、bitwisexor、bitwisenot 已被对应运算符 &、 、^、~ 替代，并在 0.4.2 中完全移除。 方向很一致：让代码读起来更直接，不需要记住额外的 API 名称。","order":9,"section":"位运算与弃用项"},{"id":"built-ins#contract-creation","href":"/docs/built-ins#contract-creation","title":"内建函数与底层能力","group":"进阶","description":"理解 raw_call、raw_create 与 proxy / blueprint 等底层原语的边界。","content":"合约创建原语 Vyper 提供四种合约创建方式，各有不同的创建成本、调用开销和构造函数支持： 能力 适用场景 关键特征 --- --- --- createminimalproxy_to() 低成本克隆 创建便宜（仅部署 EIP-1167 转发字节码），调用有 DELEGATECALL 开销 createcopyof() 复制运行时代码 逐字节复制，调用无额外开销，但创建较贵（200 gas/字节） createfromblueprint() 基于 blueprint 部署 支持构造函数，需要预部署 blueprint 合约 raw_create() 底层 create/create2 自由度最高，直接传入 initcode createminimalproxy_to createminimalproxy_to( target: address, value: uint256 = 0, revertonfailure: bool = True, [salt: bytes32] ) -> address 部署一个 EIP-1167 兼容的最小代理合约。代理合约拥有独立状态，但逻辑通过 DELEGATECALL 委托给 target。 @external def foo(target: address) -> address: return createminimalproxy_to(target) 安全提示 确保 target 合约是你已知且可信的代码，不实现 selfdestruct 也没有可升级逻辑。 编译器不会检查 target 地址是否已有代码（支持反事实部署），多数应用应自行添加此检查。 createcopyof createcopyof( target: address, value: uint256 = 0, revertonfailure: bool = True, [salt: bytes32] ) -> address 创建 target 运行时代码的物理副本。会执行 EXTCODESIZE 检查确认 target 有代码。 createfromblueprint createfromblueprint( target: address, *args, value: uint256 = 0, raw_args: bool = False, code_offset: int = 3, revertonfailure: bool = True, [salt: bytes32] ) -> address 将 target 的代码作为 initcode 执行。*args 作为构造函数参数进行 ABI 编码。 @external def foo(blueprint: address) -> address: arg1: uint256 = 18 arg2: String[32] = \"some string\" return createfromblueprint(blueprint, arg1, arg2, code_offset=1) Blueprint 部署 使用 vyper -f blueprint_bytecode 生成 ERC-5202 兼容的 blueprint 字节码。 推荐使用 0xFE7100 前缀来防止 blueprint 被当作普通合约调用。 code_offset 默认为 3（对应 ERC-5202 前缀长度）。 raw_create raw_create( initcode: Bytes[...], *args, value: uint256 = 0, revertonfailure: bool = True, [salt: bytes32] ) -> address 底层创建，直接传入 initcode 字节。*args 会被 ABI 编码后拼接到 initcode 后面。","order":9,"section":"合约创建原语"},{"id":"built-ins#low-level-calls","href":"/docs/built-ins#low-level-calls","title":"内建函数与底层能力","group":"进阶","description":"理解 raw_call、raw_create 与 proxy / blueprint 等底层原语的边界。","content":"底层调用与日志 raw_call raw_call( to: address, data: Bytes, max_outsize: uint256 = 0, gas: uint256 = gasLeft, value: uint256 = 0, isdelegatecall: bool = False, isstaticcall: bool = False, revertonfailure: bool = True ) -> Bytes[max_outsize] 向指定地址发起底层调用。 @external @payable def foo(_target: address) -> Bytes[32]: response: Bytes[32] = raw_call( _target, method_id(\"someMethodName()\"), max_outsize=32, value=msg.value ) return response 当 revertonfailure=False 时，返回 (success, response) 元组： @external @payable def bar(_target: address) -> Bytes[32]: success: bool = False response: Bytes[32] = b\"\" x: uint256 = 123 success, response = raw_call( _target, abiencode(x, methodid=method_id(\"someMethodName(uint256)\")), max_outsize=32, value=msg.value, revertonfailure=False ) assert success return response raw_log raw_log(topics: bytes32[4], data: Union[Bytes, bytes32]) -> None 底层日志，不需要声明 ABI 类型。topics 列表长度决定使用哪个 LOG 操作码。 raw_revert raw_revert(data: Bytes) -> None 底层 revert，返回指定数据。 send send(to: address, value: uint256, gas: uint256 = 0) -> None 向目标地址发送 ETH。金额始终以 wei 为单位。gas 默认为 0（即不附带 stipend）。","order":9,"section":"低层调用"},{"id":"built-ins#safety-checklist","href":"/docs/built-ins#safety-checklist","title":"内建函数与底层能力","group":"进阶","description":"理解 raw_call、raw_create 与 proxy / blueprint 等底层原语的边界。","content":"密码学函数 keccak256 keccak256(_value) -> bytes32 返回输入的 keccak256 哈希。输入可以是 String、Bytes 或 bytes32。 @external @view def foo(_value: Bytes[100]) -> bytes32: return keccak256(_value) sha256 sha256(_value) -> bytes32 返回输入的 SHA-256 哈希。输入类型与 keccak256 相同。 ecrecover ecrecover(hash: bytes32, v: uint256 uint8, r: uint256 bytes32, s: uint256 bytes32) -> address 从椭圆曲线签名中恢复关联的地址。无效输入返回 empty(address)。 @external @view def foo(hash: bytes32, v: uint8, r: bytes32, s: bytes32) -> address: return ecrecover(hash, v, r, s) ecadd / ecmul ecadd(a: uint256[2], b: uint256[2]) -> uint256[2] ecmul(point: uint256[2], scalar: uint256) -> uint256[2] Alt-BN128 曲线上的点加法和标量乘法。 数据操作函数 函数 签名 说明 --- --- --- concat concat(a, b, *args) -> Bytes \\ String 拼接多个字节数组或字符串 slice slice(b, start, length) -> Bytes \\ String 截取指定范围的片段 len len(b) -> uint256 返回 Bytes、String 或 DynArray 的长度 convert convert(value, type_) -> Any 显式类型转换（详见类型系统） uint2str uint2str(value) -> String 无符号整数转字符串 extract32 extract32(b, start, output_type=bytes32) -> Any 从字节数组中提取 32 字节值 methodid methodid(method, output_type=Bytes[4]) -> Bytes[4] \\ bytes4 计算函数选择器 @external @view def foo(s: String[32]) -> String[5]: return slice(s, 4, 5) @external @view def bar() -> Bytes[4]: return methodid('transfer(address,uint256)', outputtype=Bytes[4]) ABI 编码与解码 abiencode(*args, ensuretuple: bool = True, method_id: Bytes[4] = None) -> Bytes[...] abidecode(b: Bytes, outputtype: type, unwraptuple: bool = True) -> Any abiencode 将参数序列化为 ABIv2 格式，常用于 rawcall 的数据构造。 abi_decode 将 ABIv2 编码的字节串解码为指定类型。 @external @view def foo() -> Bytes[132]: x: uint256 = 1 y: Bytes[32] = b\"234\" return abiencode(x, y, methodid=method_id(\"foo()\")) @external @view def bar(someInput: Bytes[128]) -> (uint256, Bytes[32]): x: uint256 = empty(uint256) y: Bytes[32] = empty(Bytes[32]) x, y = abi_decode(someInput, (uint256, Bytes[32])) return x, y 数学函数 函数 说明 --- --- abs(value: int256) -> int256 绝对值 ceil(value: decimal) -> int256 向上取整 floor(value: decimal) -> int256 向下取整 max(a, b) -> numeric 较大值 min(a, b) -> numeric 较小值 maxvalue(type) -> numeric 类型的最大值 minvalue(type) -> numeric 类型的最小值 pow_mod256(a, b) -> uint256 a b % (2 256)，无溢出检查的指数运算 sqrt(d: decimal) -> decimal 十进制平方根，向下舍入到最近的 epsilon isqrt(x: uint256) -> uint256 整数平方根，向下舍入 uint256_addmod(a, b, c) -> uint256 (a + b) % c（中间计算不受 2^256 取模） uint256_mulmod(a, b, c) -> uint256 (a * b) % c（中间计算不受 2^256 取模） epsilon(typename) -> decimal 返回 decimal 类型的最小非零值 Unsafe 数学函数 这些函数跳过溢出检查，结果会在类型范围内回绕。主要用于性能敏感场景： 函数 说明 --- --- unsafe_add(x, y) 无溢出检查的加法 unsafe_sub(x, y) 无溢出检查的减法 unsafe_mul(x, y) 无溢出检查的乘法 unsafe_div(x, y) 无除零检查的除法（除数为 0 时返回 0） @external @view def foo(x: uint8, y: uint8) -> uint8: return unsafe_add(x, y) # unsafe_add(255, 255) == 254 (回绕) 性能提示 对于 EVM 原生字宽类型 uint256 和 int256，unsafe 函数会直接编译为单条 EVM 指令 （ADD、SUB、MUL、DIV/SDIV），因为 EVM 本身就对 256 位运算进行回绕。 实用工具 函数 说明 --- --- asweivalue(value, unit) 将以太币单位转为 wei，例如 asweivalue(1.337, \"ether\") blockhash(block_num) 返回指定区块哈希（仅最近 256 个区块可访问） blobhash(index) 返回当前交易中第 index 个 blob 的版本化哈希 empty(typename) 返回类型的零值，用于初始化 print(*args) 调试打印，通过静态调用 console 地址实现 关于 print print 的静态调用不会在生产代码中被移除，编译器只会发出警告。 默认模式与 titanoboa 兼容，使用 hardhat_compat=True 可适配 Hardhat 风格框架。 参数求值顺序 从 v0.4.0 起，内建函数的参数求值顺序未定义。编译器可能重排参数的求值顺序。 避免在内建函数调用中使用有副作用的函数作为参数，例如 extract32(x(), y()) 可能产生非预期结果。","order":9,"section":"使用清单"},{"id":"modules","href":"/docs/modules","title":"模块与组合","group":"进阶","description":"使用 import、initializes、uses 和 exports 构建可组合的合约体系。","content":"使用 import、initializes、uses 和 exports 构建可组合的合约体系。 Vyper 不提供类继承，而是鼓励用模块化组合复用代码。模块可以同时携带函数、类型和状态， 但状态能否被访问、外部函数是否暴露，都必须显式声明。","order":10},{"id":"modules#module-basics","href":"/docs/modules#module-basics","title":"模块与组合","group":"进阶","description":"使用 import、initializes、uses 和 exports 构建可组合的合约体系。","content":"模块的基本概念 在 Vyper 里，任意 .vy 文件都可以被当成模块导入。下面这个文件既是合约，也是一块可复用逻辑： # ownable.vy owner: address @deploy def init(): self.owner = msg.sender def checkowner(): assert self.owner == msg.sender 然后在其他文件中导入：","order":10,"section":"模块的基本概念"},{"id":"modules#initialize-state","href":"/docs/modules#initialize-state","title":"模块与组合","group":"进阶","description":"使用 import、initializes、uses 和 exports 构建可组合的合约体系。","content":"初始化状态 如果模块依赖自身状态，就需要通过 initializes 显式接入： initializes: ownable @deploy def init(): ownable.init() @external def admin_only(): ownable.checkowner() 这条语法很重要，因为它明确说明了“这个合约负责管理该模块的状态布局”。","order":10,"section":"初始化状态"},{"id":"modules#uses-and-dependencies","href":"/docs/modules#uses-and-dependencies","title":"模块与组合","group":"进阶","description":"使用 import、initializes、uses 和 exports 构建可组合的合约体系。","content":"uses 与依赖 如果你只想使用另一个模块的状态，但不想在当前层完成初始化，可以用 uses： uses: ownable 更复杂的情况是模块依赖模块。这时可以在 initializes 里用 walrus 语法把依赖关系写清楚： initializes: ownable initializes: ownable_2step[ownable := ownable] 这种写法啰嗦，但可读性非常高。状态来源、依赖方向和初始化责任都写在源码表层。","order":10,"section":"uses 与依赖"},{"id":"modules#exports","href":"/docs/modules#exports","title":"模块与组合","group":"进阶","description":"使用 import、initializes、uses 和 exports 构建可组合的合约体系。","content":"导出外部接口 模块的 @external 函数不会自动暴露到最终 ABI。你必须显式 exports： exports: ownable.transfer_ownership exports: ownable.interface 如果你只是把模块当作远程接口，也可以用 at()： anownable: ownable.interface @external def bind(addr: address): self.anownable = ownable.at(addr) 模块系统的收益 模块化组合替代继承后，外部可见函数、状态所有权和依赖关系都变得更显式。 对审计者来说，这比在多重继承层级里追踪解析顺序要简单得多。","order":10,"section":"导出外部接口"},{"id":"interfaces","href":"/docs/interfaces","title":"接口","group":"进阶","description":"学习如何声明、导入、实现和导出 Vyper 接口，并安全地发起外部调用。","content":"学习如何声明、导入、实现和导出 Vyper 接口，并安全地发起外部调用。 这一页解释了 Vyper 如何声明、导入、实现和导出接口。 接口本质上是一组外部函数签名，用来让合约之间安全地通信。","order":11},{"id":"interfaces#declaring-and-using","href":"/docs/interfaces#declaring-and-using","title":"接口","group":"进阶","description":"学习如何声明、导入、实现和导出 Vyper 接口，并安全地发起外部调用。","content":"声明与使用接口 接口既可以直接写在当前合约里，也可以从独立文件导入。 内联接口 使用 interface 关键字可以定义内联外部接口： interface FooBar: def calculate() -> uint256: view def test1(): nonpayable 定义完成后，就可以把它当成参数类型来发起外部调用： @external def test(foobar: FooBar): extcall foobar.test1() @external def test2(foobar: FooBar) -> uint256: return staticcall foobar.calculate() 接口类型也可以直接用于状态变量，然后在构造时绑定一个地址： foobar_contract: FooBar @deploy def init(foobar_address: address): self.foobarcontract = FooBar(foobaraddress) @external def test(): extcall self.foobar_contract.test1() 如果你已经有一个地址变量，也可以显式把它转换为接口类型，例如 FooBar(some_address)。 extcall 与 staticcall Vyper 强制你在外部调用前写出调用意图： staticcall 只用于 view 和 pure 函数。 extcall 用于 payable 和 nonpayable 函数。 payable 调用允许附带非零 value。 staticcall 的输出必须被接收或直接返回。 interface FooBar: def calculate() -> uint256: pure def query() -> uint256: view def update(): nonpayable def pay(): payable @external def test(foobar: FooBar): value: uint256 = staticcall foobar.calculate() value = staticcall foobar.query() extcall foobar.update() extcall foobar.pay(value=1) 签名必须精确匹配 如果接口中的签名和目标合约真实签名不一致，运行时可能报错，甚至出现未定义行为。 例如把真实会改状态的函数错误标成 view，staticcall 就可能在被调合约里直接回滚。 外部调用可选参数 Vyper 允许给外部调用传入一些额外关键字参数： 关键字 作用 --- --- gas 指定本次调用可用的 gas value 指定随调用发送的 ether skipcontractcheck 跳过 EXTCODESIZE 检查，但保留 RETURNDATASIZE 检查 defaultreturnvalue 当目标未返回值时，指定一个默认返回值 defaultreturnvalue 对兼容“缺失返回值”的旧 ERC20 很有用，行为类似 Solidity 里的 safeTransfer： extcall IERC20(USDT).transfer(msg.sender, 1, defaultreturnvalue=True) extcall IERC20(USDT).transfer(msg.sender, 1) 第一行会把“未返回任何值”当作 True 处理，第二行则会因为没有返回值而回滚。","order":11,"section":"声明与使用接口"},{"id":"interfaces#built-in-interfaces","href":"/docs/interfaces#built-in-interfaces","title":"接口","group":"进阶","description":"学习如何声明、导入、实现和导出 Vyper 接口，并安全地发起外部调用。","content":"内建接口 Vyper 内置了一些常见标准接口，例如 IERC20 和 IERC721。它们从 ethereum.ercs 导入： from ethereum.ercs import IERC20 implements: IERC20 这类内建接口适合直接拿来约束 ERC 标准实现，或者给外部调用提供类型信息。","order":11,"section":"内建接口"},{"id":"interfaces#implementing-interfaces","href":"/docs/interfaces#implementing-interfaces","title":"接口","group":"进阶","description":"学习如何声明、导入、实现和导出 Vyper 接口，并安全地发起外部调用。","content":"实现接口 如果要声明“当前合约实现了某个接口”，可以使用 implements： implements: FooBarInterface 这里导入的接口通常来自 an_interface.vyi，也可以来自 ABI JSON 接口文件。 编译器会检查当前合约是否真正实现了接口里定义的全部外部函数；如果缺失，就会编译失败。 多个 implements 可以合并声明： implements: Foo implements: Bar 等价于 implements: ( Foo, Bar, ) 还有几个细节值得记住： 如果接口返回 Bytes、DynArray、String 这类需要上界的类型，接口里写的上界在当前版本里会被视为实现方的“最小要求”。 自 v0.4.0 起，接口里定义的事件不需要在实现合约中重新声明；只要导入并使用，它们就会自动出现在 ABI 输出里。 如果接口函数定义了默认参数，例如 deposit(assets: uint256, receiver: address = msg.sender)，那意味着被调合约必须真的支持对应的 ABI 签名组合。","order":11,"section":"实现接口"},{"id":"interfaces#standalone-and-extraction","href":"/docs/interfaces#standalone-and-extraction","title":"接口","group":"进阶","description":"学习如何声明、导入、实现和导出 Vyper 接口，并安全地发起外部调用。","content":"独立接口与导出 .vyi 独立接口 独立接口文件使用 .vyi 后缀，函数体必须写成省略号： # ISomeInterface.vyi @external def test1(): ... @external def calculate() -> uint256: ... 这样编译器才能在导入时识别它是接口文件，而不是普通合约实现。 从现有合约导出接口 Vyper 自带接口导出格式，可以从现有合约直接提取接口： vyper -f interface examples/voting/ballot.vy 如果你想得到可直接粘贴到合约里的内联接口格式，也可以导出 external_interface： vyper -f external_interface examples/voting/ballot.vy 这两个输出都很适合在审计、重构和模块拆分时快速生成接口边界。","order":11,"section":"独立接口与导出"},{"id":"event-logging","href":"/docs/event-logging","title":"事件日志","group":"进阶","description":"理解事件的声明、topics 与 data 的划分，以及链下监听的工作方式。","content":"理解事件的声明、topics 与 data 的划分，以及链下监听的工作方式。 这一页解释了 Vyper 里的事件声明、日志写入和前端监听方式。 事件写入的是交易日志而不是合约存储，因此更便宜，但只能被链下客户端读取。","order":12},{"id":"event-logging#example-of-logging","href":"/docs/event-logging#example-of-logging","title":"事件日志","group":"进阶","description":"理解事件的声明、topics 与 data 的划分，以及链下监听的工作方式。","content":"日志记录示例 下面这个例子来自官方 ERC20 示例，展示了事件从声明到写入的完整流程： event Transfer: sender: indexed(address) receiver: indexed(address) value: uint256 event Approval: owner: indexed(address) spender: indexed(address) value: uint256 @external def transfer(to: address, value: uint256) -> bool: ... log Transfer(sender=msg.sender, receiver=to, value=value) 这里发生了两件事： 合约先声明事件结构，说明哪些字段会被记录。 在业务逻辑完成后，用 log 把本次状态变化广播给链下监听者。 前端或脚本可以通过 ABI 订阅这些事件。参考文档给出的 web3.js 监听方式如下： var abi = / compiler generated ABI / var MyToken = web3.eth.contract(abi) var myToken = MyToken.at(\"0x1234...ab67\") var event = myToken.Transfer(function (error, result) { if (!error) { var args = result.returnValues console.log(\"value transferred =\", args._amount) } }) 只要合约发出 Transfer，回调就会被触发。","order":12,"section":"日志记录示例"},{"id":"event-logging#declaring-events","href":"/docs/event-logging#declaring-events","title":"事件日志","group":"进阶","description":"理解事件的声明、topics 与 data 的划分，以及链下监听的工作方式。","content":"声明事件 一个典型事件看起来像这样： event Transfer: sender: indexed(address) receiver: indexed(address) value: uint256 EVM 底层提供 LOG0 到 LOG4 五个操作码来生成日志记录。 每条日志由两部分组成： topics：可检索的 32 字节主题，用于过滤和索引。 data：不可检索但更灵活的数据区，可以放字符串、数组等复杂值。 在 Vyper 里，事件参数通常分成两类： indexed(...) 参数：进入 topic，适合做地址、标识符等过滤条件。 普通值参数：进入 data，适合传业务载荷。 事件还会把“事件签名”编码进日志主题中，用于表明具体发生了哪个事件。 如果事件没有任何参数，可以直接写成： event Foo: pass","order":12,"section":"声明事件"},{"id":"event-logging#logging-events","href":"/docs/event-logging#logging-events","title":"事件日志","group":"进阶","description":"理解事件的声明、topics 与 data 的划分，以及链下监听的工作方式。","content":"写入事件 事件声明完成后，就可以在任意需要的位置多次写入日志： log Transfer(sender=msg.sender, receiver=to, value=value) 注意点有三个： 传入的参数类型必须与事件声明完全匹配。 如果使用关键字参数，顺序不影响结果。 事件写入交易日志而不是 storage，通常比持久化状态便宜很多。 但它的限制也很明确：合约本身不能读取过去写出的事件，只有链下客户端才能消费这些日志。","order":12,"section":"写入事件"},{"id":"event-logging#listening-for-events","href":"/docs/event-logging#listening-for-events","title":"事件日志","group":"进阶","description":"理解事件的声明、topics 与 data 的划分，以及链下监听的工作方式。","content":"监听事件 监听端在收到事件后，通常会拿到一整份结果对象。其中最常用的是 result.returnValues， 它的字段名与事件里声明的普通值参数一致。 需要注意的是： indexed 字段主要用于订阅过滤条件。 真正传给回调的值通常集中在 returnValues 中。 客户端是否把 indexed 字段也映射到结果对象，取决于具体库的封装方式。 实践上，事件最适合承担两类工作： 给前端、索引器和分析脚本提供状态变化通知。 在不增加链上读取成本的情况下，保留关键操作的审计轨迹。","order":12,"section":"监听事件"},{"id":"natspec","href":"/docs/natspec","title":"NatSpec 元数据","group":"工具与规范","description":"使用标准化 docstring 生成 userdoc 和 devdoc，补齐合约的可读元数据。","content":"使用标准化 docstring 生成 userdoc 和 devdoc，补齐合约的可读元数据。 NatSpec 是 Ethereum Natural Language Specification。 它让 Vyper 合约能把开发者说明和面向终端用户的说明一起编进编译产物。","order":13},{"id":"natspec#overview","href":"/docs/natspec#overview","title":"NatSpec 元数据","group":"工具与规范","description":"使用标准化 docstring 生成 userdoc 和 devdoc，补齐合约的可读元数据。","content":"NatSpec 概览 Vyper 可以使用一种特殊 docstring 为合约、外部函数、返回值等提供结构化文档。 这些文档会区分为两类： 面向终端用户的说明，适合在签名交易时展示。 面向开发者的说明，适合放进文档系统、审计产物或工具链输出。 Vyper 使用接近 doxygen 的标记风格。一个完整示例如下： \"\"\" @title A simulator for Bug Bunny, the most famous Rabbit @license MIT @author Warned Bros @notice You can use this contract for only the most basic simulation @dev Simply chewing a carrot does not count, carrots must pass the throat to be considered eaten \"\"\" @external @payable def doesEat(food: String[30], qty: uint256) -> bool: \"\"\" @notice Determine if Bugs will accept qty of food to eat @dev Compares the entire string and does not rely on a hash @param food The name of a food to evaluate (in English) @param qty The number of food items to evaluate @return True if Bugs will eat it, False otherwise \"\"\" 内部函数不会被处理 编译器不会解析内部函数的 docstring。你仍然可以给内部函数写普通注释或 NatSpec 风格注释， 但这些内容不会进入编译器输出。","order":13,"section":"NatSpec 概览"},{"id":"natspec#tags","href":"/docs/natspec#tags","title":"NatSpec 元数据","group":"工具与规范","description":"使用标准化 docstring 生成 userdoc 和 devdoc，补齐合约的可读元数据。","content":"标签说明 所有 NatSpec 标签都是可选的。官方文档给出的常用标签如下： 标签 说明 适用范围 --- --- --- @title 描述合约标题 合约 @license 合约许可证 合约 @author 作者名 合约、函数 @notice 面向终端用户的说明 合约、函数 @dev 面向开发者的补充说明 合约、函数 @param 描述单个输入参数 函数 @return 描述一个或全部返回值 函数 @custom:... 自定义标签，语义由应用定义 合约、函数 使用时还有几条规则： 单个标签的描述可以跨多行，编译器会把中间空白折叠成一个空格。 如果写了 docstring 但没有任何 NatSpec 标签，它会被当作 @notice。 每个 @param 后面都必须跟一个真实存在的输入参数名；重复或无效名称会抛出 NatSpecSyntaxException。 @return 最好一项对应一个返回值，也可以把所有返回值合并写成一项；如果数量超出真实返回值数量，同样会报 NatSpecSyntaxException。","order":13,"section":"标签说明"},{"id":"natspec#documentation-output","href":"/docs/natspec#documentation-output","title":"NatSpec 元数据","group":"工具与规范","description":"使用标准化 docstring 生成 userdoc 和 devdoc，补齐合约的可读元数据。","content":"文档输出 编译器会把 NatSpec 解析成两份 JSON： userdoc：给终端用户展示的说明。 devdoc：给开发者和工具链消费的详细说明。 如果合约文件名是 carrots.vy，可以这样导出： vyper -f userdoc,devdoc carrots.vy userdoc 上面的示例会生成类似这样的用户文档： { \"methods\": { \"doesEat(string,uint256)\": { \"notice\": \"Determine if Bugs will accept qty of food to eat\" } }, \"notice\": \"You can use this contract for only the most basic simulation\" } 这里的方法键名不是函数名本身，而是 ABI 里的规范签名，例如 doesEat(string,uint256)。 devdoc 开发者文档会额外包含作者、许可证、参数说明和返回值说明： { \"author\": \"Warned Bros\", \"license\": \"MIT\", \"details\": \"Simply chewing a carrot does not count, carrots must pass the throat to be considered eaten\", \"methods\": { \"doesEat(string,uint256)\": { \"details\": \"Compares the entire string and does not rely on a hash\", \"params\": { \"food\": \"The name of a food to evaluate (in English)\", \"qty\": \"The number of food items to evaluate\" }, \"returns\": { \"_0\": \"True if Bugs will eat it, False otherwise\" } } }, \"title\": \"A simulator for Bug Bunny, the most famous Rabbit\" } 如果你的团队要做钱包弹窗说明、合约门户、审计附录或 SDK 文档，NatSpec 是最轻量也最标准的元数据来源。","order":13,"section":"文档输出"},{"id":"compiling","href":"/docs/compiling","title":"编译与产物","group":"工具与规范","description":"掌握 vyper CLI、JSON 接口、优化模式、EVM 版本和警告控制。","content":"掌握 vyper CLI、JSON 接口、优化模式、EVM 版本和警告控制。 编译器不是文档最后才需要看的工具。越早理解 vyper 和 vyper-json 的输出能力， 你越容易在开发、调试和审计阶段建立稳定的工程习惯。","order":14},{"id":"compiling#cli-basics","href":"/docs/compiling#cli-basics","title":"编译与产物","group":"工具与规范","description":"掌握 vyper CLI、JSON 接口、优化模式、EVM 版本和警告控制。","content":"CLI 基础 Vyper 提供两套命令行入口： vyper：编译合约文件或归档文件，支持多种输出格式 vyper-json：JSON 标准接口，便于与构建系统集成 最小编译命令： vyper Counter.vy 指定搜索路径（用于导入解析）： vyper -p yourProject yourProject/Counter.vy 使用 vyper --help 查看完整选项列表。","order":14,"section":"CLI 基础"},{"id":"compiling#output-formats","href":"/docs/compiling#output-formats","title":"编译与产物","group":"工具与规范","description":"掌握 vyper CLI、JSON 接口、优化模式、EVM 版本和警告控制。","content":"输出格式全集 -f 参数指定输出格式，多个格式用逗号分隔： vyper -f abi,bytecode,layout Counter.vy 完整的输出格式列表： 格式 说明 --- --- abi 合约 ABI（前端、脚本和测试必需） abi_python Python 格式的 ABI bytecode 部署字节码 bytecode_runtime 运行时字节码 blueprint_bytecode ERC-5202 兼容的 blueprint 字节码 interface Vyper 接口定义 external_interface 外部接口定义 ast 抽象语法树 annotated_ast 带注解的 AST ir 中间表示 ir_json JSON 格式的 IR ir_runtime 运行时 IR asm EVM 汇编 opcodes 操作码列表 opcodes_runtime 运行时操作码列表 source_map 源码映射 sourcemapruntime 运行时源码映射 method_identifiers 函数选择器哈希列表 userdoc NatSpec 用户文档 devdoc NatSpec 开发者文档 metadata 编译元数据 combined_json 组合 JSON 输出 layout 存储布局 integrity 完整性哈希 archive Vyper 归档文件 solc_json Solidity 兼容的标准 JSON 输入 cfg 控制流图 cfg_runtime 运行时控制流图 存储布局 查看合约的存储布局： vyper -f layout Counter.vy 输出一个 JSON 对象，显示编译器为所有状态变量确定的存储位置。 覆盖默认存储布局（用于升级或审计场景）： vyper --storage-layout-file storageLayout.json Counter.vy 输入格式必须与 vyper -f layout 输出的 .storage_layout 字段匹配。","order":14,"section":"输出格式全集"},{"id":"compiling#json-interface","href":"/docs/compiling#json-interface","title":"编译与产物","group":"工具与规范","description":"掌握 vyper CLI、JSON 接口、优化模式、EVM 版本和警告控制。","content":"JSON 接口 vyper-json 接收 JSON 格式输入，返回 JSON 格式输出： # 从 stdin 读取 vyper-json 从文件读取 vyper-json project.json 输出到文件 vyper-json project.json -o compiled.json 输入 JSON 格式 { \"language\": \"Vyper\", \"sources\": { \"contracts/foo.vy\": { \"content\": \"@external\\ndef foo() -> bool:\\n return True\" } }, \"interfaces\": { \"contracts/bar.vy\": { \"content\": \"\" }, \"contracts/baz.json\": { \"abi\": [] } }, \"storagelayoutoverrides\": { \"contracts/foo.vy\": { \"a\": {\"type\": \"uint256\", \"slot\": 1, \"n_slots\": 1}, \"b\": {\"type\": \"uint256\", \"slot\": 0, \"n_slots\": 1} } }, \"settings\": { \"evmVersion\": \"prague\", \"optimize\": \"gas\", \"bytecodeMetadata\": true, \"experimentalCodegen\": false, \"search_paths\": [], \"outputSelection\": { \"*\": [\"evm.bytecode\", \"abi\"], \"contracts/foo.vy\": [\"ast\"] } } } 依赖解析顺序： interfaces 字段中定义的接口 sources 字段中合约推导出的接口 输出 JSON 格式 输出包含以下主要字段： compiler：编译器版本 errors：错误和警告列表（含 sourceLocation、type、severity、message） sources：文件级输出（AST 等） contracts：合约级输出（ABI、字节码、源码映射、方法标识符等） 错误的 component 字段指示发生阶段：json（JSON 解析）、parser（语法解析）、compiler（编译）、vyper（内部异常）。","order":14,"section":"JSON 接口"},{"id":"compiling#compiler-settings","href":"/docs/compiling#compiler-settings","title":"编译与产物","group":"工具与规范","description":"掌握 vyper CLI、JSON 接口、优化模式、EVM 版本和警告控制。","content":"编译设置 优化模式 vyper --optimize gas Counter.vy # 默认：优化 gas 消耗 vyper --optimize codesize Counter.vy # 优化代码大小 vyper --optimize none Counter.vy # 不优化 模式 选择器表 常量处理 循环处理 --- --- --- --- gas（默认） 稀疏选择器表，优化 gas 内联常量 尝试展开循环 codesize 密集选择器表 外联代码 使用循环复制数据 none 最小优化 — — EVM 版本 vyper --evm-version prague Counter.vy 也可以在源码中指定： #pragma evm-version prague 如果 CLI 选项与源码 pragma 冲突，编译器会抛出异常。 支持的版本： 版本 关键变化 --- --- london 基准版本 paris block.difficulty 弃用，改用 block.prevrandao shanghai 编译器自动生成 PUSH0 替代 PUSH1 0 cancun 支持 transient 关键字；@nonreentrant 使用 TLOAD/TSTORE；自动生成 MCOPY prague（默认） 当前默认版本 EVM 版本的重要性 为错误的 EVM 版本编译可能导致错误、异常或不可预测的行为。 特别是运行私有链时，务必确保 EVM 版本匹配。 Venom 实验后端 使用 --experimental-codegen（或别名 --venom-experimental）启用新的 Venom IR 管线。 Venom IR 受 LLVM IR 启发，支持更先进的分析和优化。 vyper --experimental-codegen Counter.vy 警告控制 vyper -Wnone Counter.vy # 抑制所有警告 vyper -Werror Counter.vy # 将警告提升为错误（推荐用于 CI） 完整性哈希 -f integrity 输出合约的完整性哈希——基于源码及其依赖（导入）和存储布局覆盖的递归 SHA-256 哈希。 用于工具链检测两次构建是否相同。 Vyper 归档 Vyper 归档（.vyz）是包含编译输入和设置的 ZIP 包，可直接作为编译器输入： # 生成归档 vyper -f archive Counter.vy -o Counter.vyz 生成 base64 编码归档 vyper -f archive Counter.vy --base64 > Counter.vyz.b64 编译归档 vyper Counter.vyz vyper Counter.vyz.b64 归档内部结构包含 MANIFEST/ 目录：clisettings.txt、compilationtargets、compilerversion、integrity、settings.json、searchpaths，以及可选的 storagelayout.json。 在线编译器 Try VyperLang!：Vyper 团队维护的 JupyterHub 沙箱，支持浏览器内部署 Remix IDE：支持 Vyper 和 Solidity 的在线 IDE 工程建议 把编译参数写入项目脚本或 CI，而不是靠人肉记忆。 对合约项目来说，可复现性本身就是安全性的一部分。 推荐使用 Vyper 归档来确保编译的可复现性。","order":14,"section":"编译设置"},{"id":"compiler-exceptions","href":"/docs/compiler-exceptions","title":"编译器异常","group":"工具与规范","description":"快速理解 Vyper 在编译阶段会抛出的主要异常类型，以及常见定位方式。","content":"快速理解 Vyper 在编译阶段会抛出的主要异常类型，以及常见定位方式。 这一页汇总了 Vyper 编译阶段可能抛出的主要异常。 这些异常大多会附带源码高亮，方便你直接定位出错位置。","order":15},{"id":"compiler-exceptions#common-exceptions","href":"/docs/compiler-exceptions#common-exceptions","title":"编译器异常","group":"工具与规范","description":"快速理解 Vyper 在编译阶段会抛出的主要异常类型，以及常见定位方式。","content":"常见编译异常 只要编译器能定位到错误位置，异常信息通常都会带源码片段，例如： vyper.exceptions.VariableDeclarationException: line 79:17 Persistent variable undeclared: highstBid 78 # If bid is less than highest bid, bid fails ---> 79 if (value <= self.highstBid): ---------------------^ 80 return False 下面是参考文档列出的主要异常类型： 异常 说明 --- --- ArgumentException 调用函数时参数非法，例如位置参数数量不对，或关键字参数无效 CallViolation 非法函数调用，例如在两个外部函数之间直接互相调用 ArrayIndexException 数组索引越界 EventDeclarationException 事件声明不合法 EvmVersionException 合约中使用了当前 EVM 规则集不支持的行为 FunctionDeclarationException 函数声明不合法，例如返回值定义错误或不匹配 ImmutableViolation 试图修改不可修改的变量、常量或定义 InterfaceViolation 接口没有被完整实现 InvalidAttribute 引用了不存在的属性 InvalidLiteral 字面量值无法匹配任何合法 Vyper 类型 InvalidOperation 某个类型上使用了不支持的操作符 InvalidReference 对已有定义的引用方式不合法 InvalidType 值本身是合法字面量，但不能赋给当前目标类型 IteratorException 迭代器构造或使用方式错误 JSONError 编译器 JSON 输入格式不正确 NamespaceCollision 试图使用一个已经占用的名字 NatSpecSyntaxException NatSpec 元数据存在非法语法 NonPayableViolation 在非 @payable 函数中访问了 msg.value OverflowException 数值超出目标类型边界 StateAccessViolation 在 @view 或 @pure 上下文里做了越权状态访问 StructureException 语法虽然可解析，但结构上非法 SyntaxException 无法被解析的语法错误 TypeMismatch 两个或多个对象的类型不兼容 UndeclaredDefinition 访问了尚未声明的对象 VariableDeclarationException 变量声明不合法 VersionException 版本字符串格式错误，或与当前编译器不兼容 ZeroDivisionException 发生除零或模零 典型示例 InvalidLiteral #pragma enable-decimals @external def foo(): bar: decimal = 3.123456789123456789 这里字面量小数位数过多，无法分配给合法的 Vyper decimal 表示，因此会触发 InvalidLiteral。 InvalidOperation @external def foo(): a: String[10] = \"hello\" * 2 字符串类型不支持乘法运算，所以这里会抛出 InvalidOperation。 InvalidReference baz: int128 @external def foo(): bar: int128 = baz baz 是状态变量，函数里访问它必须写成 self.baz。直接写 baz 会触发 InvalidReference。 InvalidType @external def foo(): bar: int128 = 3.5 3.5 是合法字面量，但它不能赋值给 int128，因此会得到 InvalidType。 NatSpecSyntaxException vyper.exceptions.SyntaxException: line 14:5 No description given for tag '@param' 13 @dev the feet are sticky like rice ---> 14 @param ---------^ 15 @return always True @param 后必须跟参数名和描述，否则 NatSpec 解析会失败。 NonPayableViolation @external def _foo(): bar: uint256 = msg.value 如果函数没有声明为 @payable，却读取了 msg.value，就会抛出 NonPayableViolation。 TypeMismatch #pragma enable-decimals @external def foo(): bar: int128 = 3 baz: decimal = 4.2 if baz + bar > 4: pass 这里 bar 是 int128，baz 是 decimal，两者直接相加会触发 TypeMismatch。 VariableDeclarationException vyper.exceptions.VariableDeclarationException: line 79:17 Persistent variable undeclared: highstBid 78 # If bid is less than highest bid, bid fails ---> 79 if (value <= self.highstBid): ---------------------^ 80 return False 这种错误常见于变量拼写错误、缺失声明，或把 storage / memory 变量写错位置。","order":15,"section":"常见编译异常"},{"id":"compiler-exceptions#compiler-panic","href":"/docs/compiler-exceptions#compiler-panic","title":"编译器异常","group":"工具与规范","description":"快速理解 Vyper 在编译阶段会抛出的主要异常类型，以及常见定位方式。","content":"CompilerPanic CompilerPanic 和上面的普通编译异常不同。它通常表示问题出在编译器内部，而不是你的合约源码本身： $ vyper v.vy Error compiling: v.vy vyper.exceptions.CompilerPanic: Number of times repeated must be a constant nonzero positive integer: 0 Please create an issue. 如果你遇到这类错误，官方建议直接到 Vyper GitHub 提交 issue， 因为它意味着编译器在某个输入路径上出现了内部故障，而不仅仅是用户代码写错。","order":15,"section":"CompilerPanic"},{"id":"deploying-contracts","href":"/docs/deploying-contracts","title":"部署合约","group":"工具与规范","description":"了解字节码部署、浏览器工具、Titanoboa 和官方在线环境的部署路径。","content":"了解字节码部署、浏览器工具、Titanoboa 和官方在线环境的部署路径。 部署路径其实很清楚： 编译出字节码之后，你可以用客户端、浏览器工具、Titanoboa，或官方在线环境完成部署。","order":16},{"id":"deploying-contracts#deployment-options","href":"/docs/deploying-contracts#deployment-options","title":"部署合约","group":"工具与规范","description":"了解字节码部署、浏览器工具、Titanoboa 和官方在线环境的部署路径。","content":"部署方式 当你准备把合约部署到测试网或主网时，官方文档给出了几条常见路径： 直接拿字节码部署 先用 vyper 编译出字节码，再交给 geth 或其他 Ethereum 客户端部署： vyper yourFileName.vy 上面的输出就是部署所需的字节码。 配合 ABI 走浏览器工具 如果你希望通过浏览器钱包或网页工具部署，可以先导出 ABI： vyper -f abi yourFileName.vy 然后把 ABI 和字节码一起交给浏览器侧工具，例如合约部署面板。 使用 Titanoboa Titanoboa 既能测试，也能连真实 RPC 进行部署： from eth_account import Account boa.setnetworkenv( ) 真实项目里应从安全存储中读取私钥 boa.env.add_account(Account( )) deployer = boa.load_partial(\"yourFileName.vy\") deployer.deploy() 这种方式适合 Python 驱动的部署脚本，尤其适合已经在用 Titanoboa 跑测试的项目。 使用 try.vyperlang.org 官方在线环境 try.vyperlang.org 也支持编译和部署。 它内置了 Titanoboa 和浏览器签名器集成，适合演示、教学或快速验证部署流程。","order":16,"section":"部署方式"},{"id":"deploying-contracts#practical-guidelines","href":"/docs/deploying-contracts#practical-guidelines","title":"部署合约","group":"工具与规范","description":"了解字节码部署、浏览器工具、Titanoboa 和官方在线环境的部署路径。","content":"实践建议 如果是本地或 CI 自动化部署，优先使用 Titanoboa 或你自己的脚本化流程。 如果只是临时验证部署参数，在线环境和浏览器工具更省事。 无论使用哪条路径，部署前都应先固定编译器版本、优化模式和目标 EVM 版本，避免“本地能复现，链上产物却不同”。","order":16,"section":"实践建议"},{"id":"testing-contracts","href":"/docs/testing-contracts","title":"测试合约","group":"工具与规范","description":"以 pytest 为基础，选择 Titanoboa 或 Moccasin 构建 Vyper 测试工作流。","content":"以 pytest 为基础，选择 Titanoboa 或 Moccasin 构建 Vyper 测试工作流。 Vyper 官方推荐项目基于 pytest 测试， 再搭配 Titanoboa 或 Moccasin 其中之一完成解释执行、部署和调试。","order":17},{"id":"testing-contracts#recommended-stack","href":"/docs/testing-contracts#recommended-stack","title":"测试合约","group":"工具与规范","description":"以 pytest 为基础，选择 Titanoboa 或 Moccasin 构建 Vyper 测试工作流。","content":"推荐测试栈 官方建议把 pytest 作为基础测试框架，再按项目形态选择上层工具： 偏底层、偏解释器能力和调试体验：选 Titanoboa。 想要更完整的项目脚手架和测试工作流：选 Moccasin。","order":17,"section":"推荐测试栈"},{"id":"testing-contracts#titanoboa","href":"/docs/testing-contracts#titanoboa","title":"测试合约","group":"工具与规范","description":"以 pytest 为基础，选择 Titanoboa 或 Moccasin 构建 Vyper 测试工作流。","content":"Titanoboa Titanoboa 是 Vyper 团队维护的解释器式工具， 适合做快速测试和调试。官方文档强调了它的几项能力： 测试执行速度快。 traceback 更易读，便于调试。 支持 forking。 提供部署能力。 入门文档见官方站点：Titanoboa Documentation。 适用场景 如果你的重点是快速写测试、直接解释执行合约、并在失败时拿到尽可能友好的调试信息， Titanoboa 往往是最短路径。","order":17,"section":"Titanoboa"},{"id":"testing-contracts#moccasin","href":"/docs/testing-contracts#moccasin","title":"测试合约","group":"工具与规范","description":"以 pytest 为基础，选择 Titanoboa 或 Moccasin 构建 Vyper 测试工作流。","content":"Moccasin Moccasin 构建在 Titanoboa 之上，是更完整的 Pythonic 开发与测试框架。参考文档列出的特点包括： 内建 ZKsync 支持。 具名合约，便于管理地址和依赖。 自定义 pytest 标记，适合 staging 测试。 加密钱包支持，不必把私钥直接放进 .env。 GitHub 与 Python 依赖安装能力。 入门文档见：Moccasin Documentation。","order":17,"section":"Moccasin"},{"id":"testing-contracts#selection-guidance","href":"/docs/testing-contracts#selection-guidance","title":"测试合约","group":"工具与规范","description":"以 pytest 为基础，选择 Titanoboa 或 Moccasin 构建 Vyper 测试工作流。","content":"如何选择 你只想测试 Vyper 合约本身，并保持工具链最轻：先用 Titanoboa。 你需要项目模板、部署脚本、网络配置和更完整的开发体验：选 Moccasin。 两者都基于 Python 生态，所以和 pytest 的整合都比较自然。","order":17,"section":"如何选择"},{"id":"design-tradeoffs","href":"/docs/design-tradeoffs","title":"设计取舍与风格建议","group":"工具与规范","description":"理解 Vyper 与 Solidity 的差异，并建立更易审计的编码习惯。","content":"理解 Vyper 与 Solidity 的差异，并建立更易审计的编码习惯。 很多开发者第一次看 Vyper，会先注意到它\"少了很多东西\"。但从安全角度看， 这些缺失恰恰定义了 Vyper 的价值主张：减少容易出错的自由度，让合约行为更显式。 本页详细对比 Vyper 与 Solidity 的差异，并给出风格建议。","order":18},{"id":"design-tradeoffs#why-less-is-more","href":"/docs/design-tradeoffs#why-less-is-more","title":"设计取舍与风格建议","group":"工具与规范","description":"理解 Vyper 与 Solidity 的差异，并建立更易审计的编码习惯。","content":"为什么更少即更多 Vyper 在设计上持续围绕三个关键词展开：安全性、简洁性、可审计性。 为实现这些目标，Vyper 主动排除了那些虽然强大但会让控制流和状态边界变模糊的能力。 每一项排除都是经过权衡的——以灵活性换取显式行为。 快速对照表 Solidity Vyper 背后的理由 --- --- --- modifier 内联 assert / raise 检查逻辑留在函数体里，执行顺序更直观 类继承 import + exports 显式依赖关系 assembly { } 不支持 使用 rawcall、createminimalproxyto 等内建函数 while (true) for i in range(n) 有界 gas 成本 mapping HashMap 语义相同 emit Event() log Event() 语义相同 require() assert / raise 不同语义，更显式的错误路径 contract.call() extcall / staticcall 显式外部调用","order":18,"section":"为什么更少即更多"},{"id":"design-tradeoffs#solidity-comparison","href":"/docs/design-tradeoffs#solidity-comparison","title":"设计取舍与风格建议","group":"工具与规范","description":"理解 Vyper 与 Solidity 的差异，并建立更易审计的编码习惯。","content":"与 Solidity 的核心差异 没有 Modifier Solidity 的 modifier 可以在函数体前后执行代码、修改状态，理解一个函数需要去别处查找 modifier 定义： modifier onlyOwner() { require(msg.sender == owner, \"Not owner\"); _; } function withdraw() public onlyOwner { // ... } Vyper 将检查逻辑内联，控制流从上到下完全可见： @external def withdraw(): assert msg.sender == self.owner, \"Not owner\" # ... 没有类继承 Solidity 支持多重继承，带来了菱形问题和 C3 线性化复杂度。Vyper 完全排除继承。 从 0.4.0 起，Vyper 引入了模块系统实现强大的代码复用： initializes: ownable exports: ownable.transfer_ownership @deploy def init(): ownable.init() 三个关键声明管理模块关系： initializes：当前合约管理该模块的存储 uses：当前合约读取模块状态但不初始化 exports：将模块函数暴露到 ABI 理解一个合约只需要读一个文件及其直接导入——依赖关系和对外暴露的函数都是显式的。 没有内联汇编 汇编绕过了编译器的安全检查：类型验证、溢出保护、内存安全。 Vyper 通过显式的内建函数提供底层访问：rawcall、rawcreate、createminimalproxyto、createfrom_blueprint。 没有函数重载 Solidity 允许同名不同参数的多个函数。Vyper 要求唯一的函数名， 保持 ABI 和调用点在审查时的明确性。 没有运算符重载 a + b 永远是算术加法。运算符不能为自定义类型重新定义， 运算符行为在整个代码库中保持一致。 没有无限循环和递归 所有循环必须有编译期上界，函数不能直接或间接调用自身： for i: uint256 in range(100): # 循环体 for i: uint256 in range(count, bound=100): # 变量上界，但编译期有 bound 限制 无界的存储迭代可能超过区块 gas 上限导致合约不可用。有界循环和无递归使得 gas 成本可静态分析—— 每个函数调用都有可计算的 gas 上界。 有界动态数组 存储数组需要编译时的最大大小： balances: DynArray[uint256, 100] 这保证了 gas 成本可预测并防止 DoS 攻击。对于无界集合，使用 HashMap。 显式类型转换 Vyper 允许安全的自动放宽（如 uint8 → uint256），但对可能有损或语义重要的转换要求显式 convert()： x: uint256 = 100 y: int256 = convert(x, int256) addr: address = 0x1234567890123456789012345678901234567890 num: uint160 = convert(addr, uint160) 原生十进制定点数 Vyper 内置 10 位精度的十进制定点运算： a: decimal = 0.1 b: decimal = 0.2 total: decimal = a + b # 精确等于 0.3 0.1 和 0.2 在二进制浮点中无法精确表示，但 Vyper 的十进制类型能精确处理。 Solidity 没有原生定点类型，需要手动整数缩放。 边界检查 数组访问和算术运算在运行时进行边界检查。越界访问 revert，整数溢出 revert。 Solidity 0.8+ 提供类似保护但可在 unchecked 块中关闭。Vyper 无法关闭这些检查。 需要回绕行为时使用显式的 unsafe_* 内建函数。 重入保护 内建 @nonreentrant 装饰器，编译器生成互斥锁，无需手动实现： @external @nonreentrant def withdraw(): # 不能被重入 ... extcall 关键字使外部调用点在代码审查时显而易见。详见函数与控制流。","order":18,"section":"与 Solidity 的核心差异"},{"id":"design-tradeoffs#style-guidelines","href":"/docs/design-tradeoffs#style-guidelines","title":"设计取舍与风格建议","group":"工具与规范","description":"理解 Vyper 与 Solidity 的差异，并建立更易审计的编码习惯。","content":"语法对照与风格建议 版本声明 每个 Vyper 文件必须以版本 pragma 开头，文件扩展名为 .vy： #pragma version ^0.4.0 状态变量 // Solidity uint256 public counter; address private owner; # Vyper — 默认私有，用 public() 生成 getter counter: public(uint256) owner: address 函数声明 // Solidity function deposit() external payable returns (uint256) { return msg.value; } # Vyper — 装饰器指定可见性和可变性 @external @payable def deposit() -> uint256: return msg.value 构造函数 // Solidity constructor(address _owner) { owner = _owner; } # Vyper @deploy def init(owner: address): self.owner = owner 事件 // Solidity event Transfer(address indexed from, address indexed to, uint256 value); emit Transfer(msg.sender, to, amount); # Vyper — log 替代 emit event Transfer: sender: indexed(address) receiver: indexed(address) amount: uint256 log Transfer(msg.sender, to, amount) 映射 // Solidity mapping(address => uint256) public balances; mapping(address => mapping(address => uint256)) public allowances; # Vyper balances: public(HashMap[address, uint256]) allowances: public(HashMap[address, HashMap[address, uint256]]) 接口 // Solidity interface IERC20 { function transfer(address to, uint256 amount) external returns (bool); } # Vyper（内联声明） interface IERC20: def transfer(to: address, amount: uint256) -> bool: nonpayable 或导入内建接口 from ethereum.ercs import IERC20 错误处理 // Solidity require(amount > 0, \"Amount must be positive\"); revert(\"Operation failed\"); # Vyper assert amount > 0, \"Amount must be positive\" raise \"Operation failed\" Self 引用 状态变量访问必须加 self. 前缀，使存储操作（成本更高）在审查时一目了然： self.counter = self.counter + 1 外部调用 // Solidity IERC20(token).transfer(to, amount); uint256 balance = IERC20(token).balanceOf(address(this)); # Vyper — extcall/staticcall 显式标记外部调用 extcall IERC20(token).transfer(to, amount) balance: uint256 = staticcall IERC20(token).balanceOf(self) 常量与 Immutable // Solidity uint256 constant FEE = 100; address immutable owner; constructor() { owner = msg.sender; } # Vyper FEE: constant(uint256) = 100 owner: immutable(address) @deploy def init(): owner = msg.sender 默认函数 // Solidity — 两个独立函数 fallback() external payable { } receive() external payable { } # Vyper — 单一 default 函数 @external @payable def default(): pass","order":18,"section":"语法对照与风格建议"},{"id":"design-tradeoffs#documentation-habits","href":"/docs/design-tradeoffs#documentation-habits","title":"设计取舍与风格建议","group":"工具与规范","description":"理解 Vyper 与 Solidity 的差异，并建立更易审计的编码习惯。","content":"编码风格与文档习惯 以下原则来自 Vyper 编译器项目的风格指南，但同样适用于合约代码。 命名规范 模块名：全小写，可用下划线 类名：大驼峰（CapWords） 函数/方法名：小写加下划线 常量：全大写加下划线 布尔值：使用 is 前缀（如 isactive），避免双重否定（不要 isnotset） 方法命名约定 前缀 含义 --- --- get_ 简单数据获取，无副作用 fetch_ 可能有副作用的获取 build_ 从其他数据创建新对象 set_ 添加或修改值 add_ 添加新属性（已存在则抛异常） validate_ 验证，无返回或抛异常 compare_ 比较，返回布尔值 文档写作原则 用祈使式现在时描述 API：用\"返回\"而非\"返回了\" 术语前后保持一致 避免模糊代词 每段只讲一个主题，每句只讲一个想法 新功能不应脱离文档单独落地 测试原则 每个测试验证单一行为 测试之间不能有依赖关系 优先使用参数化测试和基于属性的测试 不使用 mock 提交信息 推荐遵循 Conventional Commits 规范： [optional scope]: [optional body] 常见类型：fix（补丁）、feat（新功能）、docs、style、refactor、test、chore。 最佳实践： 标题行限制 50 字符 使用祈使式现在时 正文解释\"为什么\"而非\"怎么做\" 为什么选择 Vyper 如果你有 Python 经验、希望编译器强制约束（无限循环、隐式转换、递归调用都不允许）、 偏好显式代码（大部分事情只有一种写法）、希望安全检查不能被全局关闭——Vyper 就是为你设计的。","order":18,"section":"编码风格与文档习惯"},{"id":"resources","href":"/docs/resources","title":"其他资源","group":"资源","description":"汇总课程、示例、工具、安全资料和演讲，补充 Vyper 学习与实践材料。","content":"汇总课程、示例、工具、安全资料和演讲，补充 Vyper 学习与实践材料。 这里收集了 Vyper 社区里常用的第三方学习材料、工具、课程和演讲。 这页更像索引，适合在你已经掌握基础语法之后继续扩展生态视野。","order":19},{"id":"resources#general","href":"/docs/resources#general","title":"其他资源","group":"资源","description":"汇总课程、示例、工具、安全资料和演讲，补充 Vyper 学习与实践材料。","content":"综合学习资源 Cyfrin Updraft - Python & Vyper Ape Academy – Learn how to build Vyper projects More Vyper by Example Vyper greatest hits smart contract examples A curated list of Vyper resources, libraries, tools, and more","order":19,"section":"综合学习资源"},{"id":"resources#frameworks-and-tooling","href":"/docs/resources#frameworks-and-tooling","title":"其他资源","group":"资源","description":"汇总课程、示例、工具、安全资料和演讲，补充 Vyper 学习与实践材料。","content":"框架与工具 Titanoboa：带漂亮 traceback、forking 和调试能力的 Vyper 解释器。 Moccasin：构建在 Titanoboa 之上的 Vyper/Python 开发与测试框架。 ApeWorX：面向 Python 开发者、安全研究员和数据科学家的 Ethereum 框架。 VyperDeployer：在 Foundry 中编译和测试 Vyper 合约的辅助合约。 snekmate：一组 Vyper 合约构建积木。 Ethereum.org 上的 Vyper 工具列表","order":19,"section":"框架与工具"},{"id":"resources#security","href":"/docs/resources#security","title":"其他资源","group":"资源","description":"汇总课程、示例、工具、安全资料和演讲，补充 Vyper 学习与实践材料。","content":"安全 VyperPunk：围绕 Vyper 合约安全与攻击面的学习仓库。","order":19,"section":"安全"},{"id":"resources#presentations","href":"/docs/resources#presentations","title":"其他资源","group":"资源","description":"汇总课程、示例、工具、安全资料和演讲，补充 Vyper 学习与实践材料。","content":"会议与演讲 Vyper Smart Contract Programming Language by Patrick Collins (2022, 30 mins) Python and DeFi by Curve Finance (2022, 15 mins) My experience with Vyper over the years by Benjamin Scherrey (2022, 15 mins) Short introduction to Vyper by Edison Que (3 mins)","order":19,"section":"会议与演讲"},{"id":"resources#unmaintained","href":"/docs/resources#unmaintained","title":"其他资源","group":"资源","description":"汇总课程、示例、工具、安全资料和演讲，补充 Vyper 学习与实践材料。","content":"维护状态较弱的资源 这些资料一段时间没有更新，但仍然可能有参考价值： Awesome Vyper curated resources Brownie – Python framework for developing smart contracts (deprecated) Foundry x Vyper – Foundry template to compile Vyper contracts Vyper Hub for development（自 2021 年后基本不再维护）","order":19,"section":"维护状态较弱的资源"},{"id":"contributing","href":"/docs/contributing","title":"参与贡献","group":"资源","description":"了解 Vyper 上游社区对 issue、VIP、Pull Request 和测试补充的基本要求。","content":"了解 Vyper 上游社区对 issue、VIP、Pull Request 和测试补充的基本要求。 这一页说明了如何为 Vyper 项目贡献代码、文档、讨论和问题报告。 如果你准备参与上游社区，这一页给出了最基础的协作规则。","order":20},{"id":"contributing#types-of-contributions","href":"/docs/contributing#types-of-contributions","title":"参与贡献","group":"资源","description":"了解 Vyper 上游社区对 issue、VIP、Pull Request 和测试补充的基本要求。","content":"贡献类型 官方明确表示，以下几类贡献都很有价值： 改进文档。 在 StackExchange 和 GitHub Discussions 回答其他用户问题。 参与 Vyper Discord 社区讨论。 提出改进建议。 修复并响应 GitHub issues。 如果你刚接触 Vyper，先熟悉安装流程、编译流程和合约编写方式，会更容易进入贡献状态。","order":20,"section":"贡献类型"},{"id":"contributing#suggesting-improvements","href":"/docs/contributing#suggesting-improvements","title":"参与贡献","group":"资源","description":"了解 Vyper 上游社区对 issue、VIP、Pull Request 和测试补充的基本要求。","content":"提出改进 如果你要提出结构性改进，官方建议使用 Vyper Improvement Proposal，也就是 VIP。 参考入口是官方仓库里的 VIP 模板： VIP 模板 这类提案适合放语言设计、编译器行为或工作流层面的改动，不适合拿来报普通 bug。","order":20,"section":"提出改进"},{"id":"contributing#reporting-issues","href":"/docs/contributing#reporting-issues","title":"参与贡献","group":"资源","description":"了解 Vyper 上游社区对 issue、VIP、Pull Request 和测试补充的基本要求。","content":"报告问题 提交 issue 时，官方建议至少包含以下信息： 你使用的 Vyper 版本。 相关源码片段。 运行平台。 操作系统名称与版本。 复现步骤。 实际结果。 预期行为。 能把问题最小化成一个可复现示例会非常有帮助，它不仅方便维护者定位问题，也经常能帮助你自己发现误解。 问题入口： Vyper Issues","order":20,"section":"报告问题"},{"id":"contributing#pull-request-workflow","href":"/docs/contributing#pull-request-workflow","title":"参与贡献","group":"资源","description":"了解 Vyper 上游社区对 issue、VIP、Pull Request 和测试补充的基本要求。","content":"Pull Request 工作流 提交代码贡献时，参考文档给出的要求包括： 从 master 分支 fork 出自己的分支。 提交信息不仅说明“改了什么”，也说明“为什么这样改”。 如果需要同步上游 master，优先使用 git rebase，避免 git merge。 新功能应补足相应的 pytest 测试，并放到 tests/ 下。 大改动最好先到 Discord 的 #compiler-dev 频道沟通。 提 PR 之前，确保本地构建和支持的 Python 版本测试都能通过。 提交信息 官方文档还专门强调了 commit message 质量，并引用了这篇经典文章： How to Write a Git Commit Message 如果使用上游仓库自带格式化脚本，原始流程是： 把提交信息写到文本文件里。 运行 python fmtcommitmsg.py commitmsg.txt 原地格式化。 把格式化后的内容放进 PR 描述，并用三反引号包起来。 维护者在 squash merge 时会复用这段信息。","order":20,"section":"Pull Request 工作流"},{"id":"versioning","href":"/docs/versioning","title":"版本策略","group":"资源","description":"理解 Major、Minor、Patch 和预发布版本分别会影响哪些使用者与升级路径。","content":"理解 Major、Minor、Patch 和预发布版本分别会影响哪些使用者与升级路径。 Vyper 定义了明确的版本策略，核心目标是让开发者、集成方和审计方 都能判断一次版本升级会影响什么，以及自己需要重点关注哪些变化。","order":21},{"id":"versioning#motivation","href":"/docs/versioning#motivation","title":"版本策略","group":"资源","description":"理解 Major、Minor、Patch 和预发布版本分别会影响哪些使用者与升级路径。","content":"为什么需要版本指南 Vyper 官方把使用者分成三类： 用户组 典型使用方式 --- --- 开发者（Developers） 用 Vyper 编写和编译智能合约 集成方（Integrators） 把 Vyper 包或 CLI 集成进工具链 审计方（Auditors） 关注 Vyper 语言特性和安全问题 Vyper 的“公共 API”不只有 Python 包和 CLI，还包括语言语法本身。 这意味着一旦语法、导出模块或命令行行为发生变化，不同角色受到的影响也不同。 版本指南就是为了明确这种影响边界。","order":21,"section":"为什么需要版本指南"},{"id":"versioning#version-types","href":"/docs/versioning#version-types","title":"版本策略","group":"资源","description":"理解 Major、Minor、Patch 和预发布版本分别会影响哪些使用者与升级路径。","content":"版本类型 Vyper 采用接近语义化版本的格式： MAJOR.MINOR.PATCH[-STAGE.DEVNUM] Major 版本 X.0.0 Major 版本允许发生向后不兼容的语法变化，例如 v1.x 升到 v2.x。 对开发者来说，这通常意味着较大范围的迁移成本。 发布要求与影响重点： 用户组 主要关注点 --- --- 开发者 语法弃用、重大新特性 集成方 通常无额外变化 审计方 审计报告及已修复问题 官方还要求在 Major 发布前进行审计，并解决审计中报告的 moderate 或 severe 级别漏洞；minor 和 informational 级别通常也应处理，但最终可由维护者判断。 Minor 版本 x.Y.0 Minor 版本可以引入新特性，也可以修复 moderate 或 severe 级别漏洞。 它可能会以不完全向后兼容的方式改变包 API 或 CLI 行为，因此集成方需要特别留意。 用户组 主要关注点 --- --- 开发者 新特性、安全修复 集成方 外部 API 变化 审计方 moderate 或 severe 级补丁 Patch 版本 x.y.Z Patch 版本主要用于： 文档修复。 使用层 bug 修复。 minor 或 informational 级别漏洞修复。 错误消息与文档层面的外部 API 微调。 用户组 主要关注点 --- --- 开发者 文档更新、使用 bug 修复、错误消息变化 集成方 文档更新、使用 bug 修复、错误消息变化 审计方 minor 或 informational 级补丁","order":21,"section":"版本类型"},{"id":"versioning#security-and-prereleases","href":"/docs/versioning#security-and-prereleases","title":"版本策略","group":"资源","description":"理解 Major、Minor、Patch 和预发布版本分别会影响哪些使用者与升级路径。","content":"安全与预发布版本 安全问题 随着 Vyper 演进，语言特性使用方式和编译器生成代码中都可能暴露安全漏洞。 官方的处理方式是遵循其安全策略： 安全策略：Vyper Security Policy 已公开安全通告：Published Security Advisories 这些漏洞的修复也会反映在 release notes 中。 预发布版本 官方定义了以下预发布标记： -alpha.N：工作进行中的版本。 -beta.N：计划最终发布的预览版本。 -rc.N：Major 版本前的候选版本。 其中 -rc.1 通常会触发一次外部审计，后续 RC 可能继续吸收审计反馈。 最后一个 RC 会成为下一个 Major 正式版本，并伴随完整审计报告一起发布。","order":21,"section":"安全与预发布版本"},{"id":"versioning#communication","href":"/docs/versioning#communication","title":"版本策略","group":"资源","description":"理解 Major、Minor、Patch 和预发布版本分别会影响哪些使用者与升级路径。","content":"PR 与发布沟通 参考文档还给了两条协作约束： Pull Request 应提交到 master 分支。 Major 和 Minor 版本应通过适当渠道对外沟通；Patch 通常不会专门宣发，除非有特别原因。 如果你在维护依赖 Vyper 的工具链，最实用的做法是： 把 Major 当作潜在迁移项目看待。 把 Minor 当作“需要认真读发布说明”的升级。 把 Patch 当作“可以较快跟进，但仍要看变更说明”的升级。","order":21,"section":"PR 与发布沟通"},{"id":"release-notes","href":"/docs/release-notes","title":"发布记录","group":"资源","description":"查看 Vyper 0.4.x 系列的版本更新、破坏性变更和安全修复。","content":"查看 Vyper 0.4.x 系列的版本更新、破坏性变更和安全修复。 本页汇总 Vyper 0.4.x 系列的发布记录。每个版本都包括破坏性变更、新功能、安全修复和工具链改进。 完整的安全审计报告可在 vyperlang/audits 仓库查看。","order":22},{"id":"release-notes#v0-4-3","href":"/docs/release-notes#v0-4-3","title":"发布记录","group":"资源","description":"查看 Vyper 0.4.x 系列的版本更新、破坏性变更和安全修复。","content":"v0.4.3 — \"Buttermilk Racer\" 发布日期： 2025-06-19 审计：ChainSecurity、Anatomist 破坏性变更 默认 EVM 版本改为 prague 新功能 新增 @raw_return 装饰器：允许合约直接返回原始字节而无需 ABI 编码，适用于代理合约场景 接口中禁止使用 @raw_return Venom 改进 多项 Venom 优化器管线改进，包括 DFTPass 修复、reduce single-use expansion、函数内联修复、LALR(1) 语法重写等。","order":22,"section":"v0.4.3"},{"id":"release-notes#v0-4-2","href":"/docs/release-notes#v0-4-2","title":"发布记录","group":"资源","description":"查看 Vyper 0.4.x 系列的版本更新、破坏性变更和安全修复。","content":"v0.4.2 — \"Lernaean Hydra\" 发布日期： 2025-05-31 审计：ChainSecurity、Anatomist 破坏性变更 sqrt 移至新的 stdlib math 模块（from vyper.builtins import math） 禁止从 @nonreentrant 函数调用其他 @nonreentrant 函数 禁止调用 default 函数 移除弃用的位运算内建函数（bitwise_and 等） --venom 重命名为 --venom-experimental 更新存储布局格式 新功能 默认重入保护：@nonreentrant 现在可通过 #pragma nonreentrancy on 全局启用 新增 raw_create() 内建函数 为 bytesM 类型启用位运算操作 asweivalue 扩展至所有数值类型 安全修复 concat() 零长度参数可能跳过副作用 (GHSA-vgf2-gvx8-xwc3) slice() 输出长度为 0 时可能跳过副作用 (GHSA-4w26-8p97-f4jp) Venom 改进 新增死存储消除、CSE 消除优化、调用约定改进。基准合约通常减小 5% 体积。","order":22,"section":"v0.4.2"},{"id":"release-notes#v0-4-1","href":"/docs/release-notes#v0-4-1","title":"发布记录","group":"资源","description":"查看 Vyper 0.4.x 系列的版本更新、破坏性变更和安全修复。","content":"v0.4.1 — \"Tokara Habu\" 发布日期： 2025-03-01 主要是打磨版本，聚焦于 bug 修复、UX 改进和安全修复（4 个低中等级 GHSA 通告）。Venom 管线取得了显著进步。 破坏性变更 .vyi 文件中 @external 修饰符变为可选 即使设置了 skipcontractcheck 也检查 returndatasize 修复 IERC4626 签名 禁止绝对相对导入 新功能 module.at() 用于转换为接口 事件实例化支持关键字参数 原生十六进制字符串字面量 引入 mana 作为 gas 的别名 支持从导入接口中使用 flag 类型 安全修复 某些预编译合约的成功状态未检查 AugAssign 求值顺序导致对象内 OOB 写入 sqrt 未定义舍入行为 for 列表迭代器的多次求值问题 工具改进 新增 -Werror 和 -Wnone 选项 支持通过 json 和 .vyz 输入的存储布局 initcode 中添加完整性哈希","order":22,"section":"v0.4.1"},{"id":"release-notes#v0-4-0","href":"/docs/release-notes#v0-4-0","title":"发布记录","group":"资源","description":"查看 Vyper 0.4.x 系列的版本更新、破坏性变更和安全修复。","content":"v0.4.0 — \"Nagini\" 发布日期： 2024-06-20 v0.4.0 是 Vyper 的重大版本升级。核心改动包括全新的导入/模块系统和实验性 Venom 后端。 审计：ChainSecurity（3 轮）、Statemind、OtterSec（2 轮） 破坏性变更 默认 EVM 版本改为 cancun 移除命名重入锁（改为全局锁） block.prevrandao 签名变更 decimal 的 ABI 类型改为 int168 abiencode/abidecode 重命名（移除前缀下划线） decimal 需要显式 feature flag 启用 @internal 装饰器变为可选 外部调用需要 extcall/staticcall 关键字 引入 //（整数除法），禁止对整数使用 / 结构体实例化使用关键字参数 循环变量需要类型标注 enum 关键字替换为 flag 移除内建常量 移除 Istanbul 和 Berlin 支持 range 支持双参数加 bound vyper.interfaces 重命名为 ethereum.ercs 模块系统 这是 v0.4.0 最核心的新功能： 实现\"无状态\"模块 单例模块与所有权层级 函数导出（exports） ABI 中自动导出事件 支持导入模块中的常量和接口定义 Venom IR 全新的中间表示，受 LLVM IR 启发： SSA（静态单赋值） mem2var 和 SCCP 优化 存储消除和字面量提取 代数优化和分支优化 其他新功能 blobhash() 内建函数 支持 block.blobbasefee create 内建函数支持 revertonfailure 参数 允许字节串的向下转型","order":22,"section":"v0.4.0"},{"id":"examples/simple-auction","href":"/docs/examples/simple-auction","title":"简单拍卖","group":"示例","description":"公开拍卖合约：参与者在限定时间内提交出价，最高出价者胜出。","content":"公开拍卖合约：参与者在限定时间内提交出价，最高出价者胜出。 一个简单的公开拍卖合约：参与者在限定时间内提交出价，拍卖结束后最高出价者的资金发送给受益人。 学习用途 本示例仅用于学习目的。请勿在未经充分审查和测试的情况下用于生产环境。","order":23},{"id":"examples/simple-auction#overview","href":"/docs/examples/simple-auction#overview","title":"简单拍卖","group":"示例","description":"公开拍卖合约：参与者在限定时间内提交出价，最高出价者胜出。","content":"概览 这个合约实现了最基础的拍卖逻辑： 部署时设定受益人、拍卖开始时间和竞价时长 参与者在竞价期内通过 bid() 提交出价（附带 ETH） 被超越的出价者可以通过 withdraw() 取回资金（提款模式） 拍卖结束后调用 endAuction() 将最高出价发送给受益人","order":23,"section":"概览"},{"id":"examples/simple-auction#contract-code","href":"/docs/examples/simple-auction#contract-code","title":"简单拍卖","group":"示例","description":"公开拍卖合约：参与者在限定时间内提交出价，最高出价者胜出。","content":"完整合约代码 #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]) Create a simple auction with auctionstart and biddingtime seconds bidding time on behalf of the beneficiary address _beneficiary. @deploy def init(beneficiary: address, auctionstart: uint256, bidding_time: uint256): self.beneficiary = _beneficiary self.auctionStart = auctionstart self.auctionEnd = self.auctionStart + biddingtime assert block.timestamp < self.auctionEnd @external @payable def bid(): assert block.timestamp >= self.auctionStart assert block.timestamp < self.auctionEnd assert msg.value > self.highestBid self.pendingReturns[self.highestBidder] += self.highestBid self.highestBidder = msg.sender self.highestBid = msg.value @external def withdraw(): pending_amount: uint256 = self.pendingReturns[msg.sender] self.pendingReturns[msg.sender] = 0 send(msg.sender, pending_amount) @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)","order":23,"section":"完整合约代码"},{"id":"examples/simple-auction#walkthrough","href":"/docs/examples/simple-auction#walkthrough","title":"简单拍卖","group":"示例","description":"公开拍卖合约：参与者在限定时间内提交出价，最高出价者胜出。","content":"代码解析 状态变量 合约使用 public() 声明了受益人地址、拍卖时间范围、当前最高出价者和出价金额等关键状态。pendingReturns 是一个 HashMap，用于跟踪每个地址待退还的金额，这是提款模式的核心数据结构。 构造函数 init 接收受益人地址、拍卖开始时间和竞价时长。通过 assert 确保拍卖结束时间在未来。 bid() — 提交出价 标记为 @payable，用户调用时附带 ETH 作为出价。三个 assert 检查：竞价期已开始、未结束、新出价高于当前最高。通过将前一个最高出价累加到 pendingReturns 来实现提款模式。 withdraw() — 提取退款 被超越的出价者调用此函数取回资金。先读取待退金额，清零后再发送——这个顺序防止了重入攻击。 endAuction() — 结束拍卖 遵循\"检查-效果-交互\"模式：先检查条件，再修改状态，最后与外部交互。这是 Solidity 和 Vyper 中推荐的安全编码模式。","order":23,"section":"代码解析"},{"id":"examples/blind-auction","href":"/docs/examples/blind-auction","title":"盲拍","group":"示例","description":"加密出价拍卖：竞价阶段提交哈希，揭示阶段验证真实出价。","content":"加密出价拍卖：竞价阶段提交哈希，揭示阶段验证真实出价。 盲拍合约：参与者提交加密出价，竞价结束后进入揭示阶段，最终确定最高出价者。 学习用途 本示例仅用于学习目的。请勿在未经充分审查和测试的情况下用于生产环境。","order":24},{"id":"examples/blind-auction#overview","href":"/docs/examples/blind-auction#overview","title":"盲拍","group":"示例","description":"加密出价拍卖：竞价阶段提交哈希，揭示阶段验证真实出价。","content":"概览 与公开拍卖不同，盲拍分为两个阶段： 竞价阶段：参与者提交加密的出价哈希（keccak256 编码的价格 + 是否虚假标记 + 密钥） 揭示阶段：参与者揭示真实出价，合约验证哈希匹配后确定有效出价 这种设计消除了竞价末期的时间压力，因为出价在揭示前是保密的。由于 Vyper 不支持动态数组，每个地址最多可提交 128 个出价。","order":24,"section":"概览"},{"id":"examples/blind-auction#contract-code","href":"/docs/examples/blind-auction#contract-code","title":"盲拍","group":"示例","description":"加密出价拍卖：竞价阶段提交哈希，揭示阶段验证真实出价。","content":"完整合约代码 #pragma version >0.3.10 Blind Auction struct Bid: blindedBid: bytes32 deposit: uint256 MAX_BIDS: constant(int128) = 128 event AuctionEnded: highestBidder: address highestBid: uint256 beneficiary: public(address) biddingEnd: public(uint256) revealEnd: public(uint256) ended: public(bool) highestBid: public(uint256) highestBidder: public(address) bids: HashMap[address, Bid[128]] bidCounts: HashMap[address, int128] pendingReturns: HashMap[address, uint256] @deploy def init(beneficiary: address, biddingTime: uint256, _revealTime: uint256): self.beneficiary = _beneficiary self.biddingEnd = block.timestamp + _biddingTime self.revealEnd = self.biddingEnd + _revealTime @external @payable def bid(_blindedBid: bytes32): assert block.timestamp < self.biddingEnd numBids: int128 = self.bidCounts[msg.sender] assert numBids < MAX_BIDS self.bids[msg.sender][numBids] = Bid( blindedBid=_blindedBid, deposit=msg.value ) self.bidCounts[msg.sender] += 1 @internal def placeBid(bidder: address, _value: uint256) -> bool: if (_value <= self.highestBid): return False if (self.highestBidder != empty(address)): self.pendingReturns[self.highestBidder] += self.highestBid self.highestBid = _value self.highestBidder = bidder return True @external def reveal(numBids: int128, values: uint256[128], fakes: bool[128], secrets: bytes32[128]): assert block.timestamp > self.biddingEnd assert block.timestamp < self.revealEnd assert _numBids == self.bidCounts[msg.sender] refund: uint256 = 0 for i: int128 in range(MAX_BIDS): if (i >= _numBids): break bidToCheck: Bid = (self.bids[msg.sender])[i] value: uint256 = _values[i] fake: bool = _fakes[i] secret: bytes32 = _secrets[i] blindedBid: bytes32 = keccak256(concat( convert(value, bytes32), convert(fake, bytes32), secret )) assert blindedBid == bidToCheck.blindedBid refund += bidToCheck.deposit if (not fake and bidToCheck.deposit >= value): if (self.placeBid(msg.sender, value)): refund -= value zeroBytes32: bytes32 = empty(bytes32) bidToCheck.blindedBid = zeroBytes32 if (refund != 0): send(msg.sender, refund) @external def withdraw(): pendingAmount: uint256 = self.pendingReturns[msg.sender] if (pendingAmount > 0): self.pendingReturns[msg.sender] = 0 send(msg.sender, pendingAmount) @external def auctionEnd(): assert block.timestamp > self.revealEnd assert not self.ended log AuctionEnded(highestBidder=self.highestBidder, highestBid=self.highestBid) self.ended = True send(self.beneficiary, self.highestBid)","order":24,"section":"完整合约代码"},{"id":"examples/blind-auction#walkthrough","href":"/docs/examples/blind-auction#walkthrough","title":"盲拍","group":"示例","description":"加密出价拍卖：竞价阶段提交哈希，揭示阶段验证真实出价。","content":"代码解析 数据结构 Bid 结构体包含加密出价哈希和押金。使用固定大小数组 Bid[128] 限制每个地址的最大出价数。 竞价阶段（bid） 参与者提交 keccak256(concat(convert(value, bytes32), convert(fake, bytes32), secret)) 作为加密出价，同时附带押金。押金可以大于实际出价（用于隐藏真实金额），也可以设置 fake=True 来提交虚假出价。 揭示阶段（reveal） 参与者提供所有出价的原始值、虚假标记和密钥。合约重新计算哈希并验证匹配。对于有效的非虚假出价，如果成为最高出价，则扣除对应金额；其余作为退款返还。 内部函数 placeBid 遵循提款模式：如果新出价更高，将前最高出价者的资金记入 pendingReturns，而非直接发送。","order":24,"section":"代码解析"},{"id":"examples/safe-remote-purchase","href":"/docs/examples/safe-remote-purchase","title":"安全远程购买","group":"示例","description":"通过双方押金机制确保买卖双方在无信任环境下完成交易。","content":"通过双方押金机制确保买卖双方在无信任环境下完成交易。 安全远程购买合约：通过双方押金机制确保买卖双方在无信任环境下完成交易。 学习用途 本示例仅用于学习目的。请勿在未经充分审查和测试的情况下用于生产环境。","order":25},{"id":"examples/safe-remote-purchase#overview","href":"/docs/examples/safe-remote-purchase#overview","title":"安全远程购买","group":"示例","description":"通过双方押金机制确保买卖双方在无信任环境下完成交易。","content":"概览 交易流程： 卖方发布商品并提交双倍商品价值的押金（余额 = 2x） 买方以商品价值购买并额外提交等额押金（余额 = 4x） 卖方发货 买方确认收货 — 买方退还押金（x），卖方退还押金（2x）+ 商品价值（x）","order":25,"section":"概览"},{"id":"examples/safe-remote-purchase#contract-code","href":"/docs/examples/safe-remote-purchase#contract-code","title":"安全远程购买","group":"示例","description":"通过双方押金机制确保买卖双方在无信任环境下完成交易。","content":"完整合约代码 #pragma version >0.3.10 Safe Remote Purchase value: public(uint256) seller: public(address) buyer: public(address) unlocked: public(bool) ended: public(bool) finalized: public(bool) @deploy @payable def init(): assert (msg.value % 2) == 0 assert msg.value > 0 self.value = msg.value // 2 self.seller = msg.sender self.unlocked = True @external def abort(): assert not self.finalized assert self.unlocked assert msg.sender == self.seller self.finalized = True assert self.balance > 0 and self.balance == 2 * self.value send(self.seller, self.balance) @external @payable def purchase(): assert not self.finalized assert self.unlocked assert msg.value == (2 * self.value) self.buyer = msg.sender self.unlocked = False @external def received(): assert not self.finalized assert not self.unlocked assert msg.sender == self.buyer assert not self.ended self.ended = True self.finalized = True send(self.buyer, self.value) assert self.balance == 3 * self.value send(self.seller, self.balance)","order":25,"section":"完整合约代码"},{"id":"examples/safe-remote-purchase#walkthrough","href":"/docs/examples/safe-remote-purchase#walkthrough","title":"安全远程购买","group":"示例","description":"通过双方押金机制确保买卖双方在无信任环境下完成交易。","content":"代码解析 押金机制 构造函数要求卖方发送偶数金额的 ETH 作为双倍押金。value 存储商品的实际价值（发送金额的一半）。 abort() — 取消交易 仅卖方可在买方购买前取消，退回全部押金。 purchase() — 买方购买 买方必须发送恰好双倍商品价值的 ETH（一份是商品价格，一份是押金）。购买后合约锁定。 received() — 确认收货 买方确认收货后，合约按规则分配资金：买方拿回押金（1x），卖方拿回押金加商品价值（3x）。","order":25,"section":"代码解析"},{"id":"examples/crowdfund","href":"/docs/examples/crowdfund","title":"众筹","group":"示例","description":"众筹合约：达到目标则资金发送给受益人，否则参与者可以退款。","content":"众筹合约：达到目标则资金发送给受益人，否则参与者可以退款。 众筹合约：参与者向活动捐款，达到目标则资金发送给受益人，否则参与者可以退款。 学习用途 本示例仅用于学习目的。请勿在未经充分审查和测试的情况下用于生产环境。","order":26},{"id":"examples/crowdfund#overview","href":"/docs/examples/crowdfund#overview","title":"众筹","group":"示例","description":"众筹合约：达到目标则资金发送给受益人，否则参与者可以退款。","content":"概览 这个合约实现了经典的众筹逻辑： 部署时设定受益人、筹款目标和截止时间 参与者在截止前通过 participate() 捐款 截止后如果达到目标，调用 finalize() 将资金发送给受益人 如果未达目标，参与者通过 refund() 取回捐款","order":26,"section":"概览"},{"id":"examples/crowdfund#contract-code","href":"/docs/examples/crowdfund#contract-code","title":"众筹","group":"示例","description":"众筹合约：达到目标则资金发送给受益人，否则参与者可以退款。","content":"完整合约代码 #pragma version >0.3.10 example of a crowd funding contract funders: HashMap[address, uint256] beneficiary: address deadline: public(uint256) goal: public(uint256) timelimit: public(uint256) finalized: bool @deploy def init(beneficiary: address, goal: uint256, _timelimit: uint256): self.beneficiary = _beneficiary self.deadline = block.timestamp + _timelimit self.timelimit = _timelimit assert _goal > 0, \"Goal must be non-zero\" self.goal = _goal @external @payable def participate(): assert block.timestamp < self.deadline, \"deadline has expired\" assert not self.finalized self.funders[msg.sender] += msg.value @external def finalize(): assert block.timestamp >= self.deadline, \"deadline has not expired yet\" assert self.balance >= self.goal, \"goal has not been reached\" assert self.balance > 0 self.finalized = True send(self.beneficiary, self.balance) @external def refund(): assert block.timestamp >= self.deadline and self.balance < self.goal assert not self.finalized assert self.funders[msg.sender] > 0 value: uint256 = self.funders[msg.sender] self.funders[msg.sender] = 0 send(msg.sender, value)","order":26,"section":"完整合约代码"},{"id":"examples/crowdfund#walkthrough","href":"/docs/examples/crowdfund#walkthrough","title":"众筹","group":"示例","description":"众筹合约：达到目标则资金发送给受益人，否则参与者可以退款。","content":"代码解析 状态变量 funders 映射记录每个地址的捐款金额。finalized 标记防止重复提取。goal 是筹款目标，deadline 是截止时间。 participate() — 参与捐款 标记为 @payable，检查截止时间和是否已完成，然后将捐款累加到调用者的记录中。同一地址可以多次捐款。 finalize() — 完成筹款 只有在截止时间过后、余额达到目标时才能调用。使用 self.balance 直接检查合约余额，设置 finalized 后将全部余额发送给受益人。 refund() — 退款 截止后如果余额未达目标，参与者可以取回自己的捐款。先读取金额、清零、再发送——标准的提款模式。","order":26,"section":"代码解析"},{"id":"examples/voting","href":"/docs/examples/voting","title":"投票","group":"示例","description":"带委托功能的投票系统：支持投票权委托和最终统计。","content":"带委托功能的投票系统：支持投票权委托和最终统计。 投票合约：主席授权参与者投票，支持投票委托，最终统计胜出提案。 学习用途 本示例仅用于学习目的。请勿在未经充分审查和测试的情况下用于生产环境。","order":27},{"id":"examples/voting#overview","href":"/docs/examples/voting#overview","title":"投票","group":"示例","description":"带委托功能的投票系统：支持投票权委托和最终统计。","content":"概览 这个合约实现了一个带委托功能的投票系统： 主席（部署者）授予参与者投票权 每个参与者可以直接投票，或将投票权委托给另一个投票者 委托链会自动传递权重 调用 winningProposal() 返回票数最多的提案","order":27,"section":"概览"},{"id":"examples/voting#contract-code","href":"/docs/examples/voting#contract-code","title":"投票","group":"示例","description":"带委托功能的投票系统：支持投票权委托和最终统计。","content":"完整合约代码 #pragma version >0.3.10 Voting with delegation. struct Voter: weight: int128 voted: bool delegate: address vote: int128 struct Proposal: name: bytes32 voteCount: int128 voters: public(HashMap[address, Voter]) proposals: public(HashMap[int128, Proposal]) voterCount: public(int128) chairperson: public(address) int128Proposals: public(int128) @view @internal def _delegated(addr: address) -> bool: return self.voters[addr].delegate != empty(address) @view @external def delegated(addr: address) -> bool: return self._delegated(addr) @view @internal def _directlyVoted(addr: address) -> bool: return self.voters[addr].voted and (self.voters[addr].delegate == empty(address)) @view @external def directlyVoted(addr: address) -> bool: return self._directlyVoted(addr) @deploy def init(_proposalNames: bytes32[2]): self.chairperson = msg.sender self.voterCount = 0 for i: int128 in range(2): self.proposals[i] = Proposal( name=_proposalNames[i], voteCount=0 ) self.int128Proposals += 1 @external def giveRightToVote(voter: address): assert msg.sender == self.chairperson assert not self.voters[voter].voted assert self.voters[voter].weight == 0 self.voters[voter].weight = 1 self.voterCount += 1 @internal def forwardWeight(delegatewithweightto_forward: address): assert self.delegated(delegatewithweightto_forward) assert self.voters[delegatewithweighttoforward].weight > 0 target: address = self.voters[delegatewithweighttoforward].delegate for i: int128 in range(4): if self._delegated(target): target = self.voters[target].delegate assert target != delegatewithweighttoforward else: break weighttoforward: int128 = self.voters[delegatewithweighttoforward].weight self.voters[delegatewithweighttoforward].weight = 0 self.voters[target].weight += weighttoforward if self._directlyVoted(target): self.proposals[self.voters[target].vote].voteCount += weighttoforward self.voters[target].weight = 0 @external def forwardWeight(delegatewithweighttoforward: address): self.forwardWeight(delegatewithweightto_forward) @external def delegate(to: address): assert not self.voters[msg.sender].voted assert to != msg.sender assert to != empty(address) self.voters[msg.sender].voted = True self.voters[msg.sender].delegate = to self._forwardWeight(msg.sender) @external def vote(proposal: int128): assert not self.voters[msg.sender].voted assert proposal < self.int128Proposals self.voters[msg.sender].vote = proposal self.voters[msg.sender].voted = True self.proposals[proposal].voteCount += self.voters[msg.sender].weight self.voters[msg.sender].weight = 0 @view @internal def _winningProposal() -> int128: winningvotecount: int128 = 0 winning_proposal: int128 = 0 for i: int128 in range(2): if self.proposals[i].voteCount > winningvotecount: winningvotecount = self.proposals[i].voteCount winning_proposal = i return winning_proposal @view @external def winningProposal() -> int128: return self._winningProposal() @view @external def winnerName() -> bytes32: return self.proposals[self._winningProposal()].name","order":27,"section":"完整合约代码"},{"id":"examples/voting#walkthrough","href":"/docs/examples/voting#walkthrough","title":"投票","group":"示例","description":"带委托功能的投票系统：支持投票权委托和最终统计。","content":"代码解析 数据结构 Voter 结构体包含投票权重（weight）、是否已投票（voted）、委托对象（delegate）和投票选择（vote）。Proposal 包含提案名称和累计票数。 权限控制 只有主席（chairperson，即合约部署者）可以调用 giveRightToVote 授予投票权。每个投票者初始权重为 1。 投票委托 delegate() 允许将投票权委托给其他人。_forwardWeight 内部函数处理委托链传递，最多跟踪 4 层委托（防止无限循环）。如果最终目标已直接投票，权重会自动累加到对应提案。 直接投票 vote() 将调用者的权重直接加到指定提案的票数上，并将权重清零以防重复投票。 查询结果 winningProposal() 遍历所有提案返回票数最多的索引，winnerName() 返回获胜提案的名称。","order":27,"section":"代码解析"},{"id":"examples/company-stock","href":"/docs/examples/company-stock","title":"公司股票","group":"示例","description":"模拟股票发行、买卖、转让和公司付款的简化金融系统。","content":"模拟股票发行、买卖、转让和公司付款的简化金融系统。 公司股票合约：模拟股票发行、买卖、转让和公司付款的简化金融系统。 学习用途 本示例仅用于学习目的。请勿在未经充分审查和测试的情况下用于生产环境。","order":28},{"id":"examples/company-stock#overview","href":"/docs/examples/company-stock#overview","title":"公司股票","group":"示例","description":"模拟股票发行、买卖、转让和公司付款的简化金融系统。","content":"概览 这个合约模拟了一个简化的公司股票系统： 公司初始持有所有股票 用户可以用 ETH 按固定价格购买股票 股东可以回售股票换回 ETH 股东之间可以直接转让股票 公司可以用合约余额支付账单","order":28,"section":"概览"},{"id":"examples/company-stock#contract-code","href":"/docs/examples/company-stock#contract-code","title":"公司股票","group":"示例","description":"模拟股票发行、买卖、转让和公司付款的简化金融系统。","content":"完整合约代码 #pragma version >0.3.10 event Transfer: sender: indexed(address) receiver: indexed(address) value: uint256 event Buy: buyer: indexed(address) buy_order: uint256 event Sell: seller: indexed(address) sell_order: uint256 event Pay: vendor: indexed(address) amount: uint256 company: public(address) totalShares: public(uint256) price: public(uint256) holdings: HashMap[address, uint256] @deploy def init(company: address, totalshares: uint256, initialprice: uint256): assert totalshares > 0 assert initial_price > 0 self.company = _company self.totalShares = totalshares self.price = initial_price self.holdings[self.company] = totalshares @view @external def stockAvailable() -> uint256: return self._stockAvailable() @external @payable def buyStock(): buy_order: uint256 = msg.value // self.price assert self.stockAvailable() >= buyorder self.holdings[self.company] -= buy_order self.holdings[msg.sender] += buy_order log Buy(buyer=msg.sender, buyorder=buyorder) @view @external def getHolding(_stockholder: address) -> uint256: return self.getHolding(stockholder) @view @external def cash() -> uint256: return self.balance @external def sellStock(sell_order: uint256): assert sell_order > 0 assert self.getHolding(msg.sender) >= sellorder assert self.balance >= (sell_order * self.price) self.holdings[msg.sender] -= sell_order self.holdings[self.company] += sell_order send(msg.sender, sell_order * self.price) log Sell(seller=msg.sender, sellorder=sellorder) @external def transferStock(receiver: address, transfer_order: uint256): assert transfer_order > 0 assert self.getHolding(msg.sender) >= transferorder self.holdings[msg.sender] -= transfer_order self.holdings[receiver] += transfer_order log Transfer(sender=msg.sender, receiver=receiver, value=transfer_order) @external def payBill(vendor: address, amount: uint256): assert msg.sender == self.company assert self.balance >= amount send(vendor, amount) log Pay(vendor=vendor, amount=amount) @view @external def debt() -> uint256: return self._debt() @view @external def worth() -> uint256: return self.balance - self._debt() @view @internal def _debt() -> uint256: return (self.totalShares - self._stockAvailable()) * self.price @view @internal def _stockAvailable() -> uint256: return self.holdings[self.company] @view @internal def getHolding(stockholder: address) -> uint256: return self.holdings[_stockholder]","order":28,"section":"完整合约代码"},{"id":"examples/company-stock#walkthrough","href":"/docs/examples/company-stock#walkthrough","title":"公司股票","group":"示例","description":"模拟股票发行、买卖、转让和公司付款的简化金融系统。","content":"代码解析 事件系统 合约定义了四个事件（Transfer、Buy、Sell、Pay）用于链下监听交易活动。 买卖机制 buyStock 接收 ETH 按固定价格换算为股票数量（向下取整）。sellStock 反向操作，检查公司有足够 ETH 后回购股票。 公司财务 debt() 计算公司因售出股票产生的负债（已售股数 x 价格），worth() 返回净值（余额 - 负债）。payBill 仅允许公司地址调用。","order":28,"section":"代码解析"},{"id":"examples/storage","href":"/docs/examples/storage","title":"简单存储","group":"示例","description":"最基础的 Vyper 合约：一个状态变量和一个写入方法。","content":"最基础的 Vyper 合约：一个状态变量和一个写入方法。 最简存储合约：展示 Vyper 中状态变量的基本读写操作。 学习用途 本示例仅用于学习目的。请勿在未经充分审查和测试的情况下用于生产环境。","order":29},{"id":"examples/storage#overview","href":"/docs/examples/storage#overview","title":"简单存储","group":"示例","description":"最基础的 Vyper 合约：一个状态变量和一个写入方法。","content":"概览 这是 Vyper 最基础的合约示例，只有一个状态变量和一个写入方法。适合作为学习 Vyper 的第一个合约。","order":29,"section":"概览"},{"id":"examples/storage#contract-code","href":"/docs/examples/storage#contract-code","title":"简单存储","group":"示例","description":"最基础的 Vyper 合约：一个状态变量和一个写入方法。","content":"完整合约代码 #pragma version >0.3.10 storedData: public(int128) @deploy def init(_x: int128): self.storedData = _x @external def set(_x: int128): self.storedData = _x","order":29,"section":"完整合约代码"},{"id":"examples/storage#walkthrough","href":"/docs/examples/storage#walkthrough","title":"简单存储","group":"示例","description":"最基础的 Vyper 合约：一个状态变量和一个写入方法。","content":"代码解析 状态变量 storedData 声明为 public(int128)，编译器自动生成同名 getter 函数，外部可通过 contract.storedData() 读取。 构造函数 init 接收一个初始值并存入 storedData。 set() — 更新值 任何人都可以调用 set() 修改存储值。在实际应用中，通常会添加权限检查（如 assert msg.sender == self.owner）。","order":29,"section":"代码解析"},{"id":"examples/name-registry","href":"/docs/examples/name-registry","title":"名称注册表","group":"示例","description":"将名称映射到地址的简单注册表，先到先得。","content":"将名称映射到地址的简单注册表，先到先得。 名称注册表合约：将字节串名称映射到以太坊地址，每个名称只能注册一次。 学习用途 本示例仅用于学习目的。请勿在未经充分审查和测试的情况下用于生产环境。","order":30},{"id":"examples/name-registry#overview","href":"/docs/examples/name-registry#overview","title":"名称注册表","group":"示例","description":"将名称映射到地址的简单注册表，先到先得。","content":"概览 一个简单的名称注册表，支持注册和查询。名称一旦注册就不能被覆盖——先到先得。","order":30,"section":"概览"},{"id":"examples/name-registry#contract-code","href":"/docs/examples/name-registry#contract-code","title":"名称注册表","group":"示例","description":"将名称映射到地址的简单注册表，先到先得。","content":"完整合约代码 #pragma version >0.3.10 registry: HashMap[Bytes[100], address] @external def register(name: Bytes[100], owner: address): assert self.registry[name] == empty(address) # check name has not been set yet. self.registry[name] = owner @view @external def lookup(name: Bytes[100]) -> address: return self.registry[name]","order":30,"section":"完整合约代码"},{"id":"examples/name-registry#walkthrough","href":"/docs/examples/name-registry#walkthrough","title":"名称注册表","group":"示例","description":"将名称映射到地址的简单注册表，先到先得。","content":"代码解析 注册逻辑 register 使用 assert 确保名称尚未被注册（值为零地址），然后将名称与地址关联。这是一个不可逆操作。 查询 lookup 是一个 @view 函数，返回名称对应的地址。如果名称未注册，返回零地址。","order":30,"section":"代码解析"},{"id":"examples/erc20","href":"/docs/examples/erc20","title":"ERC-20 代币","group":"示例","description":"标准 ERC-20 代币实现，支持转账、授权、铸造和销毁。","content":"标准 ERC-20 代币实现，支持转账、授权、铸造和销毁。 ERC-20 代币合约：实现标准代币接口，支持转账、授权、铸造和销毁。 学习用途 本示例仅用于学习目的。请勿在未经充分审查和测试的情况下用于生产环境。","order":31},{"id":"examples/erc20#overview","href":"/docs/examples/erc20#overview","title":"ERC-20 代币","group":"示例","description":"标准 ERC-20 代币实现，支持转账、授权、铸造和销毁。","content":"概览 ERC-20 是以太坊上最常用的代币标准。这个合约实现了完整的 ERC-20 接口： transfer / transferFrom：代币转账 approve：授权第三方花费代币 mint / burn：铸造和销毁代币 使用 from ethereum.ercs import IERC20 导入标准接口","order":31,"section":"概览"},{"id":"examples/erc20#contract-code","href":"/docs/examples/erc20#contract-code","title":"ERC-20 代币","group":"示例","description":"标准 ERC-20 代币实现，支持转账、授权、铸造和销毁。","content":"完整合约代码 #pragma version >0.3.10 from ethereum.ercs import IERC20 from ethereum.ercs import IERC20Detailed implements: IERC20 implements: IERC20Detailed name: public(String[32]) symbol: public(String[32]) decimals: public(uint8) balanceOf: public(HashMap[address, uint256]) allowance: public(HashMap[address, HashMap[address, uint256]]) totalSupply: public(uint256) minter: address @deploy def init(name: String[32], symbol: String[32], decimals: uint8, supply: uint256): initsupply: uint256 = supply 10 * convert(_decimals, uint256) self.name = _name self.symbol = _symbol self.decimals = _decimals self.balanceOf[msg.sender] = init_supply self.totalSupply = init_supply self.minter = msg.sender log IERC20.Transfer(sender=empty(address), receiver=msg.sender, value=init_supply) @external def transfer(to: address, value: uint256) -> bool: self.balanceOf[msg.sender] -= _value self.balanceOf[to] += value log IERC20.Transfer(sender=msg.sender, receiver=to, value=value) return True @external def transferFrom(from: address, to: address, _value: uint256) -> bool: self.balanceOf[from] -= value self.balanceOf[to] += value self.allowance[from][msg.sender] -= value log IERC20.Transfer(sender=from, receiver=to, value=_value) return True @external def approve(spender: address, value: uint256) -> bool: self.allowance[msg.sender][spender] = value log IERC20.Approval(owner=msg.sender, spender=spender, value=value) return True @external def mint(to: address, value: uint256): assert msg.sender == self.minter assert _to != empty(address) self.totalSupply += _value self.balanceOf[to] += value log IERC20.Transfer(sender=empty(address), receiver=to, value=value) @internal def burn(to: address, _value: uint256): assert _to != empty(address) self.totalSupply -= _value self.balanceOf[to] -= value log IERC20.Transfer(sender=to, receiver=empty(address), value=value) @external def burn(_value: uint256): self.burn(msg.sender, value) @external def burnFrom(to: address, value: uint256): self.allowance[to][msg.sender] -= value self.burn(to, _value)","order":31,"section":"完整合约代码"},{"id":"examples/erc20#walkthrough","href":"/docs/examples/erc20#walkthrough","title":"ERC-20 代币","group":"示例","description":"标准 ERC-20 代币实现，支持转账、授权、铸造和销毁。","content":"代码解析 接口实现 使用 implements: IERC20 声明合约实现 ERC-20 标准。编译器会验证所有必需的函数签名是否正确实现。 溢出保护 Vyper 默认检查算术溢出。balanceOf[msg.sender] -= _value 在余额不足时自动 revert，无需额外的 require 检查。 事件日志 铸造使用 sender=empty(address) 表示从零地址转出，销毁使用 receiver=empty(address) 表示转入零地址。这是 ERC-20 标准的约定。 授权模式 approve + transferFrom 实现了两步授权转账。注意 approve 直接覆盖旧值（存在竞态条件风险，生产代码应考虑 increaseAllowance/decreaseAllowance 模式）。","order":31,"section":"代码解析"},{"id":"examples/erc721","href":"/docs/examples/erc721","title":"ERC-721 NFT","group":"示例","description":"ERC-721 非同质化代币实现，支持铸造、安全转账和授权。","content":"ERC-721 非同质化代币实现，支持铸造、安全转账和授权。 ERC-721 非同质化代币合约：实现 NFT 标准，支持铸造、转账、授权和安全转账。 学习用途 本示例仅用于学习目的。完整合约代码较长，请参阅 源码文件。","order":32},{"id":"examples/erc721#overview","href":"/docs/examples/erc721#overview","title":"ERC-721 NFT","group":"示例","description":"ERC-721 非同质化代币实现，支持铸造、安全转账和授权。","content":"概览 ERC-721 是以太坊上非同质化代币（NFT）的标准。每个代币都有唯一的 ID。本合约实现了： IERC721 和 IERC165 接口 safeTransferFrom：带接收者检查的安全转账 approve / setApprovalForAll：单个 / 批量授权 mint / burn：铸造和销毁 ERC721Receiver 回调接口","order":32,"section":"概览"},{"id":"examples/erc721#contract-code","href":"/docs/examples/erc721#contract-code","title":"ERC-721 NFT","group":"示例","description":"ERC-721 非同质化代币实现，支持铸造、安全转账和授权。","content":"核心结构 #pragma version >0.3.10 from ethereum.ercs import IERC165 from ethereum.ercs import IERC721 implements: IERC721 implements: IERC165 interface ERC721Receiver: def onERC721Received( _operator: address, _from: address, _tokenId: uint256, _data: Bytes[1024] ) -> bytes4: nonpayable idToOwner: HashMap[uint256, address] idToApprovals: HashMap[uint256, address] ownerToNFTokenCount: HashMap[address, uint256] ownerToOperators: HashMap[address, HashMap[address, bool]] minter: address SUPPORTED_INTERFACES: constant(bytes4[2]) = [ 0x01ffc9a7, # ERC165 0x80ac58cd, # ERC721 ]","order":32,"section":"核心结构"},{"id":"examples/erc721#walkthrough","href":"/docs/examples/erc721#walkthrough","title":"ERC-721 NFT","group":"示例","description":"ERC-721 非同质化代币实现，支持铸造、安全转账和授权。","content":"代码解析 ERC-165 支持 supportsInterface 检查请求的接口 ID 是否在支持列表中。这使其他合约能在调用前验证是否支持 ERC-721。 安全转账 safeTransferFrom 在转账后检查接收者是否是合约——如果是，调用 onERC721Received 回调并验证返回值。这防止了代币被发送到不能处理它们的合约中永久锁定。 授权机制 两级授权：approve 授权单个代币，setApprovalForAll 授权某地址操作所有者的全部代币（常用于 NFT 市场）。 铸造与销毁 mint 仅限 minter 调用，分配新的 token ID 给目标地址。burn 销毁代币并清除所有相关的授权状态。","order":32,"section":"代码解析"},{"id":"examples/erc1155","href":"/docs/examples/erc1155","title":"ERC-1155 多代币","group":"示例","description":"ERC-1155 多代币标准实现，支持批量操作和动态 URI。","content":"ERC-1155 多代币标准实现，支持批量操作和动态 URI。 ERC-1155 多代币合约：在单个合约中管理多种同质化和非同质化代币，支持批量操作。 学习用途 本示例仅用于学习目的。完整合约代码较长，请参阅 源码文件。","order":33},{"id":"examples/erc1155#overview","href":"/docs/examples/erc1155#overview","title":"ERC-1155 多代币","group":"示例","description":"ERC-1155 多代币标准实现，支持批量操作和动态 URI。","content":"概览 ERC-1155 是一种多代币标准，允许单个合约表示任意数量的同质化和非同质化代币类型。相比分别部署 ERC-20 和 ERC-721，ERC-1155 更节省 gas 且支持批量操作。 本合约实现了： 批量铸造和转账（safeBatchTransferFrom） 余额批量查询（balanceOfBatch） URI 管理（支持动态 URI） 所有者权限控制和暂停功能 OpenSea 兼容的 name 和 symbol","order":33,"section":"概览"},{"id":"examples/erc1155#contract-code","href":"/docs/examples/erc1155#contract-code","title":"ERC-1155 多代币","group":"示例","description":"ERC-1155 多代币标准实现，支持批量操作和动态 URI。","content":"核心结构 #pragma version >0.3.10 from ethereum.ercs import IERC165 BATCH_SIZE: constant(uint256) = 128 MAXURILENGTH: constant(uint256) = 300 owner: public(address) paused: public(bool) name: public(String[128]) symbol: public(String[16]) balances[id][owner] -> balance balanceOf: public(HashMap[uint256, HashMap[address, uint256]]) operator approvals isApprovedForAll: public(HashMap[address, HashMap[address, bool]])","order":33,"section":"核心结构"},{"id":"examples/erc1155#walkthrough","href":"/docs/examples/erc1155#walkthrough","title":"ERC-1155 多代币","group":"示例","description":"ERC-1155 多代币标准实现，支持批量操作和动态 URI。","content":"代码解析 批量操作 safeBatchTransferFrom 在单次交易中转移多种代币，显著降低多代币转账的 gas 成本。使用固定大小数组（BATCH_SIZE = 128）来限制单次批量操作的最大规模。 URI 系统 支持静态和动态两种 URI 模式。动态模式通过 uint2str 将代币 ID 转为字符串拼接到基础 URI 后，生成每个代币类型的元数据 URL。 安全检查 所有转账操作都会检查接收者是否为合约，如果是则调用 onERC1155Received 或 onERC1155BatchReceived 回调验证。","order":33,"section":"代码解析"},{"id":"examples/erc4626","href":"/docs/examples/erc4626","title":"ERC-4626 金库","group":"示例","description":"代币化金库标准实现，将 ERC-20 资产转化为可组合的份额代币。","content":"代币化金库标准实现，将 ERC-20 资产转化为可组合的份额代币。 ERC-4626 代币化金库合约：标准化收益聚合金库，将存入的 ERC-20 资产转化为可组合的份额代币。 学习用途 本示例仅用于学习目的。完整合约代码较长，请参阅 源码文件。","order":34},{"id":"examples/erc4626#overview","href":"/docs/examples/erc4626#overview","title":"ERC-4626 金库","group":"示例","description":"代币化金库标准实现，将 ERC-20 资产转化为可组合的份额代币。","content":"概览 ERC-4626 定义了代币化金库的标准接口，使得不同的 DeFi 协议可以统一处理收益聚合。核心概念： 用户存入底层资产（ERC-20 代币），获得金库份额 份额代表用户在金库中的比例 随着金库产生收益，份额的价值增长 用户可随时赎回份额换回底层资产","order":34,"section":"概览"},{"id":"examples/erc4626#contract-code","href":"/docs/examples/erc4626#contract-code","title":"ERC-4626 金库","group":"示例","description":"代币化金库标准实现，将 ERC-20 资产转化为可组合的份额代币。","content":"核心结构 #pragma version >0.3.10 from ethereum.ercs import IERC20 from ethereum.ercs import IERC4626 implements: IERC20 implements: IERC4626 totalSupply: public(uint256) balanceOf: public(HashMap[address, uint256]) allowance: public(HashMap[address, HashMap[address, uint256]]) asset: public(IERC20)","order":34,"section":"核心结构"},{"id":"examples/erc4626#walkthrough","href":"/docs/examples/erc4626#walkthrough","title":"ERC-4626 金库","group":"示例","description":"代币化金库标准实现，将 ERC-20 资产转化为可组合的份额代币。","content":"代码解析 资产与份额的转换 convertToShares 和 convertToAssets 实现资产与份额之间的换算。当金库总资产为零时，汇率为 1:1；否则按比例计算。 存取操作 deposit：存入指定数量的资产，铸造相应份额 mint：铸造指定数量的份额，扣取相应资产 withdraw：取出指定数量的资产，销毁相应份额 redeem：赎回指定数量的份额，返还相应资产 预览函数 previewDeposit、previewMint、previewWithdraw、previewRedeem 让用户在执行前预估结果，无需消耗 gas。 最大值限制 maxDeposit、maxMint、maxWithdraw、maxRedeem 返回当前条件下各操作的最大允许值。","order":34,"section":"代码解析"},{"id":"examples/market-maker","href":"/docs/examples/market-maker","title":"链上做市商","group":"示例","description":"基于恒定乘积公式的自动做市商，实现 ETH 与代币的去中心化兑换。","content":"基于恒定乘积公式的自动做市商，实现 ETH 与代币的去中心化兑换。 链上做市商合约：基于恒定乘积公式的自动做市商（AMM），实现 ETH 与代币之间的去中心化兑换。 学习用途 本示例仅用于学习目的。请勿在未经充分审查和测试的情况下用于生产环境。","order":35},{"id":"examples/market-maker#overview","href":"/docs/examples/market-maker#overview","title":"链上做市商","group":"示例","description":"基于恒定乘积公式的自动做市商，实现 ETH 与代币的去中心化兑换。","content":"概览 这个合约实现了最基础的 AMM： 使用恒定乘积公式 x * y = k 支持 ETH 换代币和代币换 ETH 收取 0.2% 手续费 所有者可提取流动性并关闭交易","order":35,"section":"概览"},{"id":"examples/market-maker#contract-code","href":"/docs/examples/market-maker#contract-code","title":"链上做市商","group":"示例","description":"基于恒定乘积公式的自动做市商，实现 ETH 与代币的去中心化兑换。","content":"完整合约代码 #pragma version >0.3.10 from ethereum.ercs import IERC20 totalEthQty: public(uint256) totalTokenQty: public(uint256) invariant: public(uint256) token: IERC20 owner: public(address) finalized: bool @external @payable def initiate(tokenaddr: address, tokenquantity: uint256): assert self.invariant == 0 self.token = IERC20(token_addr) extcall self.token.transferFrom(msg.sender, self, token_quantity) self.owner = msg.sender self.totalEthQty = msg.value self.totalTokenQty = token_quantity self.invariant = msg.value * token_quantity assert self.invariant > 0 @external @payable def ethToTokens(): assert not self.finalized fee: uint256 = msg.value // 500 ethinpurchase: uint256 = msg.value - fee newtotaleth: uint256 = self.totalEthQty + ethinpurchase newtotaltokens: uint256 = self.invariant // newtotaleth extcall self.token.transfer(msg.sender, self.totalTokenQty - newtotaltokens) self.totalEthQty = newtotaleth self.totalTokenQty = newtotaltokens @external def tokensToEth(sell_quantity: uint256): assert not self.finalized extcall self.token.transferFrom(msg.sender, self, sell_quantity) newtotaltokens: uint256 = self.totalTokenQty + sell_quantity newtotaleth: uint256 = self.invariant // newtotaltokens ethtosend: uint256 = self.totalEthQty - newtotaleth send(msg.sender, ethtosend) self.totalEthQty = newtotaleth self.totalTokenQty = newtotaltokens @external def ownerWithdraw(): assert self.owner == msg.sender self.finalized = True extcall self.token.transfer(self.owner, self.totalTokenQty) if self.balance > 0: send(self.owner, self.balance)","order":35,"section":"完整合约代码"},{"id":"examples/market-maker#walkthrough","href":"/docs/examples/market-maker#walkthrough","title":"链上做市商","group":"示例","description":"基于恒定乘积公式的自动做市商，实现 ETH 与代币的去中心化兑换。","content":"代码解析 恒定乘积公式 initiate 设置初始的 ETH 和代币数量，计算不变量 k = ethQty * tokenQty。后续每次交易都维持这个不变量。 兑换机制 ethToTokens：用户发送 ETH，合约根据新的 ETH 总量反算代币总量，差额即为用户获得的代币数。手续费通过在计算前扣除 0.2% 的 ETH 实现。 tokensToEth：反向操作。使用 extcall 从用户处转入代币，计算应发送的 ETH 数量。 注意 extcall 的使用 这个合约展示了 Vyper 0.4.0+ 要求的 extcall 关键字——所有修改外部状态的调用都必须显式标记。","order":35,"section":"代码解析"},{"id":"examples/factory","href":"/docs/examples/factory","title":"工厂模式","group":"示例","description":"通过代码哈希验证注册子合约，实现去中心化交易路由。","content":"通过代码哈希验证注册子合约，实现去中心化交易路由。 工厂合约模式：通过代码哈希验证注册交易所合约，实现跨代币对的去中心化交易路由。 学习用途 本示例仅用于学习目的。请勿在未经充分审查和测试的情况下用于生产环境。","order":36},{"id":"examples/factory#overview","href":"/docs/examples/factory#overview","title":"工厂模式","group":"示例","description":"通过代码哈希验证注册子合约，实现去中心化交易路由。","content":"概览 工厂模式是 DeFi 中常见的架构：一个工厂合约注册和管理多个子合约（交易所）。本示例包含两个合约： Factory.vy：工厂合约，注册交易所并路由交易 Exchange.vy：交易所合约，管理单个代币对","order":36,"section":"概览"},{"id":"examples/factory#contract-code","href":"/docs/examples/factory#contract-code","title":"工厂模式","group":"示例","description":"通过代码哈希验证注册子合约，实现去中心化交易路由。","content":"Factory 合约代码 #pragma version >0.3.10 from ethereum.ercs import IERC20 interface Exchange: def token() -> IERC20: view def receive(from: address, amt: uint256): nonpayable def transfer(to: address, amt: uint256): nonpayable exchange_codehash: public(bytes32) exchanges: public(HashMap[IERC20, Exchange]) @deploy def init(exchangecodehash: bytes32): self.exchangecodehash = exchange_codehash @external def register(): assert msg.sender.codehash == self.exchange_codehash exchange: Exchange = Exchange(msg.sender) self.exchanges[staticcall exchange.token()] = exchange @external def trade(token1: IERC20, token2: IERC20, _amt: uint256): extcall self.exchanges[token1].receive(msg.sender, amt) extcall self.exchanges[token2].transfer(msg.sender, amt)","order":36,"section":"Factory 合约代码"},{"id":"examples/factory#walkthrough","href":"/docs/examples/factory#walkthrough","title":"工厂模式","group":"示例","description":"通过代码哈希验证注册子合约，实现去中心化交易路由。","content":"代码解析 代码哈希验证 register 通过 msg.sender.codehash 验证调用者的运行时代码哈希与预期匹配。这确保只有部署了正确代码的交易所合约才能注册。 接口定义 使用 Vyper 的 interface 关键字定义 Exchange 接口。staticcall 用于只读调用（token()），extcall 用于状态变更调用（receive、transfer）。 交易路由 trade 函数实现了简单的 1:1 代币兑换——从 token1 的交易所接收代币，从 token2 的交易所发送等量代币。实际应用需要解决价格预言机问题。","order":36,"section":"代码解析"},{"id":"examples/wallet","href":"/docs/examples/wallet","title":"多签钱包","group":"示例","description":"多签钱包合约：多所有者签名验证、ecrecover 和 raw_call 的实际应用。","content":"多签钱包合约：多所有者签名验证、ecrecover 和 raw_call 的实际应用。 多签钱包合约：需要多个所有者的签名才能执行交易，展示 ecrecover 和 raw_call 的实际应用。 学习用途 本示例仅用于学习目的。请勿在未经充分审查和测试的情况下用于生产环境。","order":37},{"id":"examples/wallet#overview","href":"/docs/examples/wallet#overview","title":"多签钱包","group":"示例","description":"多签钱包合约：多所有者签名验证、ecrecover 和 raw_call 的实际应用。","content":"概览 多签钱包是区块链安全的基石。这个合约要求至少 threshold 个所有者签名才能执行交易。核心机制： 最多 5 个所有者 使用 ecrecover 验证链下签名 使用 raw_call 执行任意外部调用 序列号防止签名重放","order":37,"section":"概览"},{"id":"examples/wallet#contract-code","href":"/docs/examples/wallet#contract-code","title":"多签钱包","group":"示例","description":"多签钱包合约：多所有者签名验证、ecrecover 和 raw_call 的实际应用。","content":"完整合约代码 #pragma version >0.3.10 owners: public(address[5]) threshold: int128 seq: public(int128) @deploy def init(owners: address[5], threshold: int128): for i: uint256 in range(5): if _owners[i] != empty(address): self.owners[i] = _owners[i] self.threshold = _threshold @external def testEcrecover(h: bytes32, v: uint8, r: bytes32, s: bytes32) -> address: return ecrecover(h, v, r, s) @external @payable def approve(seq: int128, to: address, value: uint256, data: Bytes[4096], sigdata: uint256[3][5]) -> Bytes[4096]: assert msg.value >= _value approvals: int128 = 0 h: bytes32 = keccak256(concat( convert(_seq, bytes32), convert(to, bytes32), convert(_value, bytes32), data )) h2: bytes32 = keccak256(concat(b\"\\x19Ethereum Signed Message:\\n32\", h)) assert self.seq == _seq for i: uint256 in range(5): if sigdata[i][0] != 0: assert ecrecover(h2, sigdata[i][0], sigdata[i][1], sigdata[i][2]) == self.owners[i] assert ecrecover(h2, convert(sigdata[i][0], uint8), convert(sigdata[i][1], bytes32), convert(sigdata[i][2], bytes32)) == self.owners[i] approvals += 1 assert approvals >= self.threshold self.seq += 1 return rawcall(to, data, maxoutsize=4096, gas=3000000, value=_value) @external @payable def default(): pass","order":37,"section":"完整合约代码"},{"id":"examples/wallet#walkthrough","href":"/docs/examples/wallet#walkthrough","title":"多签钱包","group":"示例","description":"多签钱包合约：多所有者签名验证、ecrecover 和 raw_call 的实际应用。","content":"代码解析 签名验证 approve 将交易参数（序列号、目标地址、金额、数据）进行 keccak256 哈希，然后用以太坊签名消息前缀包装。每个所有者的签名通过 ecrecover 恢复地址并与已知所有者对比。 重放保护 seq 序列号确保每个签名只能使用一次。每次成功执行后 seq 递增。 任意调用 通过 rawcall 实现对任意合约的调用，maxoutsize=4096 和 gas=3000000 提供了足够的灵活性。default 函数标记为 @payable 允许钱包直接接收 ETH。","order":37,"section":"代码解析"}]