分类 技术前沿 下的文章 - 六币之门
首页
视频教程
网站导航
活动日历
关于我们
用户投稿
推荐
新闻动态
搜 索
1
融资周报 | 公开融资事件11起;加密技术公司Toposware完成500万美元融资,Polygon联创参投
107 阅读
2
六币日报 | 九只比特币ETF在6天内积累了9.5万枚BTC;贝莱德决定停止推出XRP现货ETF计划
73 阅读
3
六币日报 | 美国SEC再次推迟对灰度以太坊期货ETF做出决定;Do Kwon已出黑山监狱等待引渡
68 阅读
4
融资周报 | 公开融资事件27起;L1区块链Monad Labs完成2.25亿美元融资,Paradigm领投
68 阅读
5
【ETH钱包开发06】查询某个地址的交易记录
43 阅读
新闻动态
每日快报
一周精选
融资情况
项目投研
自治组织
数字藏品
去中心化应用
去中心化游戏
去中心化社交
去中心化金融
区块链交易所
科普知识
小白入门
用户手册
开发文档
行业报告
技术前沿
登录
搜 索
标签搜索
新闻
日报
元歌Eden
累计撰写
1,087
篇文章
累计收到
0
条评论
首页
栏目
新闻动态
每日快报
一周精选
融资情况
项目投研
自治组织
数字藏品
去中心化应用
去中心化游戏
去中心化社交
去中心化金融
区块链交易所
科普知识
小白入门
用户手册
开发文档
行业报告
技术前沿
页面
视频教程
网站导航
活动日历
关于我们
用户投稿
推荐
新闻动态
用户登录
登录
找到
109
篇与
技术前沿
相关的结果
2023-03-24
Solidity 简易教程
Solidity是以太坊的主要编程语言,它是一种静态类型的 JavaScript-esque 语言,是面向合约的、为实现智能合约而创建的高级编程语言,设计的目的是能在以太坊虚拟机(EVM)上运行。本文基于CryptoZombies,教程地址为:cryptozombies.io/zh/合约Solidity 的代码都包裹在合约里面. 一份合约就是以太应币应用的基本模块, 所有的变量和函数都属于一份合约, 它是你所有应用的起点.一份名为 HelloWorld 的空合约如下:contract HelloWorld { } 复制代码hello world首先看一个简单的智能合约。pragma solidity ^0.4.0; contract SimpleStorage { uint storedData; // 声明一个类型为 uint (256位无符号整数)的状态变量,叫做 storedData function set(uint x) public { storedData = x; // 状态变量可以直接访问,不需要使用 this. 或者 self. 这样的前缀 } function get() public view returns (uint) { return storedData; } } 复制代码所有的 Solidity 源码都必须冠以 "version pragma" — 标明 Solidity 编译器的版本. 以避免将来新的编译器可能破坏你的代码。例如: pragma solidity ^0.4.0; (当前 Solidity 的最新版本是 0.4.0).关键字 pragma 的含义是,一般来说,pragmas(编译指令)是告知编译器如何处理源代码的指令的(例如, pragma once )。Solidity中合约的含义就是一组代码(它的 函数 )和数据(它的 状态 ),它们位于以太坊区块链的一个特定地址上。该合约能完成的事情并不多:它能允许任何人在合约中存储一个单独的数字,并且这个数字可以被世界上任何人访问,且没有可行的办法阻止你发布这个数字。当然,任何人都可以再次调用 set ,传入不同的值,覆盖你的数字,但是这个数字仍会被存储在区块链的历史记录中。Solidity 语句以分号(;)结尾状态变量状态变量是被永久地保存在合约中。也就是说它们被写入以太币区块链中,想象成写入一个数据库。contract HelloWorld { // 这个无符号整数将会永久的被保存在区块链中 uint myUnsignedInteger = 100; } 复制代码在上面的例子中,定义 myUnsignedInteger 为 uint 类型,并赋值100。uint 无符号数据类型, 指其值不能是负数,对于有符号的整数存在名为 int 的数据类型。Solidity中, uint 实际上是 uint256代名词, 一个256位的无符号整数。程序有时需要对不同类型的数据进行操作,因为 Solidity 是静态类型语言,对不同类型的数据进行运算会抛出异常,比如:uint8 a = 5; uint b = 6; // 将会抛出错误,因为 a * b 返回 uint, 而不是 uint8: uint8 c = a * b; 复制代码a * b 返回类型是 uint, 但是当我们尝试用 uint8 类型接收时, 就会造成潜在的错误。这时,就需要显式的进行数据类型转换:// 我们需要将 b 转换为 uint8: uint8 c = a * uint8(b); 复制代码把它的数据类型转换为 uint8, 就可以了,编译器也不会出错。Solidity 支持多种数据类型,比如:string(字符串):字符串用于保存任意长度的 UTF-8 编码数据 fixedArray(静态数组):固定长度的数组 dynamicArray(动态数组):长度不固定,可以动态添加元素的数组 enum(枚举) mapping 等 数学运算在 Solidity 中,数学运算很直观明了,与其它程序设计语言相同:加法: x + y 减法: x - y, 乘法: x * y 除法: x / y 取模 / 求余: x % y (例如, 13 % 5 余 3, 因为13除以5,余3) 乘方: x ** y 结构体Solidity 提供了 结构体,用来表示更复杂的数据类型。struct Person { uint age; string name; } 复制代码结构体允许你生成一个更复杂的数据类型,它有多个属性。创建结构体方式为:// 创建一个新的Person: Person satoshi = Person(172, "Satoshi"); 复制代码数组Solidity 提供两种类型的数组:静态数组和动态数组。// 固定长度为2的静态数组: uint[2] fixedArray; // 固定长度为5的string类型的静态数组: string[5] stringArray; // 动态数组,长度不固定,可以动态添加元素: uint[] dynamicArray; 复制代码使用 push 函数向数组中添加值:fixedArray.push[123] fixedArray.push[234] // fixedArray 值为 [123, 234] 复制代码array.push() 在数组的 尾部 加入新元素 ,所以元素在数组中的顺序就是添加的顺序 array.push() 会返回数组的长度。Solidity 数组支持多种类型,比如结构体:struct Person { uint age; string name; } Person[] people; // dynamic Array, we can keep adding to it 复制代码结构体类型的数组添加值的方式为:people.push(Person(16, "Vitalik")); // 也可以使用下面的方式,推荐使用上述一行简洁的方式 Person satoshi = Person(172, "Satoshi"); people.push(satoshi); 复制代码公共数组也可以使用public定义公共数组,Solidity 会自动创建getter方法。语法如下:struct Person { uint age; string name; } Person[] public people; // dynamic Array, we can keep adding to it 复制代码公共数组支持其它的合约读取数据(但不能写入数据),所以这在合约中是一个有用的保存公共数据的模式。(有点像全局变量,所有合约共享同一个“内存空间“,厉害了!)函数Solidity 中,函数定义如下:function eatHamburgers(string _name, uint _amount) { } 复制代码Solidity 习惯上函数里的变量都是以(_)开头 (但不是硬性规定) 以区别全局变量。这是一个名为 eatHamburgers 的函数,它接受两个参数:一个 string类型的 和 一个 uint类型的。现在函数内部还是空的。函数调用如下:eatHamburgers("vitalik", 100); 复制代码私有/公共函数Solidity 函数分为私有函数和共有函数。Solidity 定义的函数的属性默认为公共。 这就意味着任何一方 (或其它合约) 都可以调用你合约里的函数。显然,不是什么时候都需要这样,而且这样的合约易于受到攻击。所以将自己的函数定义为私有是一个好的编程习惯,只有当你需要外部世界调用它时才将它设置为公共。可以把所有的函数都显式的声明 public和private来规避这个问题。定义私有函数比较简单,只需要在函数参数后添加 private关键字即可。示例如下:uint[] numbers; function _addToArray(uint _number) private { numbers.push(_number); } 复制代码这意味着只有我们合约中的其它函数才能够调用这个函数,给 numbers数组添加新成员。和函数的参数类似,私有函数的名字用(_)起始。注意:在智能合约中你所用的一切都是公开可见的,即便是局部变量和被标记成 private 的状态变量也是如此。返回值和其它语言一样,Solidity 函数也有返回值,示例如下:string greeting = "What's up dog"; function sayHello() public returns (string) { return greeting; } 复制代码返回值使用 returns关键字标注。(已经是非常奇怪的写法了。。)修饰符viewconstant 是 view 的别名string greeting = "What's up dog"; function sayHello() public returns (string) { return greeting; } 复制代码像 sayHello 函数这种实际上没有改变合约中数据内容的情况,可以把函数定义为view,这意味着此函数只读不修改数据。可以使用以下声明方式:function sayHello() public view returns (string) 复制代码可以将函数声明为 view 类型,这种情况下要保证不修改状态。下面的语句被认为是修改状态:修改状态变量。 产生事件。 创建其它合约。 使用 selfdestruct。 通过调用发送以太币。 调用任何没有标记为 view 或者 pure 的函数。 使用低级调用。 使用包含特定操作码的内联汇编。 purepure 比 view 更轻量,使用这个修饰符修饰的函数甚至都不会读取合约中的数据,例如:function _multiply(uint a, uint b) private pure returns (uint) 复制代码这个函数没有读取应用里的状态,它的返回值只和它输入的参数相关。Solidity 编辑器会给出提示,提醒你使用 pure/view修饰符。函数可以声明为 pure ,在这种情况下,承诺不读取或修改状态。除了上面解释的状态修改语句列表之外,以下被认为是从状态中读取:读取状态变量。 访问 this.balance 或者 <address>.balance。 访问 block,tx, msg 中任意成员 (除 msg.sig 和 msg.data 之外)。 调用任何未标记为 pure 的函数。 使用包含某些操作码的内联汇编。 payablepayable 关键字用来说明,这个函数可以接受以太币,如果没有这个关键字,函数会自动拒绝所有发送给它的以太币。事件事件 是合约和区块链通讯的一种机制。你的前端应用“监听”某些事件,并做出反应。例如:// 这里建立事件 event IntegersAdded(uint x, uint y, uint result); function add(uint _x, uint _y) public { uint result = _x + _y; //触发事件,通知app IntegersAdded(_x, _y, result); return result; } 复制代码用户界面(当然也包括服务器应用程序)可以监听区块链上正在发送的事件,而不会花费太多成本。一旦它被发出,监听该事件的listener都将收到通知。而所有的事件都包含了 from , to 和 amount 三个参数,可方便追踪事务。 为了监听这个事件,你可以使用如下代码(javascript 实现):var abi = /* abi 由编译器产生 */; var ClientReceipt = web3.eth.contract(abi); var clientReceipt = ClientReceipt.at("0x1234...ab67" /* 地址 */); var event = clientReceipt.IntegersAdded(); // 监视变化 event.watch(function(error, result){ // 结果包括对 `Deposit` 的调用参数在内的各种信息。 if (!error) console.log(result); }); // 或者通过回调立即开始观察 var event = clientReceipt.IntegersAdded(function(error, result) { if (!error) console.log(result); }); 复制代码代码示例下面是一个完整的代码示例:pragma solidity ^0.4.19; contract ZombieFactory { // 建立事件 event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; // 定义状态变量 uint dnaModulus = 10 ** dnaDigits; struct Zombie { // 定义结构体 string name; uint dna; } Zombie[] public zombies; // 定义动态数组 // 创建私有函数,私有函数命名使用 _ 前缀 function _createZombie(string _name, uint _dna) private { // 函数参数命名 使用 _ 作为前缀 // arrays.push() 将元素加入到数组尾部,并且返回数组的长度 uint id = zombies.push(Zombie(_name, _dna)) - 1; // 触发事件 NewZombie(id, _name, _dna); } // view 为函数修饰符,表示此函数不需要更新或创建状态变量 // pure 表示函数不需要使用状态变量 function _generateRandomDna(string _str) private view returns (uint) { // 使用 keccak256 创建一个伪随机数 uint rand = uint(keccak256(_str)); return rand % dnaModulus; } function createRandomZombie(string _name) public { uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); } } 复制代码Ethereum 内部有一个散列函数keccak256,它用了SHA3版本。一个散列函数基本上就是把一个字符串转换为一个256位的16进制数字。 在智能合约中使用随机数很难保证节点不作弊, 这是因为智能合约中的随机数一般要依赖计算节点的本地时间得到, 而本地时间是可以被恶意节点伪造的,因此这种方法并不安全。 通行的做法是采用 链外off-chain 的第三方服务,比如 Oraclize 来获取随机数)。参考链接Solidity 文档: https://solidity-cn.readthedocs.io/zh/develop/index.html cryptozombie-lessons: https://cryptozombies.io/zh/ 最后,感谢女朋友支持和包容,比❤️也可以在公号输入以下关键字获取历史文章:公号&小程序 | 设计模式 | 并发&协程
2023年03月24日
3 阅读
0 评论
0 点赞
2023-03-24
[译] EthList:众包维护的以太坊阅读清单
原文地址:EthList: The Crowdsourced Ethereum Reading List 原文作者:Scanate 译文出自:掘金翻译计划 — 区块链分舵 本文永久链接:github.com/xitu/blockc… 译者:StellaBauhinia 校对者:davelet 发现了一个很棒的资源吗?提交一个 Pull 请求就可以加入进来了!贡献者:Phil Kurtland (Scanate), Robert Hackett (Fortune Magazine), Jorge Izquierdo and Luis Cuende (Aragon), Jake Brukhman (CoinFund), Maksim Balashevich (Santiment), Michal Brazewicz (Status), Nate Rush, Krishna Aradhi, Justin Poirier, Griff Green (Giveth.io), Eeks, Anonymous在推特上发布这个清单吧!简介 入门 生态 思想碎片 交易 编程 法务 简介以太坊的简单解释区块链视频演示 —— 区块链技术的视效演示 精简区块链命令行学习实操 虚拟货币演示 —— 数字货币技术的视效演示 区块链视频演示 以太坊,世界的计算机(视频) Vitalik Buterin 解释以太坊(视频) Joseph Lubin 对于构建去中心化应用的访谈(视频) BBC 解释以太坊(视频) 以太坊上的技术难题(视频) 什么是以太坊? 向小白解释:什么是以太坊? 以太坊:与比特币不同的、可以取代 Uber 服务的数字货币 以太坊简介:互联网共治 以太坊初学者入门 什么是以太坊? 足以用来理解以太坊的比特币知识 以太坊:比特币加上一切 以太坊和智能合约简介:一个可编程的区块链 一个非常不技术的以太坊解释(又名 另一个以太坊的解释) 以太坊特么到底是个啥? 以太坊的初学者超级指南 区块链:关于信任的新技术 给 Web 开发者的以太坊知识 以太坊的深入解释以太坊到底是怎么运作的? 最简可行的区块链 大众版白皮书,技术版 黄皮书 (PDF) 在区块链的内容中编程:以太坊 以太坊:平台概况 Vitalik Buterin 在第一次以太坊开发者大会上对以太坊的介绍(视频) 立于区块之外思考 理解以太坊报告(PDF) 非常深入的以太坊阅读清单 剖析以太坊中的庞氏骗局:身份认证,分析和影响 全面的以太坊学习大纲,附带总结和资源分享 历史文章:这个 22 岁的程序员能基于突破比特币吗? 这个怪异的天才造就了以太坊 我想要更多深入的细节!以太坊年度开发者大会(Devcon):Devcon 0(柏林,2014)座谈会和视频 Devcon 1(伦敦,2015)座谈会和视频 Devcon 2(上海,2016)座谈会和视频 Devcon 3(坎昆,2017)座谈会和视频 协议更新列表和硬分叉:(译者注:下文中的 EIP 为 Ethereum Improvement Proposal 的缩写,意思是以太坊优化计划,按数字分了不同的计划或者提议。)Olympic(奥运)预发布版本(测试网,Chain #0 —— 5 月 9 日,2015) 发布博客 **Frontier(前沿)**公开发布版本(包含以太坊创始区块,Chain #1 —— 7 月 30 日,2015) 发布博客 Homestead(家园)(区块编号至 1,150,000 —— 3 月 14 日,2016) 发布博客,EIP-2,EIP-7,EIP-8 DAO 分叉(区块编号至 1,920,000 —— 7 月 20 日,2016) 发布博客,分叉的反对者停留在了 r/EthereumClassic(译者注:即 ETC,该分叉由著名的 DAO 攻击引致。) DoS 分叉(区块编号至 2,463,000 —— 10 月 18 日,2016) 发布博客,EIP-150,EIP-158 伪龙分叉(区块编号至 2,675,000 —— 11 月 22 日,2016)(译者注:该分叉为 DoS 分叉的后续,应对的是相似的问题。) 发布博客,EIP-155,EIP-160,EIP-161,EIP-170 Metropolis(大都会) 会按计划于 2017 年 6 月(to be announced) 工作进行中:EIP-86,EIP-96,EIP-98,EIP-100,EIP-140,EIP-196,EIP-197,EIP-198 (译者注:现在已经是 2018 年中了,这个版本还没发布,不知道 2019 年能不能出,更别提楼下的最终版了。) Serenity(宁静)等待宣布(2018) 为什么以太坊计划迁移到权益证明模式?? 公开的测试网列表:Olympic,预发布的公开测试网,网络编号为 0(已被 Morden 取代) Morden,基于 Fontier 的公开测试网,网络编号为 2(已被 Ropsten 取代) Ropsten,基于 Homestead 的公开测试网,网络编号为 3(刚刚重启) ConsenSys,工作量证明模式的 公开测试网 Kovan, 权威证明模式的 公开测试网 Rinkeby,权威证明模式,等待宣布(EIP-225) (译者注:现在已经是 2018 年中了,ConsenSys 已经凉了,Rinkeby 上线也很久了。) 测试币水龙头:Ropsten 水龙头 Rinkeby 水龙头 杂文收集:了解区块链 一些区块链读物 如何投资以太坊上基于以太币的代币 以太坊的阅读指南 为潜在的 Dapp 开发者准备的以太坊阅读清单 Reddit 的以太坊指南 以太坊 Wiki 以太坊 Stack Exchange(问答平台) 区块链、代币和数字货币的终极版阅读清单 区块链相关学术文章的初创列表 研究以太坊第二层的扩容解决思路的实际方案:状态通道、Plasma、和 Truebit 权益证明(PoS)权益证明模式的问与答 Casper FFG 文章(译者注:FFG 为 Friendly Finality Gadget 的缩写,直译为 “友好的终结工具”。) Alpha 版 Casper FFG 测试网操作指南 Plasma(译者注:一种以太坊外链解决方案)Plasma 白皮书 最简可行版的 Plasma Sharding(分片技术)Sharding 研究 Sharding 在 GitHub 上的代码库 入门使用以太坊的简易指南 以太坊 —— 入门 —— 澳大利亚版 这些词好难啊:以太坊/密码界中常用词的定义 在以太坊中快速起航 安全:给数字货币交易者的线上安全指南 给你的数字生活的私人安全指引 是时候实现了:请停止依赖第三方来保护你和你的资产,你可以对你的安全负责。 傻瓜式的离线存储方式 生态区块链行业中的女性 我可以在哪里加入以太坊社区? Dapp 的状况 (译者注:Dapp 为 decentralized application 的缩写,意思是 “去中心化应用”。) 以太坊商业目录 其他的会议,工作坊,meetups(线下聚会)或者教程:欧洲以太坊开发会议(EDCON) 伦敦以太坊(meetup) 柏林以太坊(meetup) 以太坊核心开发者会议(电话会议) 用 Solidity 构建以太坊 DApp(教程) 时讯以太坊一周新闻 Dapp 日报 Consensys 媒体 The Etherian(译者注:下面这三个链接好像都失效了) The Control HEAT Crypto Roundup YouTube 频道Crypt0's News —— 每日视频博客 Ameer Rosic Decypher 媒体 Ivan 聊技术 新闻币报 ETH 新闻 CoinDesk(Digital Currency Group 所有,它的报道对竞品数字货币有利益偏向。) 以太坊区块浏览器Etherscan Ethplorer 小币浏览器 钱包My Ether Wallet(译者注:很多时候会以 MEW 缩写来表示,所以不予翻译。) MetaMask 谷歌浏览器插件 跨平台 Jaxx 钱包 Mist 钱包 Parity 钱包 Harmony 钱包 imToken Toshi 钱包 + Dapp 浏览器 + 通讯器 Trust 钱包 + Dapp 浏览器 硬件钱包Ledger Nano S Trezor ICOs (原始代币发售):ICO 日历 Smith and Crown TokenMarket imToken ICO Dapp 思想碎片为什么去中心化很重要 去中心化技术的发展可能性 以太坊的风险 以太坊社区的价值 以太坊生态的成长 协议组成的互联网:构建在区块链上的超连接未来 货币、区块链和社交的扩容 去中心化应用或协议数字通证和即将到来的协议创新时代 开放协议的黄金纪元 关于通证的思考 “胖”式协议 去中心化组织可以解决世界上最难的问题 区块链上的通证和去中心化商业模式的黎明 Dapp 的商业案例 —— 把去中心化当成一种策略 “去中心化”给 DApp 带来了一个短而新的黑暗时代 数字经济的难点 众筹区块链证券式代币适用的法律框架简介 区块链投资和新型资产给现代风投机构带来的问题 如何使用代币在区块链上募资 探索连续性代币模型:面向百万级网络的价值 不给糖就捣蛋?区块链上数字资产的投资 向代币(和代币众筹)说不 数字货币和区块链初创公司是如何使传统风头机构转向的? 如何衡量一个(项目的)原始代币发售价值(ICO)? ICO 2.0 —— 什么是理想的 ICO? 搭上区块链投资的快车:全球零售化投资的未来 交易在 Pipsology 学院中学习外汇交易(非常适用于数字货币交易) 范式转换:比特币和山寨币市场的技术分析,市场周期、结构和操纵的简介 数字货币市场资本状况 交易所去中心化交易所 (大部分基于以太坊资产)OasisDexEtherDeltaForkDeltaIdexCryptoDerivativesRadex0x Relayers:(译者注:去中心化交易所的市场平台)ParadexRadarRelayEthfinex编程在以太坊上创建并部署一个完全去中心化 DApp 的终极版由头到尾的指南 审计智能合约的终极指南 + Solidity 上的危险攻击 Dapp 全简介:我如何在以太坊上开始开发? 在以太坊上发行你自己的数字货币 不借用第三方从朋友处募资 在区块链上建立民主机制 只需十步就构建出你第一个 DApp 以太坊 DApp 开发(视频) 监测网络状态(镜像站)和 gas 费用市场状况 阅读技术文档 (Homestead 版本) 试试一些在以太坊上运行的 DApp 吧 学习 Solidity,以太坊智能合约语言 试试 Truffle 吧!一个智能合约开发框架 以太坊商业生态路径一览 在以太坊 Stack Exchange 上问问题吧 与以太坊开发者队伍、社区在线通话 发现你附近的以太坊 Meetup 在以太坊上开发智能合约的菜鸟全简介 我可以在哪里学习如何使用 Solidity 语言开发 DApp 呢? 学习在以太坊上编程 Windows 平台上以太坊、Solidity 和 Geth 的入门 —— Part 1 以太坊、Solidity 和 Geth 的入门 —— Part 2 CryptoZombies.io —— 以制作游戏的方法学习 Solicity 如何使用 Geth 部署智能合约 开发一个完整的以太坊投票 DApp 的三部曲 以太坊开发指南 —— Part 1. 开发一个以太坊文件认证合约 以太坊开发指南 —— Part 2. 与智能合约交互 以太坊开发指南 —— Part 3. 为智能合约开发网页界面 开发以太坊区块链应用 —— 实用且详尽的研究项目 以太坊智能合约安全 以太坊智能合约最佳实践案例 探索以太坊和智能合约 —— 完全版在线课程 法务比特法律地图 区块链证券式代币适用的法律框架简介 区块链代币适用的证券法律框架 智能合约适用的法律和合法性 IRS 虚拟货币指引:为保证美国联保税收,虚拟货币被定义成资产;适用于资产交易的一般规则 比特币在不同国家的合法性 如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 — 区块链分舵 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。
2023年03月24日
4 阅读
0 评论
0 点赞
2023-03-24
什么是智能合约
前言智能合约是在以太坊的EVM上运行的一段经过编译的代码。在这篇文章中我将详细阐述智能合约到底意味着什么,开发的时候有哪些需要思维模式的转变,如何避免踩坑等。智能合约智能合约与平时的代码其实没有什么区别,只是运行于一个以太坊这样的分布式平台上而已。这个运行的平台,赋予了这些代码不可变,确定性,分布式和可自校验状态等特点。代码运行过程中状态的存储,是不可变的。每一个人,都可以开一个自己的节点,重放整个区块链,将会获得同样的结果。在以太坊中,每个合约都有一个唯一的地址来标识它自己。客户端可以与这个地址进行交互,可以发送接收ether,调用函数,查询当前的状态等。智能合约,本质上来说就是代码,以及代码运行后存储到区块链上的状态两个元素组成。比如,你用来收发ETH的钱包,本质上就是一个智能合约,只是外面套了一个界面。GAS是什么既然提到了智能合约,就不得不提和他相伴的GAS限制。智能合约,就是一些代码,运行整个分布式网络中。由于网络中的每一个节点都是一个全节点。这样的好处是容错性强,坏处是效率低,消耗资源与时间。因为执行计算要花钱,而要执行的运算量与代码直接相关。所以,每个在网络运行的底层操作都需要一定量的gas。gas只是一个名字,它代表的是执行所需要花费的成本。更详尽的请参考:《Calculating Costs in Ethereum Contracts》开发智能合约意味着什么开发智能合约意味着什么,或者说为什么我们要开发智能合约,这是一个需要思考的问题,通常的示例一般都是投票,竞价,代币等,这些但是这些例子总是让人很迷惑,为什么要用智能合约来实现这些东西,能不能实现一些有实际价值的,更通俗,与真正的合约相关的内容。前面介绍了合约是什么,可能你们还是没有很清楚,合约可不可以等于合同,合同是不是具有约束性,能在双方(合同一般是双方或者多方)间提供约束,现在的智能合约是否具有法律约束。我们需不需要智能合约虽然现在区块链很火,但是我们必须要承认,他的应用面不是很广,只有少部分特定的行业适合使用。合约开发方式现在常用的语言是 solidity 类js语言,用用remix作为编辑,编译和调试的工具。或者本地使用文本编辑器编写代码,并保存为.sol 的文件,然后使用solcjs 工具将 源码编译为.abi .bin文件。合约的部署方式通过终端部署 通过web3js部署 通过JsonApi部署 引用深入浅出以太坊 4--写一个简单的智能合约 对于以太坊中gas的理解本人网站出处:www.gethgeek.com/archives/sm…
2023年03月24日
6 阅读
0 评论
0 点赞
2023-03-24
使用 React+ethers.js 开发简单加密钱包
最近在开发一个 NFT 二创平台,其中包含了很多概念和技术。我会更新一个系列的文章来总结和沉淀在这个过程中的一些知识与思考。本文是对 ethers.js 进行一个全方位介绍,非常适合 web3 入门学习。什么是 ethers.js?Web3 中的各类 DApp,都需要与智能合约进行交互。如果用原生 JS 来做这些事会很麻烦。这时就需要使用专属的 SDK。目前 JS 环境中有两个主流的库可以用来和智能合约进行交互,一个是 web3.js,另一个是 ethers.js。ethers.js VS web3.jsweb3.js 比 ethers.js 出现的更早。但是目前 ethers.js 更受欢迎。主要原因有如下两点:体积:ethers.js 体积仅有 116.5kb,web3.js 有 590.6kb。相差 5 倍左右。 设计理念:由于设计理念不同。web3.js 认为用户会在本地部署以太坊节点,私钥和网络连接状态由这个节点管理。但实际上大部分人都不会在本地部署以太坊节点。ethers.js 充分考虑了这一点,它是用 Provider 管理网络连接状态,用 Wallet 管理密钥,更加安全和灵活。而且原生支持 ENS。 ethers.js 基本使用介绍节点即服务由于在本地部署一个区块链节点的成本并不低,很少会有人真的部署一个节点,而是选择使用节点即服务。这类服务有很多,比如老牌的 Alchemy、Infura、Moralis 以及今年估值 102 亿美金的新秀 Tenderly。在这里我们选择 Alchemy,目前它的市场占有率是最高的。alchemy 的网址在这:dashboard.alchemy.com/。具体的注册登陆就不讲了。登陆之后我们创建一个 App。Chain 选择 Ethereum,Network 选择 Goerli。这样就成功创建了一个 App,点击后面的 view key,就可以查看 key。我们把它复制下来,后面会用到。构造合约在对合约进行读取或交互之前,我们首先需要构造一个合约对象。合约对象有三个构造参数,第一个是合约地址,是一个字符串。第二个是合约的 abi,可以是一个 JSON,第三个参数是 provider 对象,它用于管理网络连接状态。下面的例子中就是使用 alchemy 提供的 JSON RPC 接口作为网络连接。const rpc = `https://eth-goerli.g.alchemy.com/v2/$`; const provider = new ethers.providers.JsonRpcProvider(rpc); const contract = new ethers.Contract(contractAddress, abi, provider) 复制代码除了 JsonRpcProvider 以外,ethers 还有 IpcProvider、InfuraProvider、AlchemyProvider、Web3Provider 等多种 Provider。读取合约信息abi 中的方法会直接挂载到 contract 对象上,我们可以直接调用。不过需要注意,所有的操作都是异步的。(async () => { const owner = await contract.owner(); console.log(owner); })(); 复制代码连接钱包由于和合约交互需要支付 gas 费用,所以必须有一个数字钱包。钱包有很多种,比如 MetaMask、Rainbow、Coinbase Wallet 等。其中 MetaMask 是最常用的一种数字钱包。这里主要介绍如何连接到 MetaMask。MetaMask 有一个浏览器插件,如果用户安装了该插件,在 window 对象下会有一个 ethereum 对象。我们可以调用 ethereum.request 方法发起请求,参数是一个对象,对象的 method 描述该次请求的操作。ethereum.request 方法是异步的,会返回一个数组,该数组是所有登陆钱包的账户地址字符串,第一个账户就是当前激活的账户。如果返回的数组长度为 0,则意味着没有登陆任何账户。(async ()=> { const accounts = await ethereum.request() if(accounts.length === 0) { throw Error('未登录任何账户') } const activeAccount = accounts[0] console.log(activeAccount) })() 复制代码我们还可以通过 ethereum.request 方法获取当前的网络状态。(async ()=> { const chainId = await ethereum.request() console.log(chainId) })() 复制代码它会返回一个字符串。0x1 表示以太网主网;0x5 表示 Goerli 测试网,更多网络的 chainId 可以在这个网站查看:chainlist.org/。下面是使用 ethers.js 来连接 MetaMask 的代码。(async ()=> { const provider = new ethers.providers.Web3Provider(window.ethereum) const accounts = await provider.send("eth_requestAccounts", []) const activeAccount = accounts[0] })() 复制代码如果使用 MetaMask 作为 provider,那么就不需要再使用 alchemy 了。钱包在转账交易之前,我们需要创建一个 Wallet 实例,它的作用是对交易和消息进行签名。创建 Wallet 对象的方法有三种。通过 Wallet.createRandom 创建随机钱包这种方式创建的是一个单机钱包,需要连接网络。const wallet = ethers.Wallet.createRandom() wallet.connect(provider) 复制代码通过助记词创建const wallet = new ethers.Wallet.fromMnemonic(mnenonic.phrase) 复制代码通过私钥和 provider 创建const wallet = new ethers.Wallet(privateKey, provider) 复制代码钱包信息我们可以在创建好的钱包上面获取很多有用的信息,比如钱包地址、助记词、私钥、交易次数等。console.log(wallet.address) console.log(await wallet.getAddress()) console.log(wallet.mnemonic) console.log(wallet.privateKey) console.log(wallet.getTransactionCount()) 复制代码转账一旦又了钱包,我们就可以向其他人发起转账交易。创建一个 tx 对象,它最少需要两个属性,to 和 value,分别表示接受钱包地址和转账额度。然后使用 wallet.sendTransaction 方法发送转账,它会返回一个 receipt 对象。这个对象有一个异步的 wait 方法,当交易上链后会返回。const tx = { to: address, value: ethers.utils.parseEther("0.1"), } console.log('开始转账') const receipt = await wallet.sendTransaction(tx) await receipt.wait() console.log('完成转账') 复制代码通过合约转账交易交易需要使用 Wallet 对象。再通过 wallet 作为合约的第三个构造参数创建 Contract 对象。调用合约的 transfer 方法,进行转账交易。该方法需要两个参数,转入的钱包地址字符串和转入的数量。transfer 会返回一个 tx 对象,该对象有一个异步的 wait 方法,会在交易完成后执行。const wallet = new ethers.Wallet(privateKey, provider) const contract = new ethers.Contract(contractAddress, abi, wallet) (async ()=> { const tx = await contract.transfer(toAddress, ethers.utils.parseEther("1")) await tx.wait() })() 复制代码通过 signer 进行转账通常我们无法直接拿到 privateKey,但是可以通过 signer 对象间接使用 privateKey。只需要进行签名就可以进行交易。这也是最常用的交易方式。const signer = walletProvider.getSigner(); const tx = { to, value, }; const receipt = await signer.sendTransaction(tx); await receipt.wait(); 复制代码使用 React 和 ethers.js 开发加密钱包接下来我们开发一个最简单的加密钱包,具备最基础的转账功能和查询余额功能。创建项目我们首先创建一个 Next.js 项目。npx create-next-app 复制代码需要选择 TypeScript。安装依赖安装 ethers.jsnpm i ethers 复制代码安装 tailwindcssnpm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p 复制代码修改 tailwind.cinfig.js。/** @type */ module.exports = { content: [ "./pages/**/*.", "./components/**/*.", ], theme: { extend: , }, plugins: [], } 复制代码修改 styles/globals.css。@tailwind base; @tailwind components; @tailwind utilities; 复制代码安装 headless-uinpm install @headlessui/react 复制代码整体架构设计由于业务并不复杂,我们可以将它简单划分为几个组件。使用 Context 足够应对这个场景,而不需要额外导入状态管理库来增加复杂性。Wallet 是根组件,内部维护了很多 state,以 Context 的方式将数据和操作注入到子组件。Connect 负责连接钱包和断开连接。Details 负责显示钱包信息,是纯展示型组件。Transfer 负责向其他账户进行转账。Loading 负责渲染加载动画,是纯展示型组件。创建上下文type IWalletCtx = { walletProvider: any; setWalletProvider: (walletProvider: any) => void; msgIsOpen: boolean; setMsgIsOpen: (msgIsOpen: boolean) => void; msg: string; setMsg: (msg: string) => void; account: string; setAccount: (account: string) => void; networkName: string; setNetworkName: (networkName: string) => void; balance: string; setBalance: (balance: string) => void; showMessage: (message: string) => void; refresh: boolean; setRefresh: (refresh: boolean) => void; }; const WalletCtx = createContext<IWalletCtx>( as IWalletCtx); 复制代码通过初始化一个对象,然后断言为 IWalletCtx 的方式,可以避免在使用 WalletCtx 时添加是否为 undefined 或 null 的判断。因为我们一定会注入数据。Loading 组件Loading 作为纯展示型组件,是最简单的组件。SVG 的代码是直接从 tailwindcss 文档中搬运过来的。仅仅是添加了一个 size 属性,用来展示不同大小的尺寸。function Loading(: ) { const sizes = { sm: "h-3 w-3", md: "h-5 w-5", lg: "h-7 w-7", xl: "h-9 w-9", }; return ( <svg className= text-black`} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" > <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" ></circle> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" ></path> </svg> ); } 复制代码Wallet 组件在 Wallet 组件中创建这些 state,并注入到 context 中。export default function Wallet() { const [walletProvider, setWalletProvider] = useState<any>(null); const [msgIsOpen, setMsgIsOpen] = useState(false); const [msg, setMsg] = useState(""); const [account, setAccount] = useState<string>(""); const [networkName, setNetworkName] = useState<string>(""); const [balance, setBalance] = useState<string>(""); const [refresh, setRefresh] = useState<boolean>(false); useEffect(() => { setWalletProvider(new ethers.providers.Web3Provider(window.ethereum)); }, []); const showMessage = (message: string) => { setMsg(message); setMsgIsOpen(true); setTimeout(() => { setMsg(""); setMsgIsOpen(false); }, 2000); }; return ( <WalletCtx.Provider value={{ walletProvider, setWalletProvider, msgIsOpen, setMsgIsOpen, msg, setMsg, account, setAccount, networkName, setNetworkName, balance, setBalance, showMessage, refresh, setRefresh, }} > <div className="flex flex-col gap-4 p-4"> <Dialog open= as= onClose=> <div className="fixed flex justify-center items-center w-full top-2"> <Dialog.Panel className="inline-flex flex-col bg-green-400 text-slate-600 p-4 shadow-xl rounded-3xl"> <Dialog.Title></Dialog.Title> </Dialog.Panel> </div> </Dialog> <Connect /> <Details /> <Transfer /> </div> </WalletCtx.Provider> ); } 复制代码Wallet 组件基本上没有什么逻辑,它的主要作用有三个:向 context 注入数据。 创建 ethers.provider。 使用 Dialog 组件作为全局消息提示。 Connect 组件在 styles/globals.css 中添加按钮样式。@layer components { .btn { @apply bg-black text-white py-2 px-4 rounded-3xl; } } 复制代码在 index.tsx 中编写逻辑。function Connect() { const { walletProvider, account, setAccount, setNetworkName, setBalance, showMessage, refresh, } = useContext(WalletCtx); const refreshBalance = useCallback(async () => { if (!walletProvider || !account) return; const balance = await walletProvider.getBalance(account); setBalance(ethers.utils.formatEther(balance)); }, [setBalance, walletProvider, account]); useEffect(() => { refreshBalance(); }, [refresh, refreshBalance]); const connectToMetamask = async () => { try { await window.ethereum.enable(); const accounts = await walletProvider.send("eth_requestAccounts", []); const network = await walletProvider.getNetwork(); const balance = await walletProvider.getBalance(accounts[0]); setAccount(accounts[0]); setNetworkName(network.name); setBalance(ethers.utils.formatEther(balance)); } catch (error) { console.log(error); showMessage("failed to connect to metamask"); } }; const disconnect = async () => { setAccount(""); }; if (!account) { return ( <div className="flex justify-end p-4"> {walletProvider ? ( <button className="btn" onClick=> connect to metamask </button> ) : ( <Loading /> )} </div> ); } return ( <div className="flex justify-end items-center gap-2"> <h1 className="text-end">Hello, </h1> <button className="btn" onClick=> disconnect </button> </div> ); } 复制代码我们连接钱包后会获取 3 个重要的信息:钱包账户地址、连接的网络和余额。分别通过 walletProvider.listAccounts()、walletProvider.getNetwork() 和 walletProvider.getBalance(accounts[0]) 来获取,需要注意它们都是异步操作。Details 组件Details 作为纯展示型组件没有什么逻辑,主要是一些样式。function Details() { const = useContext(WalletCtx); if (!account) { return null; } return ( <div className="flex flex-col gap-4 w-full bg-slate-800 text-white p-4 rounded-md"> <div className="flex justify-between"> <div className="text-2xl font-thin">balance</div> <div>network: </div> </div> <div className="flex items-end gap-2"> <div className="text-2xl"></div> <div>ETH</div> </div> </div> ); } 复制代码现在我们看一下 Connect 和 Details 组件一起使用的效果。Transfer 组件Transfer 的功能比较简单,它在 UI 上仅仅包含两个输入框和一个 send 按钮。两个输入框分别可以输入 to 和 value,表示转账的钱包地址和转账金额。function Transfer() { const = useContext(WalletCtx); const [to, setTo] = useState<string>(""); const [amount, setAmount] = useState<string>(""); const [transferring, setTransferring] = useState<boolean>(false); const transfer = async () => { try { const value = ethers.utils.parseEther(amount); const signer = walletProvider.getSigner(); const tx = { to, value, }; setTransferring(true); const receipt = await signer.sendTransaction(tx); await receipt.wait(); setTo(""); setAmount(""); showMessage("successfully transferred"); } catch (error) { console.log(error); showMessage("failed to transfer"); } finally { setTransferring(false); setRefresh(!refresh); } }; if (!account) { return null; } return ( <div className="flex flex-col gap-4 mt-4"> <div className="font-bold text-4xl">Transfer</div> {transferring ? ( <div className="flex flex-col items-center gap-4"> <div className="text-3xl">transferring...</div> <Loading size="xl" /> </div> ) : ( <div className="flex flex-col gap-2"> <input className="input" value= onInput= type="text" placeholder="address" /> <input className="input" value= onInput= type="number" placeholder="amount" /> <button className="btn" onClick=> send </button> </div> )} </div> ); } 复制代码转账是通过 signer.sendTransaction 方法进行的,它会返回收据对象 receipt。在转账时使用到了 ethers.utils.parseEther,因为 value 默认的单位是 wei,它非常小,10 的 18 次方才是一个 ehter。在 JS 中需要使用 BigInt 类型表示,并不方便操作,而我们更喜欢用 ether 来描述货币。所以这个 API 可以帮我们转换货币单位。需要注意,在测试时需要选择 goerli 网或者其他测试网,否则会浪费 gas 费。你至少要有两个钱包账户,这样可以从一个钱包账户转到另一个钱包账户。下面我们来测试一下转账。这时 MetaMask 钱包会弹出来签名界面。点击确认后,需要等待一段时间上链,大约 1 分钟,或者更久。转账成功后,当前账户的余额会刷新。完成现在一个简单的加密钱包就完成了。通过这个项目的学习,相信你已经学会了 ethers.js 常规 API 的使用。源码链接:github.com/luzhenqian/…线上地址:web3-examples.vercel.app/我一直在深入研究 Web3 的最新趋势,相信这些趋势会使我们的生活变得更好。如果你对 Web3 感兴趣,欢迎联系我,一起打造一个更公平的世界吧。同时也可以关注我的 Web3 专栏,我会持续更新更多 Web3 相关内容。
2023年03月24日
12 阅读
0 评论
0 点赞
2023-03-24
智能合约全栈介绍 - Howard | Jeth 第一期
编者按:本文系 Qtum 的 Howard 叶倍宏讲师,在由掘金技术社区主办,以太坊社区基金会、以太坊爱好者与 ConsenSys 协办的《开发者的以太坊入门指南 | Jeth 第一期 - 北京场》 活动上的分享整理。Jeth 围绕以太坊技术开发主题的系列线下活动。每期 Jeth 会邀请以太坊开发领域的优秀技术团队和工程师在线下分享技术干货。旨在为开发者提供线下技术交流互动机会,帮助开发者成长。Howard 老师本次分享视频回放(B站分享整理传送门以太坊智能合约 + DApp 从入门到上线:来自前端工程师的实战指南 - 王仕军 | Jeth 第一期详解 ERC20 代币及众筹 - 熊丽兵 | Jeth 第一期Howard 是《Deep Dive Into Ethereum Virtual Machine》一书的作者,该书深度剖析了 Solidity 和以太坊的原理。目前,Howard 任 Qtum 量子链 DApp 平台核心工程师,负责开发工具和数据库。Howard 在创业界拥有10年的产品开发经验,并且对构建去中心化产品充满热情。他也是本次活动的出品人。大家好,很荣幸今天跟大家分享一下以太坊智能合约的一些开发经验,在开始之前,我今天先给大家先讲一个高层面的介绍,从前端到后端介绍一下以太坊的技术栈;然后之后我们两位老师会给大家去介绍一些细节。所以我的这场分享只需要大家脑海里有个对以太坊的概念即可,具体细节后面我们两位老师会给大家更精彩的讲解。我今天就先介绍一下智能合约以及它存在的理由,然后带大家看一下智能合约长什么样子,最后会带大家梳理以太坊的整个架构,从前面面对用户的DApp到后面的存储数据库。先简单自我介绍一下,我是台湾同胞,目前在大理远程办公。为什么要智能合约切入主题,为什么要有智能合约,挖矿非常的费资源,为什么要花这么多的精力这么多的资源在挖矿上面?有位区块链先驱者提了这样一个概念:技术让人跟人之间去协作。我们个人自己跟自己协作,但是我可能多做一点事情,会找我的朋友找我的亲人去合作,以此让我做更大的事情,这样我们需要一些合作,我信任我的兄弟,我信任我的同学这种类型的合作。更多的合作我们跟以太坊的爱好者我们掘金的事情我们互相的爱好及把我们捆绑在一起让我们做更大的事情,再更大一点在一个城市,这个时候我们要有法律,我们需要各种政府部门,机关,就算我不认识你,我不认识买我产品的人,但我可以通过我们的合约去保证我们合作的可行性,更大一点就是国家跟国家、国际法院的合作等,这些跨边界的合作是更复杂的体系。人类的文明跟技术是随着这个合作的复杂性而增长的。那为什么我们要有规则呢,信任其实是非常昂贵的资源,我最信任的人就是我的亲人和我的朋友,尤其是从小一起长大的朋友;但是我来到城市、国家这个级别的话,其实是很难信任一个不认识的人,所以我们要提高达成合作的可能性,就要降低信任最小的需求。我们只要通过这个概念降低互相伤害的风险,友谊的小船就不会翻。我们要尽量的信任别人,他讲这些东西是科技,我们从人跟人之间的信任上升到法律,所以我们开始讲法律时就不需要提及信任。从人情到市场,我可能愿意跟人合作,到市场我们开始讲价格,我们有价格的协调机制,就不需要讲人情了。我们点对点的对话,必须要认识你这个人我才有办法跟你协作,多对多的撮合市场机制,这是一个持续演化的过程。所以我们开始讲这些事情的时候,会牵扯到社会扩容,社会的扩容是需要成本的,我们需要有政府,需要有法院,需要有警察机构。上图中我们可以看到OECD政府的GDP占比是从36%到58%不等。我们虽然没有买单、没有直接的去支付这些成本,但是实际上这些东西在政府开销上占了很大比重。那区块链做的事情他是因为取代信任的机制,即以计算机替代人工成为核心的概念。所以说智能合约他具体就是一个计算机程序,它来替代靠人工运营的这些机构。智能合约具体是什么接下来我们可看一下智能合约具体是什么东西。第一个例子就是亚马逊的 LAMBDA 服务,LAMBDA一个特性是你在LAMBDA这个平台写程序的时候,你只是去写你的业务逻辑,而这个业务逻辑你直接上传到 LAMBDA 平台上面,好像只是一个服务器的回调。只要有请求进来它就调用这个回调,计算完成后就直接终止了,你要自己去部署一个服务器,没有逻辑。LAMBDA 这个平台让你直接把服务器的这个业务逻辑放在平台上面,完全不用去管后面对应哪些具体服务器,一有请求进来,并把 LAMBDA 启动,这个跟智能合约的架构非常像。我们可以看一个较为简单的例子,这个计数器的业务逻辑很简单,这边有一个COUNT,把它想成数据库里的数据,这个合约有数据也有业务逻辑,这个业务逻辑要做的事情就是把这个数字递增。当外部调用这个递增的时候,它去数据库里面修改这个COUNT这个变量,这个就是你的智能合约,从概念上来讲也没有什么,它之所以牛逼是因为我们用去中心化的网络去同意了这个数据应该是什么,我们具体接下来看一个更好的例子。我们把这个合约的这些变量理解成一个数据库,我们可能平常在写一个加法的类型,它只是存在于类型里面,你的假若死掉了,这个内存就清空了。在智能合约里面,虽然它是变量,但是你是写在链上面,而这个数据库恰巧是特别昂贵的数据库。我们类比成 Java 的伪代码,我们会想象后面每一个合约它有一个相对应的数据存储,它是一个对象,是一个数据库,在这个方法里面刚才讲了递增的方法,它只是去操作这个数据库,在这个键修改它里面的值,所以你每次去改一个变量其实是对数据库做一个操作,而且这个操作是特别昂贵的操作,可以给一个概念,普通所有的这些指定是要费用的,普通的一个纸币,比如说加减乘除,大概是2到5个不等,存储的话是2万个,大概算是5000倍的倍增。我们看一个更完整的例子,功德香火链,这个例子大概介绍了智能合约非常本质的一个东西。为什么要写智能合约,我们并不是只是为了写这个数据,他之所以牛逼是因为他里面有金融属性,大家可以相信你,如果在我自己的亚马逊 LAMBDA 上搞一个功德香火链是没有人相信的,我在数据库里面可以随便修改,但如果我们改成在智能合约中就变成是一个去中心化、不需要信任的一个程序。大家来看一下这个东西,有一些需求,他初始化的时候我们要设定管理员,任何人我们都允许他捐款,最后要用这个款项的时候我们允许管理员取款,然后让他写出所有取款他的去处去了哪里。这个就是我们整个智能合约的代码,就十几行而已,这上面大概有两点需要留意,有合约属性,拥有者的姓名,我们会用这个属性来存储他的管理员,我们控制了管理员权限才可以进行修改。下方分别是捐款和取款的函数,我们接下来把这两个函数分开讲解。初始化设定管理员,所以说这边我们是要把数据存起来,我们要知道谁是管理员,在创建这个合约的时候它是用一笔交易创建的,即有一个钱包去付钱然后去说这笔交易谁创建的。这里的属性指出当前用户的身份,这一笔交易的操作者以及合约的操作者。我们这边做的事情很简单,在创建这个合约的时候我们把当前的操作者存储在这个数据库里面,有了这个东西之后我们就可以验证正在操作的人是不是管理员。接下来我们开始做这个核心的东西,即任何人都可以转款,之后你要做众筹让别人给你转钱,这个东西就涉及到金融的属性了。我们看到这个函数也是一样,在操作这个把钱的用户,我们要记下来,也就是要记下这个功德;PAYABLE 它只是一般拿来处理跟金钱相关的东西,这里是处理存进来的钱。这里要有个断言,要确认这笔钱必须要大于0.001,因为我不想要太少,太少就看不上、不想要;最后这边有一个日志,说我们把这个输出和这个功德记下来,然后保佑发送这个钱的人,这个日志就记在链子上面了。报错是直接退款,他没有满足这一个条件这笔交易就失败了,他捐了钱就打回原本的帐号,当然这个真实的合约你会做各种其他的需求,理念上就是你先检查满足了你的需求之后,你再去操作你的业务逻辑,然后最后你就可以输出记录这个事情。我认为比较有意思的事情,这个合约他有一个相对应的帐号,这个帐号合约有多少钱,你看里面的业务逻辑并没有显性的说把这个钱给这个合约,你这个隐性只是说这一笔交易进来了,只喜欢这个逻辑,然后最后就会给这个帐号,但是你不需要代码直接说这个事。接下来看看看管理员取款,我们这个函数可以让管理员指出取钱的数量,这里又有一个断言,我们在断言里面判断当前操作这一笔交易的人是否是这个合约的拥有者,我们一开始就要把这个拥有者记录下来,之后的操作中我们再去验证一下他是否是当初创建合约的人,如果是的话我就给这个用户给他打钱,转给他所要的钱。我们首先要做一个小测试,假如说这个帐号当前有10个以太坊币,然后拥有者试着去取11个以太坊会发生什么事、触发什么条件会报错。假如说它试着转钱,这边是从合约里面把钱转给A,它账户里面的钱不够多就会报错,同事状态会直接全部回到原来的状态。这个回滚的概念特别的重要,一般你在写程序做一个操作的时候就有一个请求进来了,你对数据库做一个操作,那你报错了,之前的数据库要回滚;以太坊会自动做这个回滚的事情,只要一失败它就马上回滚。我们可以看一下一些平台的特性,过去通常是你自己公司买这个服务器,大家用这个服务是不用钱的;现在是用户承担所有的计算费用,GAS模型去计算,然后很核心的点是用户他掌握了权限,比如说你用支付宝想转帐你在支付宝平台上确认这笔帐能不能转,那现在用客户端在客户端做了签名,然后服务端说让你转帐,他是不会去验证这个客户在终端到底是谁。还有一点,虽然说以太坊或者量子,有着几千几万的节点,其中以太坊是有17000多个节点,看似这17000个节点计算能力还不错,事实上并没有。因为每一个节点要重复每一个计算,计算每一个数据,所以当我们说存储数据库,如果是当成传统数据库来用是不可行的,因为太贵了。去中心化平台也是有他自己的缺点,他很慢,处理一笔交易要花20秒;其次贵,每一笔交易要花大概0.1或者是0.2美元,如果你要发一笔比较大的合约就是10美元到100块人民币左右了。你做应用交易的生命周期极其复杂,这个东西你必须要等他确认,等一次两次三次,然后他可能中间会抛错,各种不同抛错情况,有可能是业务逻辑抛错,也有可能是他给的GAS不够,然后跑一半逻辑他没有钱了,就终止了,还有一些其他的在业务上有一些没有办法做,没有网络请求没有随机员,还有你不能更新合约代码,之前出了一些漏洞,有些人写了合约,这个合约被别人删了,然后他很多钱就卡在里面,几千万美金的钱卡在里面。所以说在做的时候就可能需要思考一下我这个东西去中心化有意义吗,到底是哪些东西值得去中心化。DAPP全栈走一回那我们接下来更深入的看一下,DApp全栈走一回,我们用一个简单的例子然后走到最后面的区块链上面存储的数据。这个合约中,我有一个变量,有一个值,然后我唯一要做的事情就是修改这个值或者是读取这个值,这个就是我的一个简单的智能合约。我们会从前端开始,我们看一下从前端再递增RPC,这个是ABI的编码,我们一会儿再展开来说,这个合约就是他的业务逻辑,EVM最后到区块链存储,我们一个一个展开来再说。所以说你可以比较一下WEB 2.0到3.0他们技术站的对比,然后WEB 3.0就在吹牛说WEB3.0技术是一样的,只是换汤不换药,在WEB 2.0我们做了一个请求,用户按一个钮就产生一个请求,用户按一个钮就产生了一笔交易,会带有一定的金额,这个需要整个网络去确认,而不是单一的服务器去确认,但是在前端是由用户批准交易之后再丢给网络让网络去处理。然后你写后端说我这个请求他里面具体的数据是怎么样的给一些结构,那我们现在做后台的话常常是用ABI的结构,以太坊这边它相对应的这个数据结构就是叫做ABI编码,传给智能合约的数据就是用ABI编码,在WEB 2.0我们做服务,后来在WEB 3.0我们想写智能合约去做这些服务。但操作系统层面在WEB 2.0时是 Windows 这些操作系统,现在的以太坊就是虚拟机,同时也能做一些操作系统方面的处理,比如说存储数据转钱,或者是调用其他合约,这些是用EBM来做的,这些是混合了两个不同的东西在一起,又是虚拟机又是操作系统。最后我们要存储数据,就是在WEB 2.0文件系统这些去存数据,在WEB 3.0我们有电子版本控制,你可以有各个版本。那我们看看前端,前端要做的事情就是展现链上的数据,重点还是一样,用户在本地签名之后再把数据提交到链上,这个权利是握在用户的手上,我们可以比较一下WEB服务和纯客户端的两个概念,大家熟悉这个应用就是币安这种平台做交易,实际上这些不够形成这些权限的控制都在平台手上,像钱包这种东西才是比较正儿八经去中心化,你说了话不算,是用户控制权限。在这两个极端去纠结如果我真的要做一个去中心化应用的话,它的用户体验会很差很难用;好处是中心化的服务,用户体验好且反应快,但是就是出了事的话那大家都一锅掀了,现在是在两个极端里面去纠结怎么做。我们看一下前端的例子,这个REMIX IDE,他指向合约,他有两个按钮,一个是GET 的按纽,还有一个是SET的按钮,我要通过调用的方法去改那个值。然后当我按向那个按钮我要去设定666,这个时候他用RPC去递交一个事物,然后在以太坊就是这个RPC的方法,他们的概念是一样的。所以我调用这个方法从我的钱包去打这个数据,然后我们之后会把这个数据去展开,它也是一个RPC,这里面的数据传给智能合约的数据,我们来展开看一下。我把刚刚那个东西调用到网络上面,我就可以看到这一笔交易,这个就会把交易里面的一些信息显示出来,我花了多少 Gas,然后里面传了一些数据。我们看 ABI 的编码,我们可以拆开两部分,这个 60fe47b1 他也是调用方法,这个是他的方法名然后拼接在一起去取这个前面四个字节就是这个方法的选择器,我调调用智能合约的时候前面四个字节指定我要调用哪些方法,后面那些就是我传参,就是用这个编码出来的,虽然666其实只需要2个字节,但是他ABI编码的设计必须要32个字节,所以说这里可以看到他是有一定的浪费的。刚刚传到数据上来之后EVM的节点就会把智能合约的代码加在这个里面去执行,这个是我们刚刚设定的方法,传值进去进行修改。我们可以看一下EVM的字节码,刚刚看到这个是智能合约,这个智能合约最后通过编译器会编译到字节码,他基本上是从上到下一个一个执行的。我们要关注的是有两个可以关注的,上面这边60fe47b1这个就是我们选择器,我先判断一下是不是这个方法,是这个方法的话我跳到这边来去继续执行。所以说这个就是我们SED方法,最下面我们存储数据了,这点是一个关键点,我们刚刚是要存到value这个属性,这个属性他可以理解为建值,他直接是映射到0的位置,我用0去存储,最终把这个数据存储到内存里面去,所以说这是整个合约最关键的指令,存储数据。我们把刚刚说的那些东西从EVM字节码变成代码,这个是我发的时候他里面会带着一些数据,这个数据就是有一个指令,然后我取前面四个字节这个就是我要选择的方法,如果匹配了这个字节我就跳到tag这边,这边我要取参数,我也是从这里去取,我直接从四个字节以后取32个字节出来,这个就是我的参数,然后最后我把我读出来的参数存到这个位置,这个就是我们刚刚合约做的事情。我们下面分析一下sstore这个指令是在干啥,我们刚刚做的事情就是把数据存在第一个位置,L1这个位置,这个位置可以是无限多的,我存在这个位置之后他就会更改,其实最上面的这个就是我的版本号,我的版本号他是取这两边的,然后每一个节点都一样,这边的 hash 是由下面来更改的,如果我更改了这里面的数据,这边的 hash 会变,然后导致最上面的这个版本号也变了,所以说直接把这个 hash 当成版本号就可以了。如果我去改动这边的数据那通过这个路径去改变最上面的版本,下面这边是我存储数据的地方,我任何地方存储的数据都会影响到最上面的版本号,跟 top 的版本控制有点像。这边就是我们存储数据的数据结构,接下来我们看一下这个数据结构最后是如何融入到链里面去,这个就是咱们的区块链,里面会存着版本号,像说你去 GitHub 上面看一个项目,会有历史更新文档,会讲这个版本号修改了什么,这个版本号修改了什么。这个就数据结构类似于一个版本,每个版本会去记录这一个相对应的版本号是什么,然后从这个版本号可以取出相对版本所有的数据。再回顾一下刚才提到的,我们从前端通过RPC,这个是以太坊节点提供的服务,通过到RPC调用这个节点,调用里面的数据是由ABI编码结构化,然后传给合约,合约处理这个数据,最后这个合约是相对应字节码,然后由EVM解析,解析的结果通常就是存出去要么就是给别的帐号打钱,大概就是这样。区块链现状&未来区块链的现况大家体会一下,有点天下大乱的样子,区块链的新世界,这个是我们对未来的小展望,现在大概是5000万个用户在炒币,可能未来十年区块链会增长到10亿用户,像手机一样普及。如果这个事成真,每个人都会有一些数据的资产,变得像手机一样普及,这样的话我们要讨论。这就牵扯到扩容,扩容的话有几种方案我现在比较看好侧链和跨链的方案,如果我们要做产品那就考虑说这个到底怎么样做出好的产品给人用,所以说现在很多正在驱动的区块链产品很难用被各种喷;再一个是隐私的问题,区块链就是公开帐本,毫无隐私可言。智能合约发现了很多的问题,智能合约在安全上或者是在可用性上面他学习成本是相当高的,因为它是不同的平台,所用的编程语言也与其他语言不同,一切都要从头开始学。现在我们可以看到一些新一代的智能合约平台可以出现了,像WEB 3.0是在浏览器里面做一个接近底层的版本。所以说我们可以看到像以太坊生态圈里面truebit在尝试然后像rholang这类项目,它也解决了合约并发的问题,像我们公司量子我现在做一个虚拟机,这个是模拟了CPU的架构,我们希望说通过这个架构可以支持一些比较底层的开发去做智能合约,然后我们会比较关注像操作系统和虚拟机的分层,现在你去分析EBM的原理,它是字节码,里面会看到一些属于操作系统的的这个指令,它其实是建在虚拟机里面,这个也是一个技术上的一些问题。刚刚也提到了一个问题,就是智能合约到底能不能更新,应该怎么更新,这也是现在行业里面讨论的问题,我们会采用一个像DGP这种方式,我们通过投票去让用户选择这个智能合约应该怎么更新。欢迎大家跟我交流,谢谢大家。
2023年03月24日
7 阅读
0 评论
0 点赞
2023-03-24
java通过web3j开发以太坊过滤器filter和事件event
web3j过滤器提供以太坊网络发生的某些事件的通知,对java和安卓程序员来说很有用。在Ethereum以太坊中支持三类过滤器:块滤波器(Block filters) 未决交易过滤器(Pending transaction filters) 主题过滤器(Topic filters) 块过滤器和未决交易过滤器提供了在网络上创建新交易或块的通知。主题过滤器更灵活。允许根据提供的特定标准创建过滤器。不幸的是,除非你使用WebSocket连接到Geth,否则通过JSON-RPC API来处理过滤器是一个繁琐的过程,这里需要轮询以太坊客户端,以便了解HTTP和IPC所请求的实时同步特征,是否有任何新的更新到你的过滤器。此外,块和交易过滤器只提供交易或区块链hash值,因此需要进一步的请求来获得hash对应的实际交易或块。web3j的过滤器解决了这些问题,因此你有一个完全异步的基于事件的API来处理过滤器。它使用RXJava的可观测性Observables,它提供了与事件协同工作的一致API,这有助于通过功能组合将JSON-RPC调用链接在一起。注:Infura不支持过滤器。块和交易过滤器接收所有新块把它们添加到区块链(false参数指定我们只需要块就ok,而不需要嵌入交易):Subscription subscription = web3j.blockObservable(false).subscribe(block -> { ... }); 复制代码接收所有新交易,把它们添加到块链:Subscription subscription = web3j.transactionObservable().subscribe(tx -> { ... }); 复制代码接收所有待提交交易并提交到网络(即在它们被分组在一起之前):Subscription subscription = web3j.pendingTransactionObservable().subscribe(tx -> { ... }); 复制代码不再需要的时候取消订阅unsubscribe:subscription.unsubscribe(); 复制代码另外还提供了其他回调,它们简单地提供了块或交易hash,这些细节涉及Web3JRX接口。再现过滤器webjs还提供用于再现块和交易历史的过滤器。从区块链再现一系列块:Subscription subscription = web3j.replayBlocksObservable( <startBlockNumber>, <endBlockNumber>, <fullTxObjects>) .subscribe(block -> { ... }); 复制代码再现包含在一个块范围内的单个交易:Subscription subscription = web3j.replayTransactionsObservable( <startBlockNumber>, <endBlockNumber>) .subscribe(tx -> { ... }); 复制代码也可以获得Web3J再现最新的块,并在你看过后提供通知(通过提交Observable):Subscription subscription = web3j.catchUpToLatestBlockObservable( <startBlockNumber>, <fullTxObjects>, <onCompleteObservable>) .subscribe(block -> { ... }); 复制代码或者,也可以在你再现最新的块后,通知新创建的后续块:Subscription subscription = web3j.catchUpToLatestAndSubscribeToNewBlocksObservable( <startBlockNumber>, <fullTxObjects>) .subscribe(block -> { ... }); 复制代码如上所述,并包含在块内的交易:Subscription subscription = web3j.catchUpToLatestAndSubscribeToNewTransactionsObservable( <startBlockNumber>) .subscribe(tx -> { ... }); 复制代码所有上述过滤器都是通过Web3JRX接口导出的。主题过滤器和EVM事件主题过滤器捕获在网络中发生的以太坊虚拟机(EVM)事件的细节。这些事件是由智能合约创建的,并存储在与智能合约相关联的交易日志中。solidity文档提供了EVM事件的良好概述。使用EthFilter类型指定希望应用于过滤器的主题。这可以包括希望应用过滤器的智能合约的地址。你还可以提供特定的主题进行筛选。其中单个主题表示智能合约上的索引参数:EthFilter filter = new EthFilter(DefaultBlockParameterName.EARLIEST, DefaultBlockParameterName.LATEST, <contract-address>) [.addSingleTopic(...) | .addOptionalTopics(..., ...) | ...]; 复制代码然后可以使用类似于上面的块和交易过滤器的语法创建该过滤器:web3j.ethLogObservable(filter).subscribe(log -> { ... }); 复制代码过滤器主题只能引用索引的Solidity事件参数。不可能对非索引事件参数进行筛选。此外,对于可变长度数组类型(如字符串和字节)的任何索引事件参数,它们的值的Keccak-256 hash 存储在EVM日志上。不可能使用它们的全部值来存储或筛选。如果创建一个没有与之相关联的主题的过滤器实例,则在网络中发生的所有EVM事件都将由过滤器捕获。操作组合标注除了send()和sendAsync之外,所有JSON-RPC方法在web3j中都实现了支持observable()方法来创建可观察的异步执行请求。这使得将JSON-RPC调用组合成新的函数是非常容易和直接的。例如, blockObservable本身由许多单独的JSON-RPC调用组成:public Observable<EthBlock> blockObservable( boolean fullTransactionObjects, long pollingInterval){ return this.ethBlockHashObservable(pollingInterval) .flatMap(blockHash -> web3j.ethGetBlockByHash(blockHash, fullTransactionObjects).observable()); } 复制代码在这里,我们首先创建一个可观察的,它提供每个新创建的块的块哈希的通知。然后,我们使用flatMap调用ethGetBlockByHash,以获得完整的块细节,这是传递给可观察者的订阅服务器的细节。进一步的例子请参阅ObservableIT,进一步举例说明。对于使用手动筛选器API的演示,可以查看EventFilterIT。web3j教程,主要是针对java和android程序员进行区块链以太坊开发的web3j开发详解。 以太坊教程,主要介绍智能合约与dapp应用开发,适合入门。 以太坊开发,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。 php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和事件等内容。 原文出处:web3j教程:过滤器(Filters)和事件(Events)
2023年03月24日
7 阅读
0 评论
0 点赞
2023-03-24
以太坊智能合约gas如何估计?
以太坊如何估计估算计算gas?Etherscan上transaction info中有个gas used by txn,结果跟remix给的结果以及geth中getTransactionReceipt的gasUsed给的结果都会是一致的,可以直接用geth或是remix模拟估算gas cost。之前一直没把这个问题搞清楚,所以干脆做个试验看一下.remix浏览器下方有个可执行的log页面,可以detail以及debug,非常方便。有gas cost的地方有两个地方,transaction cost以及 execution cost,這两个有什么不同呢?可以參考一下他们的源码。简单说一下: transaction cost指的是将交易送至ethereum blockchain所耗费的cost,是基于data size的大小,部署合约时就是基于合约內容的大小. execution cost指的是虚拟机(VM)执行所需的cost,而在部署合约时,会去执行建構子以及一些初始化的工作.在这里做一个简单的合约试验:contract Test { bytes32 public tmp; function test( bytes32 input, uint num ) constant returns (bytes32){ bytes32 result = input; for(uint i = 0; i < num; i++) { result = sha3(result); } } function set(bytes32 input, uint num) { tmp = test(input, num); } } 复制代码如果直接呼叫constant function的话,因为是由本身节点去计算不会更改到区块链上的值,是不会消耗gas的,但是如果是由一个一般合约(非constant function call)去呼叫一个constant function的話,因为让矿工来计算constant function,所以会消耗gas.上面的简单合约中,我让test函数对第一个bytes32参数做sha3,第二个uint参数代表做几次loop,我分別对set函数和test函数带入10以及1000的参数,結果如下.set(“0x63d7db5ce060b288ecf5390594d5969bc1a206ceeb24df31cffcc8876df5e44b”, 10) transaction cost:30628execution cost:6988 复制代码set(“0x63d7db5ce060b288ecf5390594d5969bc1a206ceeb24df31cffcc8876df5e44b”, 1000) transaction cost:196022 execution cost:172318 复制代码test(“0x63d7db5ce060b288ecf5390594d5969bc1a206ceeb24df31cffcc8876df5e44b”, 10) transaction cost:25663 (cost only applies when called by a contract) execution cost:2023 (cost only applies when called by a contract) 复制代码test(“0x63d7db5ce060b288ecf5390594d5969bc1a206ceeb24df31cffcc8876df5e44b”, 1000) transaction cost:191057(cost only applies when called by a contract) execution cost:167353(cost only applies when called by a contract) 复制代码ps:用transaction cost减去execution cost的话1, 3得到23640,2, 4得到23704大致上就是这样一个过程.发现参数设定成1000时,也会造成transaction cost的提高.(初步猜想加上ps的计算:transaction cost中是已经包含了execution cost,一并计算在最后要支付给miner的fee,因为每个相减结果都差不多)另外geth的estimateGas的之所以会不太准确是因为一些不确定性的operator操作会不同,比如说,在一个contract中,若是blockhash的尾数是奇数,他就去执行会消耗大量gas的合约,反之则去执行hello world合约,所以他的gas cost很大的情况下是一半一半.所以永远要记得设定一个合理的gas limit来防止自己遭受恶意攻击.另外建议可以参考traceTransaction指令,可以看每一個opcode的gas cost. 为了要确认矿工处理transaction的情况,在ropsten testnet上做个简单的试验.首先在ropsten faucet上拿一点儿ether来玩,然后在metamask上送出交易,因为ropsten是模拟pow的环境,所以我相信应该会是正确的数字.重要的话再说一次结论:Etherscan上transaction info中有个gas used by txn,结果跟remix给的结果以及geth中getTransactionReceipt的gasUsed给的结果都会是一致的,以后可以直接用geth或是remix模拟估算gas cost.参考资料:以太坊DApp开发实战入门可以加微信拉以太坊技术群聊。
2023年03月24日
6 阅读
0 评论
0 点赞
2023-03-24
开发者的以太坊入门指南 | Jeth 以太坊系列线下活动
本次活动文字整理链接智能合约全栈介绍 - Howard | Jeth 第一期以太坊智能合约 + DApp 从入门到上线:来自前端工程师的实战指南 - 王仕军 | Jeth 第一期详解 ERC20 代币及众筹 - 熊丽兵 | Jeth 第一期录像回放(哔哩哔哩)智能合约全栈介绍(视频回放) - Howard | Jeth 第一期以太坊智能合约 + DApp 从入门到上线:来自前端工程师的实战指南(视频回放) - 王仕军 | Jeth 第一期详解 ERC20 代币及众筹(视频回放) - 熊丽兵 | Jeth 第一期圆桌讨论环节(视频回放) | 掘金 Jeth 以太坊线下活动第一期???? 介绍Jeth 以太坊系列线下活动:《开发者的以太坊入门指南》来了!Jeth 是由掘金技术社区主办,以太坊社区基金会、以太坊爱好者与 ConsenSys 协办,围绕以太坊技术开发主题的系列线下活动。每期 Jeth 会邀请以太坊开发领域的优秀技术团队和工程师在线下分享技术干货。旨在为开发者提供线下技术交流互动机会,帮助开发者成长。门票免费第一期的 Jeth 线下分享我们邀请到以太坊布道师叶倍宏、ArcBlock 区块链开发工程师王仕军,以及登链科技CTO 熊丽兵,来给开发者分享一下以太坊的全栈架构,智能合约+DApp 的实战开发,以及 ERC20 代币及众筹的相关技术点,帮助有编程经验的开发者能够快速上手以太坊开发。线上直播地址:IT大咖说???? 日程时间:2018/06/10 14:00 - 17:00地点:北京市海淀区中关村创业大街 3W咖啡???? 本期嘉宾Howard 叶倍宏 - 量子链 DApp 平台核心工程师Howard 是《Deep Dive Into Ethereum Virtual Machine》一书的作者,该书深度剖析了 Solidity 和以太坊的原理。目前,Howard 任 Qtum 量子链 DApp 平台核心工程师,负责开发工具和数据库。Howard 在创业界拥有10年的产品开发经验,并且对构建去中心化产品充满热情。分享主题:《DApp 应用平台从前端到后端》通过这个分享,简单的智能合约案例来了解以太坊的全栈架构。当用户点击一个按钮时,背后会发生哪些网路请求,怎样通过共识,智能合约如何被执行,最终又怎样将数据存储于区块链。王仕军 - 以太坊布道师爱折腾、爱分享的前端老司机,有过以下作品:掘金专栏作者:王仕军,11250 人关注; 掘金小册作者:《区块链开发入门:从 0 到 1 构建基于以太坊智能合约的 ICO DApp》,709 人购买; 微信公众号前端周刊创办和维护者,2600 订阅; 高质量技术视频教程作者:async/await、styled-components,网盘下载量近 3000 次。 分享主题:《以太坊智能合约 + DApp 从入门到上线:来自前端工程师的实战指南》站在资深前端工程师的视角,从实战角度出发,讲解如何构建自动化的以太坊智能合约工作流,如何搭建 DApp 开发框架,如何完成 DApp 的构建和部署。熊丽兵 - 登链科技 CTO北京航空航天大学硕士,先后加入创新工场及猎豹移动,全面负责数款千万级用户开发及管理工作,2014年作为技术合伙人参与创建酷吧时代科技。2016年重心投入区块链技术领域,目前在登链科技任CTO,是全网访问量最大的区块链技术博客《深入浅出区块链》博主,对底层公链技术,区块链技术落地都有深入研究。分享主题:《详解 ERC20 代币及众筹》如何实现一个简单的代币,Solidity ERC20 代币接口分析及实现,如何实现挖矿、锁定等功能;给代币实现一个发行机制(众筹),以及代币合约漏洞分析。???? 入群与讲师讨论需要入群请添加稀土君微信 xitujun 回复 以太坊或者扫下面的二维码入群:???? 抽奖奖品???? 关于主办方掘金: 一个帮助开发者成长的社区。???? 协办方Ethereum Community Fund: 以太坊社区基金会以太坊爱好者: 最好的以太坊中文技术社区ConsenSys: Harness the power of Ethereum❤️ 合作伙伴???? 独家视频直播逐步更新中……???? FAQ活动收费么?Jeth 以太坊线下分享活动都不收费,但是为了保证参会者为开发者,需要对报名进行审核。活动会有直播么?Jeth 的 合作伙伴 IT大咖说 负责线上直播,直播地址会在群内发布。什么时候在 XXX 城市举办?Jeth 线下活动规划表,更明确的时间请加稀土君微信 xitujun 获取最新通告:7月 杭州 8月 北京 9月 上海 10月 北京 11月 杭州 12月 北京 我能和 Jeth 合作么?掘金欢迎媒体和社区朋友加入以太坊生态建设,欢迎各种形式的合作。联系请加微信:xitujun特别鸣谢本活动页面的布局和排版参考设计师丁一的 Sketch Meetup 活动报名页面。
2023年03月24日
7 阅读
0 评论
0 点赞
2023-03-24
一文聊透 Solidity 语法:助你成为智能合约专家
我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第9篇文章,点击查看活动详情关于区块链和智能合约开发者的区别解释我发现很多人都表述不清楚区块链和智能合约。我认识几位程序员朋友,他们都自称是在做区块链开发,但实际上是在做智能合约的开发。大多数外行分不清楚区块链和智能合约我能理解,但是很多从事智能合约开发的程序员竟然也分不清楚,我不知道是不是表述问题还是理解问题。区块链是区块链,智能合约是智能合约,两者的关系就像是微信和微信小程序一样,一个是 App 开发,一个是小程序开发,根本不一样,不能混为一谈。据我了解,区块链的需求没那么多,特别是中国这个环境下。大多数区块链相关的程序员都是在做智能合约开发,而不是真的在开发区块链。区块链是可以用很多后端语言去开发的,比如用 Go、Node.js、Rust、Java 等。但是智能合约不可以随便选择编程语言,我这里讲的智能合约是指以太坊智能合约。目前它只能选择 Solidity、Vyper、YUL、YUL+ 和 Fe 这 5 种语言。其中 solidity 最受欢迎,大多数项目和开发者都是选择了 solidity。我们几乎可以说 solidity 是智能合约的首选编程语言。这篇文章会讲什么?这篇文章将会介绍我认为使用 Solidity 编写智能合约时 90% 以上的场景中能够用到的语法和特性。但是 Solidity 是一门完整的编程语言,想要把它彻底学明白,一篇文章肯定是不够的。因为很多语言都被写成了一厚厚地本书。不过通常写编程语言的书都会非常全体、体系化地介绍语言的全部,包括那些平时压根用不到的知识,或者一些已经落伍,语言设计上糟粕的部分。总体来说,通过一本厚厚的书来讲一门编程语言,多少是从研究的角度出发的,如果你只想快速用 Solidity 开发智能合约,不想把这门语言研究的这么透彻,那么本文很适合你。同时本文会拿 solidity 和一些面向对象的语言做对比,如果你完全不懂其他编程语言,那么本文不适合你。面向合约Solidity 的设计理念和面向对象编程语言很相似,不过 Solidity 是面相合约的编程语言,如果你有面向对象编程语言的开发经验,那么学习 Solidity 就没有那么难。Solidity 语言被设计为编写合约的语言,目前来说也只能写合约,所以它不像其他语言那样可以做很多事情。合约构成解读我们先来看一个最简单的合约构成,做一个整体的感受。// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract HelloWorld { address private owner; unit public state; modifier onlyOwner() { require(msg.sender == owner, "only owner"); _; } event StateChanged(unit state); constructor() public { owner = msg.sender; } function setState(uint _state) external onlyOwner { state = state; emit StateChanged(_state) } } 复制代码我简单解释下这个合约的代码,不会详细介绍。第 1 行是指定版本许可。第 2 行是指定使用的语言版本。第 4 行是声明一个名为 HelloWorld 的合约。第 5-6 行是状态变量,它们会永久存储在合约中。第 8 -11 行是函数修改器,它可以用在函数修饰符上,可以改变函数的行为。第 13 行是声明一个事件,事件可以被触发和监听。第 15-17 行是构造函数,在部署时会被调用。第 19-22 行是声明了一个名为 setState 的函数。版本solidity 有很多种版本,目前最新的版本是 8.x。但是在早期比较流行的是 5.x、6.x 这两个版本。solidity 的版本命名规范采用 。和其他大多数编程语言不同的是,solidity 的版本是直接写在源代码里的。在任意一个 sol 文件的最开始,都应该是版本代码。语法为:pragma solidity 0.8.0; 复制代码如果你用过 npm 的话,那这个版本语言一定不会陌生,因为 solidity 同样使用了 semver 版本规范。合约合约的概念有点像面向对象编程语言的类,属于一等公民。通过关键字 contract 创建。语法:contract MyContract { } 复制代码可以通过 new 关键字创建合约。new MyContract(); 复制代码继承面向对象的语言通常会使用 extends 关键字来继承,但是 solidity 没有这样做,它使用 is 来继承合约。contract MyContract1 { uint256 num = 2022; } contract MyContract2 is MyContract1 { } 复制代码子合约被部署时,会把所有父合约的代码一起打包,所以对父合约中函数的调用都属于内部调用。子合约可以隐式转换父合约,合约也可以显式转换为地址。address addr = address(c); 复制代码重写函数使用 override 关键字。父合约中支持重写的函数必须是 virtual 的。contract Parent { function fn() public virtual } contract Child is Parent { function fn() public override } 复制代码调用父合约中的方法,使用 super 关键字。contract Parent { function fn() public } contract Child is Parent { function fn2() public { super.fn(); } } 复制代码支持多重继承。contract Parent1 { function fn() public virtual } contract Parent2 { function fn() public virtual } contract Child is Parent1, Parent2 { function fn() public override(Parent1, Parent2) } 复制代码变量与基础类型变量是永久存储在合约中的值,通常用来记录业务信息。每个变量都需要声明类型,solidity 中的类型有如下几种:string:字符串类型 bool:布尔值,true/false。 uint:无符号整型,有 uint 和 uint8/16/32/64/128/256 几个类型。uint 是 uint256 的别名。 int:有符号整型,规则和 uint 一样。 bytes:定长字节数组。从 bytes1 到 bytes32,byte 是 bytes1 的别名。它和数组类似,通过下标获取元素,通过 length 获取成员数量。 address:地址类型。保存一个 20 字节的地址。 address payable:可支付的地址,有成员函数 transfer 和 send。 contract MyContract { string name = "" } 复制代码uint对于整型变量,我们可以通过 type(x).min 和 type(x).max 来获取某个类型的最大值和最小值。addressaddress payable 可以隐式转换到 address,但是 address 必须通过 payable(address) 这种方式显示转换。address 还可以显示转换为 uint160 和 bytes20。bytes 和 stringbytes 和 string 都是数组,而不是普通的值类型。bytes 和 byte[] 非常像,但是它在 calldata 和 memory 中会紧打包。紧打包的意思是将元素连续存储在一起,不会按照每 32 字节为一个单元进行存储。string 是变长 utf-8 编码的字节数组,和 bytes 不同的是它不可以用索引来访问。字符串没有操作函数,一般都是通过第三方 string 库来操作字符串。string 可以转换为 bytes,转换时是创建引用而不是创建拷贝。function stringToBytes() public pure returns (bytes memory) { string memory str = "hello"; bytes memory bts = bytes(str); return bts; } 复制代码由于 bytes 和 string 很相似,所以我们在使用它们时应该有对应的原则。对于任意长度的原始字节使用 bytes。 对于任意长度的 UTF-8 字符串使用 string。 当需要对字节数组长度进行限制时,应该使用 byte1-byte32 之间的具体类型。 合理使用 bytes 可以节省 Gas 费。变量修饰符我们也可以为变量指定访问修饰符。语法是 类型 访问修饰符(可选) 字段名。访问修饰符有三种:public:公开,外部可以访问,声明为 public 的话会自动生成 getter 函数。 internal:默认,只有合约自身和派生的合约可以访问。 private:只有合约自身可以访问。 solidity 中的变量与传统语言的变量有些不同。字符串的值默认不可以包含中文。如果要使用除了英文外的其他语言,必须加 unicode 前缀。 string name = unicode"小明"; 复制代码结构体使用关键字 struct 创建结构,有点类似 Go/C 的 struct,或者类似 TypeScript 中的 type/interface。struct User { string name; string password; uint8 age; bool state; } 复制代码初始化结构体和调用函数类似,参数的顺序和结构体的顺序保持一致。User user = User("章三", "123", 12, false); 复制代码访问某一个属性使用点号。user.name; 复制代码属性也可以直接赋值。user.name = "里斯"; 复制代码数组和 TypeScript 中的数组语法一致,语法是 type[]。User[] users; 复制代码访问数组元素,使用 array[index] 的方式。users[0]; 复制代码访问不存在的下标,会直接报错。在创建数组时可以声明长度,如果不声明,那就是可以动态调整大小的数组。uint256[10] nums; 复制代码数组具有 pop 和 push 方法,分别用于弹出一个元素和添加一个元素。但是它们不可以用在定长数组中。push 方法可以不传递参数,这时表示它添加一个该元素类型的零值。strs.push("1"); strs.pop(); 复制代码映射类似于很多语言中的 Map 结构。语法是 mapping(keyType => valueType)。mapping(address => User) userMapping; 复制代码key 的类型只允许是基本类型,不可以是复杂类型,比如合约、枚举、映射和结构体。value 的类型没有限制。访问 mapping 元素,使用 mapping[key] 的方式。userMapping[0x021221] 复制代码访问不存在的 key,会返回 value 类型的默认值。mapping 不可以作为公有函数的参数和返回值,只可以作为变量或者函数内的存储或者库函数的参数。声明为 public 的 mapping,会自动创建 getter 函数。KeyType 作为参数,ValueType 作为返回值。mapping 无法被遍历。不过有一些开源库用一种结构来实现了可遍历的 mapping。可以直接拿过来用。枚举枚举是创建用户自定义类型的一种方式。enum ActionChoices ActionChoices choice; 复制代码枚举可以和所有的整型显示相互转换,但是不能隐式转换。uint num = uint(choice); 复制代码从整型显示转换到枚举类型,会在运行时检查整数是否在枚举范围内,超过的话会导致异常。choice = ActionChoices(num); 复制代码枚举最少包含 1 个成员,最多可以包含 256 个成员。枚举默认值是第一个成员。枚举的数据表示和 C 语言是一样的,从 0 开始的无符号整数开始递增。构造函数部署合约时会由 EVM 自动调用构造函数,和常规的编程语言语法一致。contract MyContract { constructor () { } } 复制代码如果在构造函数中设置参数的话,那么在部署时需要传入对应参数的值。contract MyContract { constructor (uint256 initNum) { } } 复制代码构造函数不支持重载。如果一个合约没有构造函数,那么会采用默认构造函数,将所有变量初始化为类型对应的默认值。函数语法是 function(type param) [pure|view|payable] [returns(paramType)]可访问性标识符、状态标识符、函数修改器函数可以定义在合约之外,但是只能通过 internal 的形式访问。函数可以接受多个参数,也可以返回多个返回值。函数修改器可以放在函数声明中,具有修改函数行为的能力。modifier onlyOwner() { require(msg.sender == owner, "only owner"); _; } 复制代码常用的关键字有 require 和 _。require 有两个参数,第一个参数是一个 bool 值,如果为 false,那么就会触发错误,终止函数运行。第二个参数是当发生错误时的消息。_ 表示函数运行。使用函数修改器只需要在函数的修饰符部分添加修改器的名字即可,如果要添加多个修改器,使用空格隔开。function setState(uint _state) external onlyOwner m2 m3 { state = state; emit StateChanged(_state) } 复制代码函数修改器可以被继承。函数修饰符修饰符可以用在成员属性或者函数上,它决定了成员属性/函数的访问权限,共有 4 种:public:最大访问权限,任何人都可以调用。 private:只有合约内部可以调用,不可以被继承。 internal:子合约可以继承和调用。 external:外部可以调用,子合约可以继承和调用,当前合约不可以调用。 external 和 public 的函数是合约的成员变量,可以通过 fn.address 来获取地址,通过 .selector 来获取标识符,这也被称作函数选择器。函数调用函数分为内部函数与外部函数。内部函数只有在同一个合约内的函数可以内部调用,内部调用可以递归调用。函数调用在 EVM 中会被解释为简单地跳转,内存不会被清除。比如可以做斐波那契数列。contract MyContract { function fibonacci(uint256 n) public returns (uint256) { if (n == 1 || n == 2) { return 1; } return fibonacci(n - 2) + fibonacci(n - 1); } } 复制代码外部调用调用父合约的 external 方法和调用其他合约中的 external/public 方法,都属于外部调用。调用父合约的方法使用 this.fn();,调用外部合约的方法使用 contract.fn();。进行外部调用会通过消息调用,而不是简单跳转。接口与传统语言一样,使用关键字 interface。接口可以被合约继承。interface Token { function transfer(address recipient, uint amount) external; } contract MyToken is Token { function transfer(address recipient, uint amount) external override } 复制代码事件定义事件:event eventName(paramsType paramsName) 复制代码触发事件。emit eventName(params) 复制代码事件会被记录到区块链的 Log 中,区块链的 Log 分为索引和数据。我们可以指定最多 3 个参数为 indexed,表示它们可以被索引。前端可以通过 web3.js 来订阅和监听事件。事件也可以被继承。控制结构solidity 支持大多数传统编程语言的流程控制语句。比如 if、else、while、do、for、break、continue、return。但是不支持 goto 和 switch。solidity 支持 try/catch 做异常处理,但是只支持外部函数调用和合约创建调用。数据存储位置所有引用类型的数据(包括数组、结构体、mapping、合约等)都有三种存储位置。分别是:内存 memory:合约执行时的内存。 存储 storage:合约的永久存储。 调用数据 calldata:不可修改,函数的参数。和 memory 有些像,但和内存不在同一个位置。 直接声明在合约中的变量都会存储在 storage 中。声明为 external 的函数,参数必须存储在 calldata。在 storage 和 memory/calldata 之间进行复制,会创建独立的拷贝。memory 和 calldata 之间相互赋值不会创建拷贝,而是创建引用。storage 与本地 storage 之间的赋值也只会创建引用。contract MyContract { uint256[] arr1; // arr1 存储在 storage 中 // arr2 存储在 memory 中 function fn1(uint256[] memory arr2) public { // memory 赋值到 storage 中,创建拷贝 arr1 = arr2; // stoarge 赋值到 本地 storage 中,创建引用 uint256[] storage arr4 = arr1; // pop 会同时影响 arr1 arr4.pop(); // 清空 arr1,同时会影响 arr4 delete arr1; // storage 是静态分配内存,所以不可以直接从 memory 赋值到本地 storage 中 // arr4 = arr2; // 因为没有指向存储位置,所以无法重置指针 // delete arr4; // storage 之间传递引用 fn3(arr1); // storage 到 memory 会拷贝 fn4(arr1); } // arr3 存储在 calldata 中 function fn2(uint256[] calldata arr3) external function fn3(uint256[] storage arr5) internal pure function fn4(uint256[] memory arr6) public pure } 复制代码在使用数据时,要优先考虑放在 memory 和 calldata 中。因为 EVM 的执行空间有限。而且如果 storage 的占用很高,Gas 费也会很贵。单位solidity 中有两种单位。以太单位和时间单位。以太单位以太单位是以太坊独有的单位,在其他编程语言中没有这种单位。1 wei 等于 1。1 gwei 等于 1e9。1 ether = 1e18。用代码表示如下:assert(1 wei == 1); assert(1 gwei == 1e9); assert(1 ether == 1e18); 复制代码时间单位默认 1 等于 1 秒。solidity 支持以下时间单位:seconds:秒 minutes:分 hours:时 days:天 weeks:周 years:年,不推荐使用。 用代码表示如下:assert(1 seconds == 1); assert(1 minutes == 60 seconds); assert(1 hours == 60 minutes); assert(1 days == 24 hours); assert(1 weeks == 7 days); 复制代码错误处理与异常Solidity 使用状态恢复异常来处理错误。这种异常会撤销当前调用以及子调用中的状态变更,并且会向调用者标记错误。外部调用的异常可以被 try/catch 捕获。assertassert 用在我们认为不会出现错误的地方,它返回 Panic(uint256) 类型的错误。function buy(address payable addr) public { addr.transfer(1 ether); assert(addr.balance > 1 ether); } 复制代码requirerequire 通常用来条件判断,它会创建一个 Error(string) 类型的错误,或者是没有错误数据的错误。function buy(uint amount) public { require(amount < 1, "amount must be greater than 1"); } 复制代码revert可以用来标记错误并且退回当前调用。require 本身也会去调用 revert。function buy(uint amount) public { if(amount < 1) { revert(amount > 1, "amount must be greater than 1"); } } 复制代码区块和交易属性区块和交易属性都是以全局变量或者全局函数的形式存在的。我们可以直接访问它们。常见的属性如下:blockhash(uint blockNumber) returns (bytes32):获取指定区块的区块哈希,可用于最新的 256 个区块,不包含当前区块。 block.chainid:uint 类型,当前链的 id。 block.coinbase:address 类型,当前区块的矿工地址。 block.diffculty:uint 类型,当前区块的难度。 block.gaslimit:uint 类型,当前区块的 gas 限额。 block.number:uint 类型,当前区块号。 block.timestamp:uint 类型,从 unix epoch 到当前区块以秒计的时间戳。 gasleft() returns (uint256):剩余的 gas。 msg.data:bytes 类型,完整的 calldata。 msg.sender:address 类型,消息发送者(当前调用者)。 msg.sig:bytes4 类型,calldata 的前 4 个字节,也就是函数标识符。 msg.value:uint 类型,消息发送的 wei 数量。 tx.gasprice:uint 类型,当前交易的 gas 价格。 tx.origin:address payable 类型,交易发起者。 receive 和 fallbackreceive 是一个特殊的函数,一个合约可以包含最多一个 receive 函数。receive 没有 function 关键字,必须是 external payable 的。可以是 virtual 的,可以被重载,可以添加 modifier。我们给合约转账时,会去执行 receive 函数。如果转账时 receive 函数不存在,会去调用 fallback 函数。如果 fallback 函数也不存在,那么合约不可以通过正常转账来接受 ether。fallback 函数和 receive 类似,只能最多有一个 fallback 函数,必须是 external 的,可以是 virtual 的,可以被重载,可以添加 modifier。但 payable 是可选的。fallback 方法可以接受参数,也可以返回数据。如果调用某个合约的函数,但是这个函数不存在,会调用 fallback。contract MyContract { receive() external payable fallback() external } 复制代码我是代码与野兽,一位长期专注于 Web3 的探索者,同时也非常擅长 Web2.0 中的前后端技术。如果你对 Web3 感兴趣,可以关注我和我的专栏。我会持续更新更多 Web3 相关的高质量文章。
2023年03月24日
9 阅读
0 评论
0 点赞
2023-03-24
下一代的智能合约编程语言Move(五)
前言上一篇文章我们了解了Move语言的结构体与类型系统,这篇文章将会介绍Move中的所有权机制。所有权owershipMove虚拟机实现了类似Rust的所有权系统,有兴趣可以先了解一下Rust中的所有权系统。每一个变量都有自己的范围,当超出范围时,该范围内的变量也会被丢弃。我们已经在表达式的相关章节中看到了这种现象,记住一个变量只在自己的范围内生效。每一个包含变量的范围都是所有者,变量可以是在这个范围内通过let定义的,也可以是通过参数传递进这个范围的,在Move中只有函数能将变量传递进一个范围。每一个变量都只有一个拥有者,这意味着当一个变量被当作参数传递给一个函数时,这个函数就变成了这个变量新的所有者。script { use }::M; fun main() { // Module::T是一个结构体 let a: Module::T = Module::create(10); // 这时候变量a离开main函数的范围,进入M::value函数范围内 M::value(a); // 这时候main函数范围内已经没有a这个变量,编译会报错 M::value(a); } } 复制代码模块M的实现如下:module M { struct T public fun create(value: u8): T { T } //变量t传递给函数value,value函数拥有变量的所有权 public fun value(t: T): u8 { t.value } // 这时候函数范围结束,变量t被丢弃,不会再存在 } 复制代码move和copy首先我们需要了解Move VM是如何工作的,当我们传递参数给一个函数又发生了什么,在VM中有两个字节码指令,一个是MoveLoc,一个是CopyLoc,它们分别可以通过move和copy关键字使用。当一个变量被传递给其他函数时,它被使用MoveLoc移动,例子如下script { use }::M; fun main() { // Module::T是一个结构体 let a: Module::T = Module::create(10); // 这时候变量a离开main函数的范围,进入M::value函数范围内 M::value(move a); //变量a已经被废弃 } } 复制代码move关键字可以省略,这里仅仅为了说明如果想传递一个值给函数且想保存变量的值可以使用关键字copy。script { use }::M; fun main() { // Module::T是一个结构体 let a: Module::T = Module::create(10); M::value(copy a); //变量a依然存在 } } 复制代码以上我们通过copy关键字避免了变量被废弃,但是copy会增加内存使用,当copy非常大的数据时代价很大,在区块链中每个字节都会影响执行的代价,为了避免过大的额外开销,可以使用引用。引用很多编程语言都实现了引用,引用是变量的链接,通过引用可以将变量传递给程序其他部分而不用传递变量的值。引用(通过&)可以不需要所有权就可以获取到一个变量module M { struct T //传递一个引用而不是传递一个值 public fun value(t: &T): u8 { t.value } } 复制代码不可变的引用只能读取变量的值,不能改变变量的值,可变的引用可以读写变量的值。module M { struct T //返回一个非引用类型的值 public fun create(value: u8): { T } //不可变的引用只允许读 public fun value(t: &T): u8 { t.value } // 可变引用允许读写值 public fun change(t: &mut T, value: u8) { t.value = value; } } 复制代码Borrow检查Move中通过Borrow检查来控制程序中引用的使用,这样有助于避免出错。module Borrow { struct B struct A // 创建一个含有B的A public fun create(value: u64): A { A } } // 获得B的可变引用 public fun ref_from_mut_a(a: &mut A): &mut B { &mut a.b } // 改变B public fun change_b(b: &mut B, value: u64) { b.value = value; } } 复制代码script { use }::Borrow; fun main() { // 创建一个A let a = Borrow::create(0); // 通过A获取B的可变引用 let mut_a = &mut a; let mut_b = Borrow::ref_from_mut_a(mut_a); // 改变B Borrow::change_b(mut_b, 100000); // 获取另一个A的可变引用 let _ = Borrow::ref_from_mut_a(mut_a); } } 复制代码上面代码可以成功编译运行,不会报错。这里究竟发生了什么呢?首先,我们使用 A 的可变引用(&mut A)来获取对其内部 struct B 的可变引用(&mut B)。然后我们改变 B。然后可以再次通过 &mut A 获取对 B 的可变引用。但是,如果我们交换最后两个表达式,即首先尝试创建新的 &mut A,而 &mut B 仍然存在,会出现什么情况呢?let mut_a = &mut a; let mut_b = Borrow::ref_from_mut_a(mut_a); let _ = Borrow::ref_from_mut_a(mut_a); Borrow::change_b(mut_b, 100000); 复制代码此时编译器会报错 ┌── /scripts/script.move:10:17 ─── │ 10 │ let _ = Borrow::ref_from_mut_a(mut_a); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid usage of reference as function argument. Cannot transfer a mutable reference that is being borrowed · 8 │ let mut_b = Borrow::ref_from_mut_a(mut_a); │ ----------------------------- It is still being mutably borrowed by this reference │ 复制代码该代码不会编译成功。为什么?因为 &mut A 已经被 &mut B 借用。如果我们再将其作为参数传递,那么我们将陷入一种奇怪的情况,A 可以被更改,但 A 同时又被引用。 结论如下编译器通过所谓的"借用检查"(最初是Rust语言的概念)来防止上面这些错误。编译器通过建立"借用图",不允许被借用的值被"move"。这就是 Move 在区块链中如此安全的原因之一。 可以从引用创建新的引用,老的引用将被新引用"借用"。可变引用可以创建可变或者不可变引用,而不可变引用只能创建不可变引用。 当一个值被引用时,就无法"move"它了,因为其它值对它有依赖。 取值可以通过取值运算*来获取引用所指向的值。值运算实际上是产生了一个副本,要确保这个值具有 Copy ability。module M { struct T has copy // value t here is of reference type public fun deref(t: &T): T { *t } } 复制代码取值运算不会将原始值 move 到当前作用域,实际上只是生成了一个副本有一个技巧用来复制一个结构体的字段:就是使用*&,引用并取值。我们来看一个例子module M { struct H has copy struct T // ... // we can do it even from immutable reference! public fun copy_inner(t: &T): H { *&t.inner } } 复制代码基本类型基本类型非常简单,它们不需要作为引用传递,缺省会被复制。当基本类型的值被传给函数时,相当于使用了copy关键字,传递进函数的是它们的副本。当然你可以使用move关键字强制不产生副本,但是由于基本类型的大小很小,复制它们其实开销很小,甚至比通过引用或者"move"传递它们开销更小。最后这篇文章主要介绍了Move中的所有权,更多文章可以关注公众号QStack。
2023年03月24日
5 阅读
0 评论
0 点赞
1
...
3
4
5
...
11