jLab:郭宇的Web3开发最佳实践阅读笔记-2

jLab:郭宇的最佳实践阅读笔记-2

概述

阅读大牛郭宇的文章

guoyu.mirror.xyz/RD-xkpoxasAU7x5MIJmiCX4gll.. 笔记不是抄写,会有自己的理解和裁剪,以及额外的建议。

第一篇笔记见这里:here

Web3处于早期,建设者都在快速奔跑,各个基础设施也在快速迭代,因此本Blog适用于3-6个月内的情况,请紧跟技术和行业进步趋势,从而获得最佳实践!

当然,架构原理不会突变,例如Relay基本不会消失,但可能内嵌于浏览器了。

普通帐号登录DApp

补充一句,如果某个阶段,为了用户体验,把普通Web2帐号和Web3绑定在服务器,钱包托管于服务器,是可行的,但也带来了中心化的风险(你跑路或者作恶,用户损失)。

这方面有 Web3AuthMagicLink ,对于要横跨Web2和Web3并提供良好体验的,可参考。

FaaS选择

这里郭宇首推了Firebase,供成本低廉,服务完善和稳定的健壮 API, Google的服务。

如果较为简单,可以参考Vercel或者Netlify,你可以依赖一个全功能 FaaS,也可以将自己为数不多的「链下状态」储存在 headless CMS 当中。

自己搭建FaaS的话,就是Supabase(嗯,和Firebase对着干?),开源免费,这个看你应用的的规模和阶段了。

Smart Contract

合约的本质特征

Transaction:所有合约代码,都是事务执行,要么成功,要么失败(回滚状态),不会有其他结果。因此不要有其他尝试,例如自动重试,错误干预,容易造成事务收到干扰。

错误处理require(condition, ERR_MESSAGE) 或者 revert customError(),都会跳出tx交易执行,事务报错回滚,看实际需要,合约抛出错误后,前端可以通过event等捕获错误。

运行成本: 这个是和传统编程模式大不同,因为是世界计算机,所以你的合约涉及的存和取,都需要消耗gas(郭宇说口误了,只说存储,实际合约内读取变量实际也会消耗gas)。

常规来说, storage最贵, memory其次, calldata没有,view和pure的是纯读取,不消耗。而所有的存,例如存储变量,更改变量值后再次存储,都会消耗gas。

详细的自己看Solidity手册吧。

这里给了一个链上白名单方案合约实现JavaScript 实现,但实际上,我个人反而推荐用中心化实现,简单,可控,只要注意好安全,对于白名单场景,较为适用。

不可变:合约部署后,不可更改,这也是可信的一个原因,规则不会因为某个人而变更。当然,如果有需要变化的,就用到了升级方案,一般用Proxy部署(openzepplin提供)。

可见性: 合约是开源(一般都会verify后开源)的,并且所有的变量状态,存储的数据,都是在链上可见的(虽然变成可读数据需要点技术门槛),因此,任何未加密的,或者敏感的数据,不要上链,这里扩展的解决方案,就是ZK了(证明你有钱但是不透露你的存款金额等类似场景)。

权限控制:因为合约是可以存储资产的tx记录,因此合约的权限一定要管理好,治理合约是群体性的决策,较少直接涉及资产,其他的建议都必须要有 Ownable 来进行基本的权限配置,复杂合约可以使用 AccessControl ,感谢OpenZeppellin!

安全性:如果你负责机构的合约,那你一定要注意安全,如果你立志做个黑客,同样要研究安全,嗯,参考这里ConsenSys 编写的合约代码安全最佳实践指南,拥有10%ETH的以太坊天使投资人,以太坊生态核心建设者。一个漏洞,几亿美元没了。

合约依赖与调用

依赖引用import 引入依赖的外部合约,抽象合约,Interface 或者库,多数时候直接npm安装openzeppelin。

调用:使用provider来获取合约地址,同时获得ABI来有直接的函数接口,就可以调用了。注意:调用同样是事务操作。合约调用合约,同样消耗gas,调用第三方失败,gas无退回,因此注意接口和测试。调试本地合约调用某个生产环境的线上合约,可以使用 fork 的方式将某个高度的区块链下载到本地运行,套路看后面。

ABI:Application Binary Interface,ABI,通常需要编译后的同名JSON文件引用,调用;接口文件通常同名文件前加I。接口可以让任意方调用你的合约,如果没在接口定义,那通过ABI也可以被调用。通过JSON文件调用,可以是全部,也可以是部分,只要符合原来定义,因为直接调用二进制机器码难度太高,所以通过JSON的 human-readable ABI,再通过Contract Address,调用合约。

calldatas[0] = abi.encodeWithSignature(
  'execTransfer(uint256,address,address[],uint256[])',
   memberId,
   memberWallet,
   payroll.tokens.addresses,
   payroll.tokens.amounts
);

合约事件:合约是一个封闭的事务,所有外部获取信息通过event订阅,原理是事件通过向日志系统中写入特定数据的方式来实现函数修改的记录,实质是状态更改记录。通过监听和查询的方式,列出一个合约注册的所有事件,实现对函数异步结果的查询和前端 UI 状态变更,也可以伪同步。

以某个单一合约为 key 来进行索引,然后可以指定个三个index最多:

event ModuleProposalCreated(
  address indexed module,
  bytes32 indexed id,
  address indexed sender,
  uint256 timestamp
);

过于复杂的查询,使用subgraph或者webhook来进行。

创建合约:合约也可以创建合约,例如合约工厂。当然,也可以通过外部调用者(钱包账户)向 0x00 地址发送合约创建操作来新建网络上的合约。创建合约会消耗大量gas,使用特定工具在创建合约前预估并计算费用,后面有套路。