ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Solidity 智能合约从入门到实战:基于 learnxinyminutes-docs 的语法全景与以太坊开发指南

Solidity 智能合约从入门到实战:基于 learnxinyminutes-docs 的语法全景与以太坊开发指南 文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载本文以开源仓库 learnxinyminutes-docs 中的 Solidity 语言速成文档 及其 葡萄牙语译本 为骨架系统讲解 Solidity 智能合约的核心语法、数据模型与实战范式并给出可在 Remix IDE 中直接运行的完整合约示例简单银行合约与众筹合约。读完本文你将掌握 Solidity 的变量与数据类型、数据结构、函数与可见性、事件、修改器、继承与外部调用、预言机与状态机设计等完整知识并能够独立在 Remix MetaMask 环境中完成合约的编译、部署与交互测试。一、Solidity 是什么Solidity 是一门面向以太坊Ethereum虚拟机的静态类型合约编程语言用于创建和执行智能合约无需依赖任何中心化或可信任的第三方。它与 JavaScript、C 语言在语法上有相似之处如同面向对象中的类每个合约contract都包含状态变量state variables、函数functions和通用数据类型合约特有的语言特性包括修改器modifier即守卫子句、供外部监听的事件通知event notifiers以及自定义全局变量。以太坊上典型的智能合约应用场景包括众筹crowdfunding、投票voting、去中心化金融decentralized finance和盲拍blind auctions等。必须正视的风险智能合约代码出错的风险与代价都极高因此必须非常谨慎地测试并缓慢发布。需要注意以太坊与 Solidity 迭代极快本文基于仓库内 solidity.md面向 Solidity 0.8.19与 pt-br/solidity.md早期 0.4.x 译本编写文中已尽量标注新旧版本差异但使用前仍应以官方文档为准与普通程序不同合约往往还需要引入**暂停pausing、弃用deprecation、限流throttling**等设计模式来降低风险Solidity 与以太坊仍处于活跃开发阶段实验性/beta 特性通常会被标记且随时可能变更。二、开发环境用 Remix MetaMask 跑通第一个合约构建、部署和测试 Solidity 代码最快捷的路径是使用Remix Web IDE浏览器端 Solidity 集成开发环境MetaMask 钱包浏览器扩展钱包。先安装 MetaMask 浏览器扩展然后在 Remix 中按以下步骤操作英文主文档 solidity.md 提供了完整工作流选择 Solidity 编译器在 Remix 左侧插件栏启用 Solidity 编译器Solidity Compiler打开示例文件通过文档提供的预加载链接打开SimpleBank.sol源码文件文件扩展名为.sol编译合约在编译器面板确认编译器版本与目标合约点击编译Compile按钮下方截图即编译面板可看到编译器版本选择、EVM 版本设置及编译配置项部署合约切换到部署与运行交易Deploy Run Transactions面板环境默认选择Remix VMJavaScript 虚拟机测试链点击 Deploy 按钮完成部署与合约交互部署成功后展开左侧已部署合约实例即可调用deposit()、withdraw()、balance()等函数观察交易记录与事件日志。在未特别说明的情况下请始终使用Remix VM作为运行环境避免在真实测试网或主网上误操作。三、示例一SimpleBank 简单银行合约仓库文档以一段完整的银行合约开场见 solidity.md它允许用户存款、取款和查询余额是理解合约结构的绝佳入口。以下是基于 Solidity 0.8.19 的版本// simple_bank.sol注意 .sol 扩展名 // SPDX-License-Identifier: MIT // 声明源文件的编译器版本 pragma solidity ^0.8.19; // 以 Natspec 注释三条斜杠开头 // 用于文档说明也可作为 UI 元素/动作的描述性数据 /// title SimpleBank /// author nemild /* contract 与其他语言中的 class 相似类变量、继承等 */ contract SimpleBank { // 大驼峰命名 CapWords // 在函数外部声明状态变量以便在合约整个生命周期内持久化 // 将地址映射到余额的字典 mapping (address uint) private balances; // private 意味着其他合约不能直接查询 balances // 但链上其他各方仍然可以看到这些数据 address public owner; // public 使得用户或合约可以外部读取但不可写 // 事件 - 向外部监听者公开动作 event LogDepositMade(address accountAddress, uint amount); // 构造函数可以接收一个或多个变量每个合约只允许一个 constructor() { // msg 提供发送给合约的消息细节 // msg.sender 是合约调用者即合约创建者的地址 owner msg.sender; } /// notice 向银行存入 ether /// return 存款后用户的余额 function deposit() public payable returns (uint) { // 用 require 校验用户输入用 assert 校验内部不变量 // 这里确保不存在溢出问题 // 在现代 Solidity 版本中整数运算的溢出会被自动检查 require((balances[msg.sender] msg.value) balances[msg.sender]); balances[msg.sender] msg.value; // 访问状态变量无需 this. 或 self. // 所有值默认初始化为其数据类型的初始值 emit LogDepositMade(msg.sender, msg.value); // 触发事件 return balances[msg.sender]; } /// notice 从银行取款 /// dev 不会退还多余发送给它的 ether /// param withdrawAmount 你想取出的金额 /// return remainingBal 剩余余额 function withdraw(uint withdrawAmount) public returns (uint remainingBal) { require(withdrawAmount balances[msg.sender]); // 注意我们在发送之前先扣减余额的方式 // 本合约的每次 .transfer/.send 都可能调用外部函数 // 这可能允许调用者通过递归调用请求超过其余额的金额 // 目标在调用外部函数包括 .transfer/.send之前先提交状态 balances[msg.sender] - withdrawAmount; // 失败时自动抛出异常意味着更新后的余额会被回滚 payable(msg.sender).transfer(withdrawAmount); return balances[msg.sender]; } /// notice 获取余额 /// return 用户余额 // view即过去的 constant防止函数修改状态变量 // 允许函数在本地/链下执行 function balance() view public returns (uint) { return balances[msg.sender]; } } // ** 示例结束 **这段代码浓缩了后续所有语法章节的核心元素mapping状态变量、address与uint类型、event声明与emit触发、constructor构造器、payable收款函数、view只读函数以及先扣减余额、再发送资金的防重入模式——这是 Solidity 最重要的安全实践之一。四、数据类型与关联方法1. 整数uint 与 intuint用于表示货币数量Solidity 没有 double/float和日期Unix 时间戳int constant a 8;表示一个 256 位的、实例化后不可修改的常量int256 constant a 8;效果相同只是显式写出了 256 位十六进制常量如uint constant VERSION_ID 0x123A1;同样合法使用constant时编译器会把每一处出现替换为实际值int/uint的位宽可以按 8 的步长显式指定范围 8256例如uint8 b;、int64 c;、uint248 e;溢出风险旧版 Solidity 中加法可能溢出需手动断言如assert(c a);现代版本0.8.x 起会自动检查整数运算的上下溢因此在 0.8.19 中这一手动防护不再必要没有内置随机数函数可以通过哈希当前区块哈希获取伪随机数或借助 Chainlink VRF 获取真正的随机数。2. 类型转换int x int(b); // 显式类型转换 bool b true; // 布尔类型3. 地址类型 addressaddress保存 20 字节 / 160 位的以太坊地址不允许进行算术运算。账户分为两类合约账户Contract account创建时确定地址由创建者地址与交易序号派生外部账户External account由公钥派生的地址个人/外部实体。添加public字段可表示公开/外部可访问编译器会自动生成 getter但不会生成 setter。所有地址都可以接收 etherowner.transfer(SOME_BALANCE); // 失败时回滚并抛异常 // 也可以使用更低层的 .send失败时返回 false if (owner.send(amount)) {} // 记住要把 send 包在 if 中 // 因为合约地址在发送时会执行函数这些函数可能失败 // 另外务必在尝试发送之前扣减余额 // 因为存在可抽干合约的递归调用风险 owner.balance; // 查询 owner用户或合约的余额4. 字节类型 bytes定长字节bytes1到bytes32如bytes1 a;、bytes2 b;、bytes32 c;动态字节bytes m;是一个特殊数组等价于紧凑打包的byte[]但比bytes1~bytes32更昂贵能用定长就尽量用定长字符串string n hello;以 UTF-8 存储注意双引号而非单引号string 工具函数尚不完善由于 UTF-8 占用更多存储优先使用bytes32/bytes。5. 默认值、delete 与元组默认情况下所有值在实例化时都初始化为 0delete x;可作用于大多数类型——它不会销毁值而是把值重置为 0初始值元组解构/交换(x, y) (2, 7);可一次赋值或交换多个值。6. 类型推断 var旧版本特性旧版文档pt-br 译本还介绍了var类型推断例如var a true;或把函数赋给变量var f a;。需要提醒的是推断可能产生错误类型如本需要int16的计数器被推断为int8且var不能用于函数参数在 0.8.x 中var已不再支持应显式声明类型。五、数据结构1. 数组 Arraysbytes32[5] nicknames; // 静态数组 bytes32[] names; // 动态数组 names.push(John); // 添加元素新版 push 不再返回长度 names.length; // 获取长度新版中已移除直接修改 length 的能力多维数组的声明顺序与多数语言相反uint[][5] x;表示一个包含 5 个动态数组元素的数组。2. 映射 mappingmapping (string uint) public balances; balances[charles] 1; // balances[ada] 的结果是 0所有未赋值的键都返回零值 // 另一个合约可通过 contractName.balances(charles) 查询到 1 // public 会生成 getter但没有 setter等价于 function balances(string memory _account) public view returns (uint balance) { return balances[_account]; } // 嵌套映射 mapping (address mapping (address uint)) public custodians; // 删除 delete balances[John]; delete balances; // 将所有元素置 0关键限制与其他语言不同mapping 无法在不知道源键的情况下遍历所有元素——如需遍历需要在 mapping 之上自行构建数据结构如数组索引。3. 结构体 struct 与枚举 enumstruct Bank { address owner; uint balance; } Bank b Bank({ owner: msg.sender, balance: 5 }); // 或 Bank c Bank(msg.sender, 5); c.balance 5; // 设置为新值 delete b; // 将结构体中所有变量置 0mapping 除外 enum State { Created, Locked, Inactive }; // 常用于状态机 State public state; state State.Created; uint createdState uint(State.Created); // 0枚举可显式转换为 int4. 数据位置memory / storage / calldata所有复杂类型数组、结构体都有数据位置data locationmemory不持久化仅存在于函数执行期间storage持久化写入链上calldata同样不持久化且只读不能被修改默认规则局部变量和状态变量为storage函数参数为memory栈stack保存小的局部变量大多数类型可以显式指定数据位置例如string memory _account。六、简单运算符Solidity 提供比较、位运算与算术运算符其中值得注意的包括幂运算**异或^按位取反~七、重要的全局变量thisthis; // 合约自身地址常在合约生命周期末尾用于把剩余余额转给相关方 this.balance; // 合约自身余额 this.someFunction(); // 通过外部调用call而非内部跳转调用函数msg —— 合约收到的当前消息msg.sender; // 发送者地址 msg.value; // 提供给本合约的 ether 数量单位 wei函数应标记为 payable msg.data; // bytes完整的调用数据旧版还有msg.gas剩余 gas在 0.8.x 中已移除。tx —— 当前交易tx.origin; // 交易的发送者地址 tx.gasprice; // 交易的 gas 价格block —— 当前区块信息block.timestamp; // 当前时间近似Unix 时间戳 // 注意矿工可以操纵该值请谨慎使用 block.number; // 当前区块号 block.difficulty; // 当前区块难度 block.blockhash(1); // 返回 bytes32仅对最近 256 个区块有效storage —— 持久化存储哈希storage[abc] def; // 将 256 位字映射到 256 位字八、函数与更多特性1. 函数基础与多返回值// 简单函数 function increment(uint x) returns (uint) { x 1; return x; } // 函数可以返回多个参数指定返回参数名后无需显式 return function increment(uint x, uint y) returns (uint x, uint y) { x 1; y 1; } // 调用 (uint a, uint b) increment(1,1);2. view 与 pureview旧关键字constant的别名声明函数不会/不能修改持久化变量因此在本地/链下执行而不上链constant关键字即将弃用在view函数中修改状态变量会编译失败pure比view更严格连读取状态变量都不允许。uint y 1; function increment(uint x) view returns (uint x) { x 1; y 1; // 此行会失败y 是状态变量不能在 view 函数中被修改 }3. 函数可见性说明符view可以替换为以下可见性说明符public外部与内部均可见函数的默认行为external仅外部可见包括通过this.的调用private仅当前合约可见internal仅当前合约及其派生合约可见。规范建议为每个函数显式标注可见性。4. 函数赋值与 payable函数会被提升hoisted且可以赋给函数类型变量function() internal z b;所有接收 ether 的函数必须标记为payablefunction depositEther() public payable { balances[msg.sender] msg.value; }5. 循环优于递归优先使用循环而非递归——最大调用栈深度为 1024。同时不要编写未限定边界的循环否则可能触及 gas 上限导致交易失败。九、事件 Events事件用于通知外部各方可通过轻客户端在链外轻松搜索和访问通常在合约参数之后声明// 声明通常大写开头并加上 Log 前缀以示明确、避免与函数调用混淆 event LogSent(address indexed from, address indexed to, uint amount); // 触发新版必须用 emit 关键字 emit LogSent(from, to, amount);外部方合约或外部实体可用 Web3 JavaScript 库监听// 以下为 JavaScript 代码非 Solidity Coin.LogSent().watch({}, , function(error, result) { if (!error) { console.log(Coin transfer: result.args.amount coins were sent from result.args.from to result.args.to .); console.log(Balances now:\n Sender: Coin.balances.call(result.args.from) Receiver: Coin.balances.call(result.args.to)); } });事件也是一个合约依赖另一个合约的常见范式例如某个合约依赖另一合约提供的实时汇率。十、修改器 Modifiers修改器用于校验函数输入如最小余额、用户授权类似其他语言中的守卫子句guard clause// _下划线常作为函数体最后一行 // 表示被调用的函数体应被放置在该位置 modifier onlyAfter(uint _time) { require (block.timestamp _time); _; } modifier onlyOwner { require(msg.sender owner); _; } // 常与状态机配合 modifier onlyIfStateA (State currState) { require(currState State.A); _; } // 紧跟在函数声明之后串联使用 function changeOwner(newOwner) onlyAfter(someTime) onlyOwner() onlyIfState(State.A) { owner newOwner; }下划线也可以放在函数体之前但此时显式 return 会跳过其后的代码需要小心使用modifier checkValue(uint amount) { _; if (msg.value amount) { uint amountToRefund amount - msg.value; msg.sender.transfer(amountToRefund); } }十一、分支与循环所有基本逻辑块都可用——if/else、for、while、break、continue、return——但没有switch。语法与 JavaScript 相同但没有从非布尔到布尔的类型转换必须用比较运算符得到布尔值。由用户行为决定的循环要格外小心合约代码块有最大 gas 上限超限会导致失败。例如for(uint x 0; x refundAddressList.length; x) { refundAddressList[x].transfer(SOME_AMOUNT); }这段代码存在两个错误某次transfer失败会中断整个循环导致资金被卡住该循环可能任意长取决于需要退款的人数可能总是因超出区块最大 gas 而失败。正确做法是让用户各自从自己的子账户中单独取款并标记已取——即优先拉取支付pull payments而非推送支付push payments。十二、对象与合约1. 调用外部合约contract InfoFeed { function info() payable returns (uint ret) { return 42; } } contract Consumer { InfoFeed feed; // 指向链上的某个合约 // 把 feed 指向已存在的合约实例 function setFeed(address addr) { // 自动类型转换小心构造器不会被调用 feed InfoFeed(addr); } // 把 feed 指向新创建的合约实例 function createNewFeed() { feed new InfoFeed(); // 创建新实例构造器会被调用 } function callFeed() { // 最后一对括号调用合约可附加自定义 ether 值或 gas feed.info{value: 10, gas: 800}(); } }2. 继承// 顺序很重要最后被继承的合约如 def可以覆盖先前继承合约的部分 contract MyContract is abc, def(a custom argument to def) { // 覆盖函数 function z() { if (msg.sender owner) { def.z(); // 调用 def 中被覆盖的函数 super.z(); // 调用直接父合约中被覆盖的函数 } } } // 抽象函数 function someAbstractFunction(uint x); // 不能被编译因此用于基类/抽象合约中再由子合约实现3. 导入 Importimport filename; import github.com/ethereum/dapp-bin/library/iterable_mapping.sol;旧版文档提示import当时仍在开发中、无法在命令行使用现代 Solidity 中导入已完全支持可导入本地文件、npm 包或通过标准 JSON 接口的 URL。十三、其他关键字throw旧版与 require现代旧版 Solidity如 pt-br 译本的 0.4.2使用throw;回滚状态未使用的 gas 返还用户且不可捕获。现代版本0.8.x中throw已废弃改为require/revert/assert// 旧版常见设计模式 if (!address.send(123)) { throw; } // 现代等价写法 require(address.send(123));selfdestruct已弃用谨慎使用// 销毁当前合约并把资金发送到指定地址通常是创建者 selfdestruct(SOME_ADDRESS);它会从当前及未来区块中移除存储/代码有助于轻客户端但历史数据仍留在链上。常见模式是允许所有者终止合约并取回剩余资金function remove() { if(msg.sender creator) { // 只允许合约创建者 selfdestruct(creator); // 使合约失效并返还资金 } }注意自 Cancun 硬分叉起见 EIP-6780selfdestruct已弃用——底层操作码不再删除账户关联的代码和数据仅向其受益人转移 Ether除非与合约创建发生在同一笔交易中。新部署的合约应强烈避免使用它。此外向已 selfdestruct 的合约发送 ether 会丢失必要时可考虑手动停用合约而非销毁。十四、合约设计笔记1. 混淆 Obfuscation区块链上所有变量都是公开可见的任何私有内容都需要混淆例如与秘密一起哈希// 步骤1. 先承诺2. 再揭示承诺 keccak256(some_bid_amount, some secret); // 承诺 // 未来调用合约的 reveal 函数展示出投标金额与秘密 reveal(100, mySecret);2. 存储优化写入区块链成本高昂因为数据将被永久保存应精心规划数据结构把最少的数据存上链。多维数组等结构的存储成本可能很高成本针对存储数据本身而不是声明未填充的变量。3. 链上数据访问无法阻止人类或计算机读取交易内容及其状态。private只是阻止其他合约直接读取数据任何一方仍可读取链上数据自创世以来的所有数据都保存在链上任何人都能观察全部历史数据与变更。4. 预言机与外部数据 Oracles预言机Oracle是与链外世界交互的桥梁用于获取现实世界数据、向现实世界发送 POST 请求或反向操作。基于时间的合约实现也依赖预言机——因为合约只能被直接调用无法订阅时间。由于智能合约是去中心化的获取数据也应采用去中心化方式否则就引入了中心化风险。最简单的做法是使用现成的去中心化数据源如 Chainlink Data Feeds引用链上已被多方聚合好的参考点作为数据银行也可以自行搭建预言机网络但务必清楚自己的应用在中心化与去中心化之间的定位。5. 定时任务 Cron Job合约必须被手动调用才能处理基于时间的调度可以编写外部代码定期 ping或提供激励ether让他人代为触发。6. 观察者模式 Observer Pattern观察者模式允许注册为订阅者并注册一个由预言机调用的回调函数注意预言机为执行该动作付费与 Pub/Sub 订阅有相似之处// 抽象合约客户端和服务端类都导入它客户端需实现该函数 contract SomeOracleCallback { function oracleCallback(int _value, uint _time, bytes32 info) external; } contract SomeOracle { SomeOracleCallback[] callbacks; // 所有订阅者数组 // 注册订阅者 function addSubscriber(SomeOracleCallback a) { callbacks.push(a); } function notify(value, time, info) private { for(uint i 0;i callbacks.length; i) { // 所有被调用的订阅者都必须实现 oracleCallback callbacks[i].oracleCallback(value, time, info); } } function doSomething() public { // 执行某些操作的代码 // 通知所有订阅者 notify(_value, _time, _info); } }之后你的客户端合约只需导入SomeOracleCallback并注册到某个 Oracle 实例即可。7. 状态机 State Machines见下文众筹示例中的State枚举与inState修改器。十五、示例二CrowdFunder 众筹合约仓库文档提供了第二个完整示例——一个类似 Kickstarter 的众筹合约见 solidity.md它综合运用了状态机、事件、修改器、结构体与取款模式// CrowdFunder.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; /// title CrowdFunder /// author nemild contract CrowdFunder { // 创建时由创建者设置的变量 address public creator; address payable public fundRecipient; // 创建者可以与收款人不同且必须 payable uint public minimumToRaise; // 达标才支付否则所有人退款 string campaignUrl; uint256 version 1; // 数据结构 enum State { Fundraising, // 募资中 ExpiredRefund, // 到期退款 Successful // 成功 } struct Contribution { uint amount; address payable contributor; } // 状态变量 State public state State.Fundraising; // 创建时初始化 uint public totalRaised; uint public raiseBy; uint public completeAt; Contribution[] contributions; event LogFundingReceived(address addr, uint amount, uint currentTotal); event LogWinnerPaid(address winnerAddress); modifier inState(State _state) { require(state _state); _; } modifier isCreator() { require(msg.sender creator); _; } // 在最终状态后等待 24 周才允许销毁合约 modifier atEndOfLifecycle() { require(((state State.ExpiredRefund || state State.Successful) completeAt 24 weeks block.timestamp)); _; } function crowdFund( uint timeInHoursForFundraising, string memory _campaignUrl, address payable _fundRecipient, uint _minimumToRaise) public { creator msg.sender; fundRecipient _fundRecipient; campaignUrl _campaignUrl; minimumToRaise _minimumToRaise; raiseBy block.timestamp (timeInHoursForFundraising * 1 hours); } function contribute() public payable inState(State.Fundraising) returns(uint256 id) { contributions.push( Contribution({ amount: msg.value, contributor: payable(msg.sender) }) // 使用数组以便可以遍历 ); totalRaised msg.value; emit LogFundingReceived(msg.sender, msg.value, totalRaised); checkIfFundingCompleteOrExpired(); return contributions.length - 1; // 返回 id } function checkIfFundingCompleteOrExpired() public { if (totalRaised minimumToRaise) { state State.Successful; payOut(); // 这里可以激励发起状态变更的发送者 } else if ( block.timestamp raiseBy ) { state State.ExpiredRefund; // 支持者现在可调用 getRefund(id) 收款 } completeAt block.timestamp; } function payOut() public inState(State.Successful) { fundRecipient.transfer(address(this).balance); emit LogWinnerPaid(fundRecipient); } function getRefund(uint256 id) inState(State.ExpiredRefund) public returns(bool) { require(contributions.length id id 0 contributions[id].amount ! 0 ); uint256 amountToRefund contributions[id].amount; contributions[id].amount 0; // 先置零防止重复领取 contributions[id].contributor.transfer(amountToRefund); return true; } // 警告selfdestruct 已弃用见前文说明 // 因此 removeContract() 已被注释掉 } // ** 示例结束 **该示例展示了几个关键设计状态机驱动State枚举 inState修改器把函数调用限定在合法状态下用数组而非 mapping 记录贡献contributions数组可遍历支持按 id 退款退款时先置零再转账contributions[id].amount 0防止重入重复领取区分收款人fundRecipient与创建者creator并使用payable地址类型完整的合约销毁逻辑因selfdestruct弃用而被注释呼应前文的安全演进。十六、其他原生函数货币单位uint minAmount 1 wei; // wei 是 Ether 的最小单位 uint a 1 finney; // 1 ether 1000 finney时间单位1 1 second 1 minutes 60 seconds // 变量可以乘以时间单位单位不存储在变量中 uint x 5; (x * 1 days); // 5 天注意对时间使用相等性判断时要小心闰秒/闰年优先使用大于/小于比较。加密函数// 所有传入的字符串在哈希前会被拼接 keccak256(ab, cd); ripemd160(abc); sha256(def);旧版文档中的sha3在现代 Solidity 中已更名为keccak256。十七、安全智能合约中的 bug 可能是灾难性的——即使是 Solidity 中流行的模式也可能被发现是反模式。核心安全要点回顾先扣减余额再发送资金防止重入攻击用require校验输入用assert校验内部不变量0.8.x 起整数溢出自动检查避免无界循环触碰 gas 上限优先拉取支付而非推送支付链上一切数据公开可见隐私内容必须混淆tx.origin与block.timestamp可被操纵谨慎使用不使用已弃用的throw、msg.gas、block.blockhash用blockhash与selfdestruct。十八、底层函数// call - 底层调用不常用不提供类型安全 (bool success, bytes memory data) someContractAddress.call( abi.encodeWithSignature(function_name(string,string), arg1, arg2) ); // delegatecall - 目标地址的代码在调用合约的 *上下文* 中执行 // 提供库功能 (bool success, bytes memory data) someContractAddress.delegatecall( abi.encodeWithSignature(function_name()) );旧版文档中的callcode在现代 Solidity 中已被delegatecall取代。十九、风格笔记Solidity 风格指南仓库英文主文档 solidity.md 摘要基于 Python 的 PEP8缩进使用 4 个空格合约声明及其他顶层声明之间空两行括号内避免多余空格单行语句if、for 等可省略花括号else应单独占一行。二十、Natspec 注释Natspec 用于文档、注释和外部 UI// 合约级 Natspec —— 始终放在合约定义上方 /// title 合约标题 /// author 作者名 // 函数级 Natspec /// notice 函数功能的说明执行函数时展示 /// dev 面向开发者的函数文档 // 参数与返回值 Natspec /// param someParam 该参数作用的说明 /// return 返回值的说明二十一、资源与工具链仓库文档末尾solidity.md整理了完整的进阶资源汇总如下文档与学习Solidity 官方文档、Cyfrin UpdraftSolidity 开发课程、Chainlink 新手教程、智能合约最佳实践合集开发框架Foundry、Hardhat、Brownie重要库OpenZeppelin众筹、SafeMath 等常见合约模式、Chainlink与外部数据交互示例合约Dapp Bin、DeFi 示例、Solidity Baby Step Contracts、ConsenSys Contracts、安全漏洞最小化代码库Cyfrin sc-exploits-minimized安全资源智能合约安全课程、安全报告数据库、Thinking About Smart Contract Security 等经典文章编辑器Remix、Emacs Solidity Mode、Vim Solidity、Ultisnips 代码片段。仓库中的对应文档 solidity.md 与 pt-br/solidity.md 本身就是可运行的以代码形式书写的文档适合作为语法速查与复习材料学习完整流程时可结合 README.md 了解仓库整体的Learn X in Y minutes速成教程风格。把本文的两个示例合约依次在 Remix 中编译、部署、交互再对照官方文档逐项验证即可完成从语法到实战的闭环。赞分享文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载上一篇k3d 配置文件完全指南用 YAML 声明式创建 K3s 集群v1alpha5 详解下一篇AirDropPlus3分钟实现Windows与iPhone无线传输的革命性方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进