Cointime

扫码下载App
iOS & Android

超越边界:深入探索Scroll zkEVM的智能合约开发

个人专家

作者:Priyank Gupta. 编译:Cointime:QDD.

我撰写了这篇文章,作为对Scroll zkEVM的技术介绍,供希望尝试该网络的智能合约开发者阅读。

在这篇文章中,我们将:

1. 了解Scroll zkEVM以及如何开始使用它的Alpha测试网络。

2. 使用Foundry搭建开发环境,并编写一个智能合约,在Alpha测试网络上基于伪随机数生成函数分发ETH。

3. 调整智能合约以符合Alpha测试网络所需的精确规格。

4. 借助Solidity脚本部署我们的智能合约。

5. 通过Foundry的命令行在Alpha测试网络上验证我们的智能合约。

通过本文,你将学会如何使用Foundry将你的智能合约部署到Scroll的Alpha测试网络中。

什么是Scroll zkEVM?

Scroll

Scroll zkEVM是一个即将推出的第二层区块链解决方案,专为增加以太坊的可扩展性而设计。

Scroll和类似的平台(如Polygon zkEVM)的区别在于使用零知识证明(ZK证明)。ZK证明通过将大量交易捆绑在一起,并一次性写入以太坊,显著降低与在以太坊上逐个处理每个交易相比的交易费用。

像Scroll这样的zkEVM的最好之处在于:智能合约开发者无需理解底层的ZK技术,即可在更便宜和可扩展的解决方案上部署与EVM兼容的智能合约。

先决条件

1. 确保你的钱包中有一些Goerli ETH。是的,我知道Goerli已经弃用了,但你现在还需要它。

2. 对区块链有基本的理解,并具备一些Solidity的经验。

3. 如果你之前没有使用过Foundry,请建议你查看我之前为一个研讨会制作的这个Github仓库的README。

4. 不需要看整个视频,如果你以前没有使用过Foundry,请快速浏览一下README文件。

首先要做的事情:桥接!

在不涉及太多ZK特定的内容的情况下,请知道Scroll网络上的ETH是与以太坊主网上的ETH相对应的。镜像意味着Scroll团队已在Scroll网络(目前是Alpha测试网络)和对应的ETH网络(目前是Goerli测试网络)上部署了桥接智能合约。

因此,要在Alpha测试网络上获得一些ETH以支付燃气费,你需要将一些Goerli ETH存入Goerli上的桥接合约中。

镜像也可以反向操作。

开始操作:

1. 访问Scroll的UI界面,该界面允许你与桥接合约进行交互。

2. 将你的Metamask钱包连接到网页。

3. 确保你将ETH发送到Scroll网络,而不是相反。按照界面上的提示(它很直观),确认桥接。

确认后,你可能需要等待30-45分钟,然后才能在Alpha测试网络上收到ETH。所以你需要耐心等待一段时间。

一旦在Alpha测试网络上有了一些ETH,你就可以开始了!

初始化一个Foundry项目

Foundry是最新的智能合约开发框架之一,越来越受欢迎。

要安装Foundry,请参考以下命令或查看Foundry-book

运行以下命令下载foundryup

curl -L https://foundry.paradigm.xyz | bash

然后重新启动终端,然后通过运行以下命令安装Foundry:

foundryup

安装完成后,在新的目录中打开一个新的终端。你可以使用以下命令初始化一个新的Foundry项目:

forge init

一些建议:

1. 所有的智能合约默认都创建在src目录下。

2. forge install命令可以在lib目录下安装新的git子模块。

3. 默认情况下,所有的测试合约都在test目录下定义,并且通常以.t.sol为后缀,这意味着一个名为hello.sol的合约文件将有一个名为hello.t.sol的文件,按照惯例。你可以使用forge test命令执行所有的测试文件。

4. 你可以通过配置foundry.toml文件来控制Foundry的行为。这个Github页面提供了对toml文件的完整参考。我还编写了一个包含默认值列表的gist。

5. script目录包含了Foundry项目的部署和可执行脚本。

每当你对代码进行任何更改时,你可以使用以下命令编译所有智能合约:

forge build

操作码和字节码:快速入门

在编写智能合约之前,让我们了解一些概念,这些概念有助于理解整个情况。

对于那些不知道的人,EVM不执行Solidity或任何其他智能合约开发语言。EVM甚至不知道这些语言的存在,也不关心。这对Scroll zkEVM也是适用的。

发生的情况是,任何智能合约代码都被分解成一组可由EVM执行的指令集,称为字节码,这是EVM实际执行的内容。

好了,但是操作码是什么,为什么我们要关心呢?还记得我说过字节码是一个“指令集”吗?

嗯,每个操作码都是一条指令,与其他操作码结合在一起,构成任何智能合约的字节码。作为区块链开发者,你必须处理操作码,这是最低级别的计算。

例如,如果你在Solidity中使用了block.number,你实际上调用了NUMBER操作码。关于EVM支持的所有操作码的完整参考(不要与Scroll的zkEVM混淆)可以在以太坊基金会的网站上找到。

让我用一个例子清楚地解释整个概念的意义。

看一下这个简单的智能合约:

只是一个返回两个数字之和的函数。等效的操作码指令会是什么样子?请查看我创建的这个Gist。你还可以在Etherscan上查看此合约,以验证字节码和操作码。

这一节可能看起来像一个不相关的插曲,但相信我,在接下来的章节中,这将更加清晰明了。

编写智能合约

最后,让我们完成它。

进入src目录并创建一个名为ScrollTutorial.sol的新文件。

将以下代码粘贴到其中:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

contract Dispenser {

   
//mapping to keep track of addresses that have already withdrawn

   
mapping(address => bool) public hasWithdrawn;

   
function withdraw() public {

       
require(

         
  !hasWithdrawn[msg.sender],

           
"You have already withdrawn once, sorry!. Try again from a
different address!"

       
);

       
require(

           
address(this).balance > 0.5 ether,

           
"Not enough funds in the contract right now"

        );

       
uint256 randomNumber = uint256(

           
keccak256(

                abi.encodePacked(

                    blockhash(block.number -
100),

                    block.prevrandao,

                    block.coinbase

                )

       
    )

       
) % 2;

       
// Check that the random number is even

       
require(

           
randomNumber == 0,

           
"Sorry but the hash generated was an odd number, try again from a
different address!"

       
);

       
// Set the hasWithdrawn flag for this address to true

       
hasWithdrawn[msg.sender] = true;

       
// Transfer 0.5 ether to the address

       
payable(msg.sender).transfer(0.5 ether);

    }

   
function deposit() public payable {}

}

这是一个简单直观的智能合约。让我们快速理解一下代码:

1. 我们希望每个地址只能提取0.5 ETH一次;mapping hasWithdrawn 用于跟踪已成功提取ETH的所有地址。

2. deposit函数是一个简单的可支付函数,允许我们的合约接收ETH。

3. withdraw函数是整个魔术发生的地方。

让我们更详细地看一下withdraw函数。

1. 函数开头的前两个require语句确保每个地址只能提取一次,并且合约中有足够的ETH进行交易。

2. randomNumber变量是我们使用几个实时值生成的伪随机数。我们很快会详细了解它们。

3. abi.encodePacked函数接收所有的参数,并将它们连接成一个单独的字节字符串。

4. keccak256函数将生成接收到的输入的Keccak-256哈希值。我们对该函数输出进行取模运算,以检查生成的哈希值是否为偶数。

5. 接下来,如果哈希值为偶数,我们支付ETH并更新映射关系。

要编译智能合约,请在终端中运行以下命令:

forge build

专业提示#1:PREVRENDAO是一个相对较新的操作码,取代了DIFFICULTY操作码,只支持Solidity版本0.8.18及以上。请确保相应地配置Solidity版本。

我们的代码存在的问题

这段代码远远不够适用于生产环境,但让我们首先讨论一个更基本的问题。

回想一下,我提供了一个EVM支持的操作码列表。

不幸的是,截至目前,Scroll的zkEVM并不支持所有这些操作码。

它支持其中大部分,但如果你要为zkEVM(任何zkEVM)开发智能合约,你必须了解网络和EVM之间在操作码支持方面的差异。

请重新检查代码并查看我们用于生成随机数的值。让我们来看看在我们的智能合约中将无法正常工作的部分:

操作码 Solidity等效 行为差异
BLOCKHASH blockHash(BlockNum) 我们使用最新区块前100个区块的哈希作为随机数哈希的一部分。然而,Scroll zkEVM不支持获取如此旧的区块的哈希。如果区块号超出了支持的范围,将返回0。以太坊支持:[最新-1] - [最新-256] Scroll zkEVM支持:[最新-1] 
PREVRANDAO block.prevrendao 将PREVRENDAO视为一个伪随机源数字,你可以在此处阅读更多信息。以太坊行为:返回一个伪随机数 Scroll zkEVM行为:返回0,没有随机性 
COINBASE block.coinbase EVM行为:返回区块的验证者地址。由于ETH验证者是随机选择的,该数字是伪随机的。Scroll行为:目前仅返回一个地址,没有随机性

因此,我们用来生成randomNumber的这三个值在EVM上具有伪随机行为。然而,它们要么无法工作,要么返回一个常量值,使它们对我们的目的毫无用处。

但说实话,即使Scroll支持所有这些操作码,这仍然不是一个适用于生产的合约。没有一个严肃的智能合约依赖将区块的值进行哈希处理来生成随机数。要部署利用随机性的智能合约,请查看Chainlink的VRF服务

调整智能合约

由于Chainlink不支持Scroll,我们将退而求其次使用伪随机性。

这次,我们将以两种方式稍微更改withdraw函数:

1. 我们现在允许用户在调用withdraw函数时传递一个参数。我们将使用该参数作为randomNumber的哈希的一部分,以便用户对生成的哈希有一定的控制。

2. 我们将其他两个值替换为前一个区块的区块哈希和当前区块的时间戳。这并不是一个很好的随机性来源,但目前这是我所知道的最好的解决方案。

如果你有更好的解决方案,请随时告诉我。

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

contract Dispenser {

   
//mapping to keep track of addresses that have already withdrawn

   
mapping(address => bool) public hasWithdrawn;

   
function withdraw(uint seedValue) public {

       
require(

           
!hasWithdrawn[msg.sender],

           
"You have already withdrawn once, sorry!. Try again from a
different address!"

     
  );

       
require(

           
address(this).balance > 0.5 ether,

           
"Not enough funds in the contract right now"

       
);

       
uint256 randomNumber = uint256(

           
keccak256(

                abi.encodePacked(

                    blockhash(block.number -
1),

                    block.timestamp,

                    seedValue

                )

           
)

       
) % 2;

       
// Check that the random number is even

       
require(

           
randomNumber == 0,

           
"Sorry but the hash generated was an odd number."

       
);

       
// Set the hasWithdrawn flag for this address to true

       
hasWithdrawn[msg.sender] = true;

       
// Transfer 0.5 ether to the address

       
payable(msg.sender).transfer(0.5 ether);

    }

   
function deposit() public payable {}

}

部署和验证我们的智能合约

我们需要向Foundry传递一些值来部署我们的智能合约。我们可以直接在命令行中部署合约时进行配置,但在.env文件中进行配置更加方便。在你的项目目录中创建一个新的.env文件。

我们需要在env文件中传递两个值:

1. RPC_URL:Foundry将需要一个RPC URL来连接Alpha测试网络。我们可以从Scroll的文档中获取Alpha测试网络的公共RPC URL。

2. PRIVATE_KEY:我们将需要一个在Alpha测试网络上具有一些ETH的钱包的私钥来签署交易。你的env文件应该如下所示:

RPC_URL=https://alpha-rpc.scroll.io/l2

PRIVATE_KEY=1dh12j1XXXXXXXXXXXXXh1pqdfjnma91k

保存你的env文件。运行以下命令将这些变量加载到你的终端中:

source .env

现在,我们已经安全地设置了所有敏感信息,让我们编写一个脚本来部署我们的合约。你也可以使用forge create命令直接从命令行部署,但我觉得脚本更加优雅。

在script目录中创建一个名为ScrollTutorial.s.sol的文件。在文件中,粘贴以下代码:

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.13;

import "forge-std/Script.sol";

import {Dispenser} from "../src/ScrollTutorial.sol";

contract MyScript is Script {

   
function run() external {

       
uint256 PrivateKey = vm.envUint("PRIVATE_KEY");

       
vm.startBroadcast(PrivateKey);

       
Dispenser dispenser = new Dispenser();

       
vm.stopBroadcast();

    }

}

让我们解释一下这里发生了什么:

1. 在前两行中,我们从forge标准库导入了脚本工具和我们的智能合约。

2. forge-std库提供了vm.sol接口,以使用有价值的秘籍。envUint函数可用于访问我们在env文件中的私钥。

3. 在startBroadcast和stopBroadcast之间的任何事务都可以发送到链上。在我们的情况下,我们需要创建一个新的智能合约实例。

保存脚本文件。现在我们准备好部署了。在终端中运行以下命令:

forge script script/ScrollTutorial.s.sol:MyScript --rpc-url $RPC_URL --broadcast --legacy -vvvv

专业提示#2:Foundry允许我们使用“-v”标志来配置命令行的详细程度。我倾向于大多数时候使用最高详细程度。你可以在此处阅读更多信息。

专业提示#3:对于Scroll和zkSync等zkEVM,你可能需要通过Foundry传递“--legacy”标志来部署合约,因为它们通常不支持EIP-1559。

Scroll的文档提供了一个Blockscout资源管理器的API URL,可以用来验证我们的合约。在终端中运行以下命令来验证你的合约:

forge verify-contract <CONTRACT_ADDRESS> src/ScrollTutorial.sol:Dispenser --chain-id 534353 --verifier-url https://blockscout.scroll.io/api/ --verifier blockscout

注意:Blockscout API似乎已经关闭,并且其行为被Scroll文档确认为不一致。我无法从命令行验证我的合约,你也可能遇到同样的问题。不过,你可以通过Blockscout的UI进行验证。

合约验证完成后,你可以在Blockscout的UI中与其进行交互。

结论

在本文中,我们介绍了Scroll zkEVM以及作为智能合约开发人员如何通过最小配置将智能合约部署到该网络。Scroll Mainnet即将推出,现在是开始使用这项出色新技术的最佳时机。

评论

所有评论

推荐阅读

  • Tether:将冻结与委内瑞拉受制裁实体相关的地址

    Tether 的一位发言人,在有报道称委内瑞拉国营石油公司正在使用其稳定币绕过制裁后,该公司仍然致力于停止与外国资产控制办公室(OFAC)制裁实体相关交易,该发言人称:“Tether 尊重 OFAC SDN 名单,并致力于确保制裁地址立即被冻结。”

  • 4月23日晚间要闻速递

    1. BTC突破67000美元

  • BTC突破67000美元

    行情显示,BTC突破67000美元,现报67018.39美元,日内涨幅达到0.85%,行情波动较大,请做好风险控制。

  • 中国拟修改反洗钱法,设立反洗钱监测分析机构

    反洗钱法修订草案已于 4 月 23 日提请十四届全国人大常委会初次审议。修订草案共 7 章 62 条,规定国务院反洗钱行政主管部门设立反洗钱监测分析机构,开展反洗钱资金监测,负责接受、分析大额交易和可疑交易报告等。在完善反洗钱义务规定方面,修订草案规定金融机构反洗钱义务主要包括:建立健全反洗钱内控制度并有效实施;开展客户尽职调查,了解客户身份、交易背景和风险状况;保存客户身份资料和交易记录;有效执行大额交易报告制度和可疑交易报告制度。此外,修订草案还规定单位和个人不得从事洗钱活动或者为洗钱活动提供便利,应当配合金融机构和特定非金融机构依法开展的客户尽职调查等。

  • 去中心化AI平台Prime Intellect完成550万美元种子轮融资

    去中心化AI平台Prime Intellect宣布完成550万美元种子轮融资,由 Distributed Global 和 CoinFund 共同领投,Compound 也参与其中。 这笔资金将用于构建计算平台,实现跨实例的去中心化培训,并通过贡献计算、代码、数据、资本或专业知识来实现​​人工智能模型的共同所有权。

  • 加密钱包初创公司Turnkey完成1500万美元A轮融资,Lightspeed Faction和Galaxy Ventures领投

    加密钱包初创公司 Turnkey 宣布完成 1500 万美元 A 轮融资,Lightspeed Faction 和 Galaxy Ventures 领投,红杉资本、Coinbase Ventures、Alchemy、Figment Capital 和 Mirana Ventures 参投。(CoinDesk)

  • Magpie Protocol:发现合约存在漏洞,建议用户尽快取消授权

    跨链基础设施 Magpie Protocol 在 X 平台表示合约存在漏洞,并督促曾对授权过其合约且钱包内仍持有资金的用户尽快取消各条链上的相关合约授权。

  • 慢雾揭秘新型骗局:恶意修改RPC节点链接骗取资产

    慢雾安全团队发文揭露一种新型的加密货币骗局。该骗局利用修改以太坊节点的远程过程调用(RPC)功能进行资金诈骗。骗子的具体作恶流程如下:诱导用户下载 imToken 钱包,并用 1 USDT 以及少量 ETH 为诱饵来取得用户的信任。然后,骗子引导用户更改其 ETH 的 RPC 网址到骗子控制的节点,该节点利用 Tenderly 的 Fork 功能造假用户的 USDT 余额。用户在看到错误的余额后,可能会进行转账尝试,而此时骗子已消失。据慢雾科技报道,此类骗局利用了用户的信任和疏忽,导致资产损失。慢雾安全团队提醒用户在交易时应保持警惕,避免使用不可信的 RPC 节点。

  • 菲律宾证交会:将币安应用从谷歌和苹果应用商店中移除

    菲律宾证券交易委员会表示,我们采取了行动将币安应用从谷歌和苹果应用商店中移除。

  • Kaiko:币安的比特币交易份额降至55.3%

    根据研究机构Kaiko的数据,过去一年,币安的比特币交易份额从81.3%降至55.3%。山寨币的交易份额也从58%降至50.5%。 Bybit和OKX等平台已经在亚洲等地区扩大了业务版图,而币安则一直在应对法律问题的影响。过去一年,Bybit在非美国比特币交易中的份额从2%上升到9.3%。目前OKX的份额为7.3%,高于之前的3%。