您正在查看: 2023年3月

用Hardhat在zkEVM上部署一个动态NFT


使用Hardhat在zkEVM Testnet上部署一个Onchain的SVG动画NFT

什么是zkEVM

简而言之,Polygon zkEVM是一个二级扩容解决方案,旨在实现EVM-equivalence(等效),并允许你使用现有的Solidity代码更便宜、更快、更安全地部署合约到它。

想进一步深入了解它吗?请看我的另一篇文章:如何将ERC20代币合约部署到zkEVM Testnet

准备网络与Token

建议使用Polygon zkEVM Bridge 站点,将网络添加到我们的钱包,并将 Goerli Token 桥接到 zkEVM Testnet:

环境要求

在我们开始之前,请确保你的电脑上已经安装了以下依赖:NVM或Node v18.15.0,并且已经配置了钱包,并且goerli 测试网代币被桥接到了zkEVM Testnet。如果你想了解如何做到这一点,请查看我的另一篇文章如何将ERC20合约部署到zkEVM Testnet

用Hardhat部署合约

让我们建立一个ERC721 NFT,并通过Hardhat将其部署到zkEVM。

为了确保我们从新开始,我们将利用Hardhat模板生成一个基本的TypeScript项目。

# Create our project folder
mkdir zkevm-erc721-hardhat;
cd zkevm-erc721-hardhat;

npx hardhat;

# Expected Output:
# Ok to proceed? (y) y
# 888    888                      888 888               888
# 888    888                      888 888               888
# 888    888                      888 888               888
# 8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
# 888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
# 888    888 .d888888 888    888  888 888  888 .d888888 888
# 888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
# 888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888
#
#  Welcome to Hardhat v2.13.0 
#
# ? What do you want to do? …
#   Create a JavaScript project
# ❯ Create a TypeScript project
#   Create an empty hardhat.config.js
#   Quit
#
# ✔ What do you want to do? · Create a TypeScript project
# ✔ Hardhat project root: · /path/to/zkevm-erc721-hardhat
# ✔ Do you want to add a .gitignore? (Y/n) · y
# ✔ Do you want to install this sample project's dependencies with npm (hardhat @nomicfoundation/hardhat-toolbox)? (Y/n) · y

接下来,让我们安装依赖项:

npme install;
npm install @openzeppelin/contracts dotenv;

创建我们的NFT

我们这里的NFT,将通过引入一个动画的SVG来使它突出一点。
首先删除默认生成的模板化合约,并在其目录上创建一个新的合约。

rm contracts/Lock.sol;
touch contracts/zkEVMNFT.sol;

在该文件中,复制并粘贴以下NFT solidity代码。
文件: ./contracts/zkEVMNFT.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

// Imports
// ========================================================
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Base64.sol";

// Contract
// ========================================================
contract ZkEVMNFT is ERC721 {
    // Extending functionality
    using Strings for uint256;

    /**
     * Main constructor
     */
    constructor() ERC721("zkEVMNFT", "zkNFT") {}

    /**
     * Main minting function
     */
    function safeMint(address to, uint256 tokenId) public {
        _safeMint(to, tokenId);
    }

    // The following functions are overrides required by Solidity.
    /**
     * @dev See {ERC721}
     */
    function _burn(uint256 tokenId) internal override(ERC721) {
        super._burn(tokenId);
    }

    /**
     * Public function or burning
     */
    function burn (uint256 tokenId) public {
        _burn(tokenId);
    }

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721)
        returns (string memory)
    {
        // Validation
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        // SVG Image
        bytes memory imageSVG = abi.encodePacked(
            "<svg width=\"256\" height=\"256\" viewBox=\"0 0 256 256\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">",
            "<style xmlns=\"http://www.w3.org/2000/svg\">@keyframes rainbow-background { 0% { fill: #ff0000; } 8.333% { fill: #ff8000; } 16.667% { fill: #ffff00; } 25.000% { fill: #80ff00; } 33.333% { fill: #00ff00; } 41.667% { fill: #00ff80; } 50.000% { fill: #00ffff; } 58.333% { fill: #0080ff; } 66.667% { fill: #0000ff; } 75.000% { fill: #8000ff; } 83.333% { fill: #ff00ff; } 91.667% { fill: #ff0080; } 100.00% { fill: #ff0000; }} #background { animation: rainbow-background 5s infinite; } #text { font-family: \"Helvetica\", \"Arial\", sans-serif; font-weight: bold; font-size: 72px; }</style>",
            "<g clip-path=\"url(#clip0_108_2)\">",
            "<rect id=\"background\" width=\"256\" height=\"256\" fill=\"#ff0000\"/>",
            "<rect x=\"28\" y=\"28\" width=\"200\" height=\"200\" fill=\"white\"/>",
            "</g>",
            "<defs>",
            "<clipPath id=\"clip0_108_2\">",
            "<rect width=\"256\" height=\"256\" fill=\"white\"/>",
            "</clipPath>",
            "</defs>",
            "<text xmlns=\"http://www.w3.org/2000/svg\" id=\"text\" x=\"128\" y=\"150\" fill=\"black\" style=\"width: 256px; display: block; text-align: center;\" text-anchor=\"middle\">", tokenId.toString(), "</text>",
            "</svg>"
        );

        // JSON
        bytes memory dataURI = abi.encodePacked(
            "{",
                "\"name\": \"NUMSVG #", tokenId.toString(), "\",",
                "\"image\": \"data:image/svg+xml;base64,", Base64.encode(bytes(imageSVG)), "\"",
            "}"
        );

        // Returned JSON
        return string(
            abi.encodePacked(
                "data:application/json;base64,",
                Base64.encode(dataURI)
            )
        );
    }
}

配置 Hardhat

接下来我们将配置Hardhat配置文件,使环境变量能够被加载,配置对网络的支持,并调整优化。
文件: ./hardhat.config.ts

// Imports
// ========================================================
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import dotenv from "dotenv";

// Config
// ========================================================
dotenv.config();
const config: HardhatUserConfig = {
  solidity: {
    version: "0.8.18",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      }
    }
  },
  networks: {
    mumbai: {
      url: `${process.env.RPC_MUMBAI_URL || ''}`,
      accounts: process.env.WALLET_PRIVATE_KEY
        ? [`0x${process.env.WALLET_PRIVATE_KEY}`]
        : [],
    },
    zkevmTestnet: {
      url: `${process.env.RPC_ZKEVM_URL || ''}`,
      accounts: process.env.WALLET_PRIVATE_KEY
        ? [`0x${process.env.WALLET_PRIVATE_KEY}`]
        : [],
    }
  },
};

// Exports
// ========================================================
export default config;

为Hardhat添加环境变量

一旦我们完成了Hardhat的配置,我们将创建一个环境变量文件来存储我们的关键值。

touch .env;

注意:记得保护你的私钥不被人窥视。
文件: ./env

RPC_MUMBAI_URL=https://rpc.ankr.com/polygon_mumbai
RPC_ZKEVM_URL=https://rpc.public.zkevm-test.net
WALLET_PRIVATE_KEY=<YOUR-WALLET-PRIVATE-KEY>

最后,让我们修改我们的部署脚本以指向正确的合约。

文件: ./scripts/deploy.ts

// Imports
// ========================================================
import { ethers } from "hardhat";

// Main Deployment Script
// ========================================================
async function main() {
  // Make sure in the contract factory that it mateches the contract name in the solidity file
  // Ex: contract zkEVMNFT
  const zkERC721Contract = await ethers.getContractFactory("zkEVMNFT");
  const contract = await zkERC721Contract.deploy();

  await contract.deployed();

  console.log(`zkEVMNFT deployed to ${contract.address}`);
};

// Init
// ========================================================
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

部署出NFT合约

一旦一切就绪,我们现在可以将我们的NFT合约部署到zkEVM Testnet

# FROM: ./zkevm-erc721-hardhat

npx hardhat run scripts/deploy.ts --network zkevmTestnet;

# Expected Output:
# zkEVMNFT deployed to 0x7F77cF06C84A7bae6D710eBfc78a48214E4937a7

如果我们在资源管理器上进入合约,我们可以看到以下结果:
https://explorer.public.zkevm-test.net/address/0x7F77cF06C84A7bae6D710eBfc78a48214E4937a7

在zkEVM区块浏览器上验证我们的NFT合约

我们可以直接通过Hardhat与合约交互,进行铸币和读取,但我们将使用区块链浏览器来连接和铸币一个新的NFT。

在我们进入验证过程之前,我们将通过标准输入JSON进行验证,我们需要一个文件来上传。

如果我们看一下我们的build-info文件夹,我们可以找到一个JSON文件,我们只需要得到input部分。复制整个部分并创建一个名为verify.json的新文件,并将JSON数据粘贴在那里。

记住,你需要事先编译合约,用npx hardhat compile生成这些文件。

文件: ./artifacts/build-info/your-build-file.json

创建 JSON 文件

我们新的标准输入JSON文件为verify.json

接下来在区块资源管理器上访问该合约,并开始验证过程

zkEVM Testnet 区块浏览器验证和发布合约

zkEVM Testnet Block Explorer 通过标准输入 JSON 验证

zkEVM Testnet Block Explorer验证Conifgure verify.json
一旦验证通过,我们就可以在zkEVM Testnet block explorer上看到我们合约的完整代码。

https://explorer.public.zkevm-test.net/address/0x7F77cF06C84A7bae6D710eBfc78a48214E4937a7/contracts#address-tabs

在 zkEVM 测试完上铸造 NFT

现在我们的合约已经被验证了,我们可以通过区块浏览器和我们的钱包与它交互。

为了本教程的目的,我们将铸造一个NFT tokenID为4,以纪念距离zkEVM主网测试版发布的天数--2023年3月27日

https://explorer.public.zkevm-test.net/address/0x7F77cF06C84A7bae6D710eBfc78a48214E4937a7/write-contract#address-tabs

在zkEVM Testnet上铸造NFT

确认在zkEVM Testnet上成功铸造了一个NFT

查看我们的zkEVM NFT

我们在zkEVM Testnet上铸造了NFT,现在我们希望能够直观地看到这个链上SVG NFT

zkEVM Testnet区块浏览器读取NFT

复制tokenURI查询的数据部分,在Chrome浏览器中,将其粘贴到地址栏。

zkEVM Testnet NFT tokenURI JSON数据

从JSON中复制image值,在Chrome中,将其粘贴在地址栏中。

zkEVM Testnet NFT SVG
我们成功地部署了合约,验证了合约,并且我们能够看到铸造的NFT。

完整的zkEVM NFT代码库

点击这里查看本教程的完整代码库.

下一步是什么?

请关注更多关于zkEVM的教程。

如果你想更深入地了解zkEVM,请查看Polygon University

转载:https://learnblockchain.cn/article/5579

Goerli 与zkEVM Testnet跨链测试

使用Polygon zkEVM Bridge 站点,将网络添加到我们的钱包,并将 Goerli Token 桥接到 zkEVM Testnet:

Goerli Token转0.01 ETH到zkEVM Testnet

交易:0x10210572d6b4924af7ef946136295e9b209e1fa0 -> 0xf6beeebb578e214ca9e23b0e9683454ff88ed2a7
tx hash: https://goerli.etherscan.io/tx/0x07aa6363c5854180b41b8c4584314ca6d6af989d48332375f06f9caef3270dd7

执行合约

bridgeAsset(uint32 destinationNetwork,address destinationAddress,uint256 amount,address token,bool forceUpdateGlobalExitRoot,bytes permitData)

执行参数

Name Type Data
0 destinationNetwork uint32 1
1 destinationAddress address 0x10210572d6b4924Af7Ef946136295e9b209E1FA0
2 amount uint256 10000000000000000
3 token address 0x0000000000000000000000000000000000000000
4 forceUpdateGlobalExitRoot bool true
5 permitData bytes

0xF6BEEeBB578e214CA9E23B0e9683454Ff88Ed2A7为代理合约,使用EIP-1967 Transparent Proxy
逻辑合约地址:0x39e780D8800f7396e8B7530A8925B14025AedC77
关键逻辑合约代码:https://goerli.etherscan.io/address/0x39e780d8800f7396e8b7530a8925b14025aedc77#code

合约bridgeAsset接口源码

/**
     * @notice Deposit add a new leaf to the merkle tree
     * @param destinationNetwork Network destination
     * @param destinationAddress Address destination
     * @param amount Amount of tokens
     * @param token Token address, 0 address is reserved for ether
     * @param forceUpdateGlobalExitRoot Indicates if the new global exit root is updated or not
     * @param permitData Raw data of the call `permit` of the token
     */
    function bridgeAsset(
        uint32 destinationNetwork,
        address destinationAddress,
        uint256 amount,
        address token,
        bool forceUpdateGlobalExitRoot,
        bytes calldata permitData
    ) public payable virtual ifNotEmergencyState nonReentrant {
        if (
            destinationNetwork == networkID ||
            destinationNetwork >= _CURRENT_SUPPORTED_NETWORKS
        ) {
            revert DestinationNetworkInvalid();
        }

        address originTokenAddress;
        uint32 originNetwork;
        bytes memory metadata;
        uint256 leafAmount = amount;

        if (token == address(0)) {
            // Ether transfer
            if (msg.value != amount) {
                revert AmountDoesNotMatchMsgValue();
            }

            // Ether is treated as ether from mainnet
            originNetwork = _MAINNET_NETWORK_ID;
        } else {
            // Check msg.value is 0 if tokens are bridged
            if (msg.value != 0) {
                revert MsgValueNotZero();
            }

            TokenInformation memory tokenInfo = wrappedTokenToTokenInfo[token];

            if (tokenInfo.originTokenAddress != address(0)) {
                // The token is a wrapped token from another network

                // Burn tokens
                TokenWrapped(token).burn(msg.sender, amount);

                originTokenAddress = tokenInfo.originTokenAddress;
                originNetwork = tokenInfo.originNetwork;
            } else {
                // Use permit if any
                if (permitData.length != 0) {
                    _permit(token, amount, permitData);
                }

                // In order to support fee tokens check the amount received, not the transferred
                uint256 balanceBefore = IERC20Upgradeable(token).balanceOf(
                    address(this)
                );
                IERC20Upgradeable(token).safeTransferFrom(
                    msg.sender,
                    address(this),
                    amount
                );
                uint256 balanceAfter = IERC20Upgradeable(token).balanceOf(
                    address(this)
                );

                // Override leafAmount with the received amount
                leafAmount = balanceAfter - balanceBefore;

                originTokenAddress = token;
                originNetwork = networkID;

                // Encode metadata
                metadata = abi.encode(
                    _safeName(token),
                    _safeSymbol(token),
                    _safeDecimals(token)
                );
            }
        }

        emit BridgeEvent(
            _LEAF_TYPE_ASSET,
            originNetwork,
            originTokenAddress,
            destinationNetwork,
            destinationAddress,
            leafAmount,
            metadata,
            uint32(depositCount)
        );

        _deposit(
            getLeafValue(
                _LEAF_TYPE_ASSET,
                originNetwork,
                originTokenAddress,
                destinationNetwork,
                destinationAddress,
                leafAmount,
                keccak256(metadata)
            )
        );
        // Update the new root to the global exit root manager if set by the user
        if (forceUpdateGlobalExitRoot) {
            _updateGlobalExitRoot();
        }
    }

// TODO 代码分析

等待zkevm-test处理完成

zkevm-test处理完成

Bridge Details

zkevm-test 对应交易

交易 0xa49d20f2f5a26d4a8e6fe44409f862d744f5b1aa -> 0xf6beeebb578e214ca9e23b0e9683454ff88ed2a7
tx hash: https://testnet-zkevm.polygonscan.com/tx/0x830ac94e69eb9e3aacd384d4db8a46e7830b8f99b12b5c6b5176bd8326fddc4b

执行方法

claimAsset(bytes32[32] smtProof,uint32 index,bytes32 mainnetExitRoot,bytes32 rollupExitRoot,uint32 originNetwork,address originTokenAddress,uint32 destinationNetwork,address destinationAddress,uint256 amount,bytes metadata)

执行参数

# Name Type Data
0 smtProof bytes32[32] 0x0000000000000000000000000000000000000000000000000000000000000000 0x5c4dafd55279a81649518c48d62f1e9105c9e7f5a8933aa2fce126d207755c14 0x890afe4ec8996c6ddd3977287b067f0f67dd25cd11cda85b22a93deac5675cdd 0xb6b78f0847e6ecdfc18ee568a1349fae5f336bdce9716be04db241b68b8dd60b 0x18dd01806c4a8a02ac0303f33fe609a944d81197cb52e8b8e159fbf9e45f12ed 0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d 0x4ac259557eb77da7cf4309feb02d02d0884d94e56bbf71f539bb3871e5c5f0db 0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83 0x08f9cb503a809cc64017397ec880786e6713c3ba6a85c181d02ae7536e9b58e3 0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0 0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5 0xa647f1787afe7ec81c77a49649ba32057b0550470d3c9318dda05fcb8168d914 0xe1f88e3362870dcdb9c43a352e2b031a0c71080b78cc2a5732c3a5c1c0c8e3b4 0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb 0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc 0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2 0x645b9978ad76c57db977fe48efb0b43110e3ebe78f9badd9047aac6bcd8d66a5 0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a 0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0 0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0 0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2 0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9 0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377 0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652 0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef 0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d 0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0 0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e 0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e 0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322 0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735 0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9
1 index uint32 72030
2 mainnetExitRoot bytes32 0x8ac4bfc9b4381015f4d41ca9d127a0a4065827d9c61ae290ffbf401ae446a97a
3 rollupExitRoot bytes32 0xd226edaccb2a0c0dc760d2a670a84862a271af7916e0a289b286f61435378411
4 originNetwork uint32 0
5 originTokenAddress address 0x0000000000000000000000000000000000000000
6 destinationNetwork uint32 1

合约地址0xf6beeebb578e214ca9e23b0e9683454ff88ed2a7 为 EIP-1967 Transparent Proxy代理合约,代理的逻辑合约地址为0x39e780d8800f7396e8b7530a8925b14025aedc77
合约源码:https://testnet-zkevm.polygonscan.com/address/0x39e780d8800f7396e8b7530a8925b14025aedc77#code

/**
     * @notice Verify merkle proof and withdraw tokens/ether
     * @param smtProof Smt proof
     * @param index Index of the leaf
     * @param mainnetExitRoot Mainnet exit root
     * @param rollupExitRoot Rollup exit root
     * @param originNetwork Origin network
     * @param originTokenAddress  Origin token address, 0 address is reserved for ether
     * @param destinationNetwork Network destination
     * @param destinationAddress Address destination
     * @param amount Amount of tokens
     * @param metadata Abi encoded metadata if any, empty otherwise
     */
    function claimAsset(
        bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProof,
        uint32 index,
        bytes32 mainnetExitRoot,
        bytes32 rollupExitRoot,
        uint32 originNetwork,
        address originTokenAddress,
        uint32 destinationNetwork,
        address destinationAddress,
        uint256 amount,
        bytes calldata metadata
    ) external ifNotEmergencyState {
        // Verify leaf exist and it does not have been claimed
        _verifyLeaf(
            smtProof,
            index,
            mainnetExitRoot,
            rollupExitRoot,
            originNetwork,
            originTokenAddress,
            destinationNetwork,
            destinationAddress,
            amount,
            metadata,
            _LEAF_TYPE_ASSET
        );

        // Transfer funds
        if (originTokenAddress == address(0)) {
            // Transfer ether
            /* solhint-disable avoid-low-level-calls */
            (bool success, ) = destinationAddress.call{value: amount}(
                new bytes(0)
            );
            if (!success) {
                revert EtherTransferFailed();
            }
        } else {
            // Transfer tokens
            if (originNetwork == networkID) {
                // The token is an ERC20 from this network
                IERC20Upgradeable(originTokenAddress).safeTransfer(
                    destinationAddress,
                    amount
                );
            } else {
                // The tokens is not from this network
                // Create a wrapper for the token if not exist yet
                bytes32 tokenInfoHash = keccak256(
                    abi.encodePacked(originNetwork, originTokenAddress)
                );
                address wrappedToken = tokenInfoToWrappedToken[tokenInfoHash];

                if (wrappedToken == address(0)) {
                    // Get ERC20 metadata
                    (
                        string memory name,
                        string memory symbol,
                        uint8 decimals
                    ) = abi.decode(metadata, (string, string, uint8));

                    // Create a new wrapped erc20 using create2
                    TokenWrapped newWrappedToken = (new TokenWrapped){
                        salt: tokenInfoHash
                    }(name, symbol, decimals);

                    // Mint tokens for the destination address
                    newWrappedToken.mint(destinationAddress, amount);

                    // Create mappings
                    tokenInfoToWrappedToken[tokenInfoHash] = address(
                        newWrappedToken
                    );

                    wrappedTokenToTokenInfo[
                        address(newWrappedToken)
                    ] = TokenInformation(originNetwork, originTokenAddress);

                    emit NewWrappedToken(
                        originNetwork,
                        originTokenAddress,
                        address(newWrappedToken),
                        metadata
                    );
                } else {
                    // Use the existing wrapped erc20
                    TokenWrapped(wrappedToken).mint(destinationAddress, amount);
                }
            }
        }

        emit ClaimEvent(
            index,
            originNetwork,
            originTokenAddress,
            destinationAddress,
            amount
        );
    }

// TODO 代码分析

确认余额

https://testnet-zkevm.polygonscan.com/address/0x10210572d6b4924af7ef946136295e9b209e1fa0
// Note: Our ETH balance display is temporarily unavailable. Please check back later.
testnet-zkevm.polygonscan.com 浏览器余额出问题了,直接MetaMask查看

以正确到账

//TODO testnet-zkevm反向Goerli的转账跟踪

Silkworm - C++ 以太坊执行客户端

GitHub:https://github.com/torquem-ch/silkworm

引用于EOS EVM

Silkworm 架构设计

EOS EVM 的推出时间表曾发生过一些变化,而导致这一变化的原因之一是实施 Silkworm 作为 EOS EVM 的执行客户端,这是一个主要架构增强优化。 Silkworm 是 Ethereum 节点的 C++ 实现,符合 Erigon 的规范。 它被用于支持 RPC 并提高该领域的兼容性。

它的设计目标是成为最快的以太坊客户端,同时不牺牲代码的性能或可读性。 以下是 EtherWorld 的一篇文章,其中介绍了 Silkworm 强大功能的一些要点:

  • Silkworm 更容易理解,因为其代码库是新的,并且不包含任何主要的遗留功能。
  • 它在开发者社区保持中立客观。
  • Silkworm 采用 Apache-2.0 许可协议进行许可。 此许可证是 Permissive,这意味着它的限制最少,可以在大多数项目中使用。
  • Silkworm 使用 evmone 作为其 EVM 解释器,这是已知的最快、完全兼容的 EVM 实现。
  • Silkworm 使用 MDBX,它是最快的嵌入式键值存储,具有完全 ACID 交易。

// TODO 代码分析

区块链模块化设计是未来吗?

如何使用模块化可能是突破不可能三角的一个途径。

介绍

大约十年前,世界见证了移动智能手机的增长。当时,一些大公司认为他们可以通过引入模块化架构来彻底改变智能手机。2013 年,谷歌发布了 Project Ara,这是一款采用模块化设计的新型智能手机。与今天由密封的铝和玻璃片制成的“单体”手机不同,Ara 允许用户以多种方式定制他们的手机,使所有基本部件都可以模块化。你不会被迫频繁升级到新手机。相反,你可以根据自己的喜好简单地为旧手机添加最好的新部件。可悲的是,模块化并没有在手机生态系统中取得成功,这个概念仍然是一段几乎被遗忘的技术历史。

模块化智能手机可以作为模块化区块链的警示故事,因为尽管当前工具存在合理的缺点,但新的和令人兴奋的解决方案的炒作并不能保证长期的胜利。然而,就区块链而言,用户对可扩展性的需求已经在推动开发人员构建和采用模块化架构。这种需求使得模块化区块链架构不太可能与模块化智能手机那样,遵循相同的命运。

但究竟什么是模块化区块链架构?我们如何确保这些解决方案不会成为另一个 Project Ara?这篇文章希望能回答所有这些问题。

单体和模块化

在进一步深入研究之前,让我们梳理一下单体架构和模块化架构之间的区别。介绍这个概念的最简单方法是举一个类似而熟悉的概念的例子。正如手机有一些核心部件,比如摄像头、电池和触摸屏,区块链也有核心部件。

iPhone 是“单体”手机的一个很好的例子。它配备了使用手机所需的所有部件,并没有提供太多定制选项。当然,你可能无法对内部进行太多定制,但它既流畅又快速。但是,有时你可能希望进一步自定义你的手机。假设随着岁月的流逝,新手机配备了更好的相机。过时手机的其余部分可能工作正常,但使用现有的相机,你无法获得新手机那样的体验。

使用模块化架构,你不必购买全新的手机。相反,你可以像乐高积木一样更换相机,然后换上更好的相机。

Google 的 Project Ara 是模块化手机的一个例子。这款手机由积木制成,可以根据你的选择换入和换出零件。只要有兼容的部件,Ara 都会支持。

与智能手机一样,区块链由多个基本组件组成;这些组件如下所示:

  • 共识
    区块链的共识层通过计算机网络就链的状态达成共识来提供排序和最终性。

  • 执行
    该层通过运行指定的代码来处理交易。它也是用户通常与区块链交互的地方,例如通过签署交易、部署智能合约和转移资产。

  • 结算
    结算层用作验证在第 2 层执行的活动的平台,例如Rollup以及争议解决。最重要的是,它是记录实际区块链最终状态的地方。

  • 数据可用性
    验证状态转换是否有效所需的数据应发布并存储在该层上。在发生攻击或操作失误,区块生产者未能提供交易数据的情况下,这应该是容易检索和验证的。

简而言之,单体式区块链在单个软件中自行执行所有这些任务,而模块化区块链将它们分成多个软件。现在,你可能想知道一次处理所有这些任务的区块链有什么缺点?

这又回到了古老的问题,即可扩展性不可能三角困境。

可扩展性不可能三角困境表示区块链只能具有以下三个特征中的两个:去中心化、安全性和可扩展性。
现有的(新的)单体区块链倾向于针对三角的安全和可扩展进行优化。比特币和以太坊更加强调去中心化和尽可能安全。但都是有代价的。去中心化区块链通常没有高带宽来执行交易。以太坊每秒最多处理 20 笔交易,而比特币的处理能力甚至更低。如果我们想在全球范围内使用这些协议,每秒 20 个交易是远远不够的。一些单体链至少在理论上,可以使我们更接近全球规模,因为它们的 TPS 和总吞吐量是足够的。然而,他们往往缺乏去中心化,而这是区块链技术的核心原则。

模块化架构的目的是将区块链的一些工作外包出去,在保持去中心化的同时做出更多性能更高的链。让我们讨论一下以太坊,并讨论它有望如何利用模块化。

以以太坊为中心的生态系统

大多数第 1 层区块链

现在的以太坊是一个单体的区块链。今天大多数其他第 1 层区块链也将被归类为单体区块链。就像 iPhone 的例子一样,单体区块链的某些功能有时会开始落后于较新的替代方案,导致在对比最新和最具创新性的第 1 层时流失开发人员和消费者。为了解决以太坊目前在吞吐量方面的瓶颈,开发人员正在构建Rollup执行层以增加交易带宽。

模块化执行层

示例:Optimism, Arbitrum, Fuel, Scroll, ZkSync

Rollups 作为执行层是当今以太坊上使用最广泛的扩容方法。Rollups 是独立的区块链,具有更高的交易执行性能,其最终结果在以太坊上结算,以有效继承其(更好的)安全性和去中心化。

在高层次上,Rollup只是一个区块链,它将其区块的结果发布到另一个区块链。然而,这只是Rollup的一个组成部分,因为你还需要欺诈和有效性证明以及一种无许可插入交易的方法。Rollups 通过在两个智能合约之间同步数据来实现这一点,一个部署在第 1 层,一个部署在第 2 层。这种设计使其成为 rollup 而不是侧链。这些关键组件对于 rollup 的安全是必不可少的,因为没有这些组件,rollup 可以被停止或审查 。

目前,大多数 rollup 都提供 EVM 兼容性以帮助以太坊开发人员轻松迁移,但在计算效率和开发易用性方面,执行层可能有更好的替代方案。用户甚至可能想要更多 EVM 等效链上不存在的功能,例如帐户抽象。鉴于开发人员的偏好广泛,这种趋势很可能会持续下去,我们将看到更多新颖的解决方案进入市场,例如 SolanaVM 和 MoveVM 执行层。Fuel 是一个不兼容 EVM 的执行层的例子,它的唯一重点是执行在其他Rollup上不可能执行的计算。Fuel 也是第一个“模块化执行层”,正如我们将看到的那样,它允许它成为一个主权Rollup、一个结算链,甚至是一个单体区块链。Rollup 只是执行层,而 Fuel 可以是更多。


Fuel 可以用普通 Rollups 做不到的方式进行模块化。因此得名“模块化执行层”。我们将很快深入探讨 Celestia 架构的机制。(图源:Fuel)

Fuel 显示执行层可以具有创造性,并且优先考虑计算速度而不是 EVM 支持。虽然许多熟悉模块化架构的人都知道 Fuel,但另一个强大的竞争者却鲜为人知。最有趣的即将到来的模块化执行层之一是Kindelia。除了是最快的计算层之一,Kindelia 还拥有一个利用其虚拟机的独特证明系统。Kindelia 的 HVM 提供了一种近乎即时的证明检查器,内置于他们名为 Kind 的智能合约语言中。Kind是必不可少的,因为智能合约可以在它们的代码中证明它们的代码是安全的,不会被利用并且操作正确。这种类型的设计可以解决智能合约编码不当的问题,并使我们免受当今困扰智能合约的漏洞之苦。这只是Kindelia比其他执行层提供价值的一种方式。

但在执行层方面的扩展只是难题的一个部分。开发人员寻求进一步模块化单体区块链,以挤出每一点可能的性能。这就引除了如何模块化数据可用性层。

模块化数据可用性层


示例:Metis、ZkPorter、Anytrust
Validium 是一种Rollup,其数据在链下而不是存储在链上。

但为什么我们要将数据转移到链下呢?这是因为我们正在尝试优化数据可用性。Rollup系统的效率在很大程度上取决于其数据可用性层的能力。当底层无法处理Rollup的交易排序器生成的数据量时,就会导致处理交易的瓶颈。因此,rollup 无法处理额外的交易,导致 gas 费用增加和/或执行时间变慢。换句话说,rollup 的数据可用性层的性能是决定其依靠的单体区块链交易处理能力和相关费用的关键因素。

validiums 的缺点是它们是链下的,引入了更多的信任假设。我们想要一个链上解决方案来提高以太坊的数据可用性层。答案是 Danksharding。

Danksharding 与以太坊的整合将其转变为一个用于结算和数据可访问性的精简平台。

Danksharding 的创新之处在于它能够将这些概念合并成一个有凝聚力的单元。Rollup证明和数据在同一块内进行验证,从而创建一个无缝且高效的系统。但是,作为其正常操作的一部分,Rollup需要为其压缩数据提供大量存储空间。Danksharding 为这一需求提供了解决方案,提供了跨多个Rollup实现数百万 TPS 的潜力。Danksharding 是一种将网络活动分成片以增加data blob 空间的技术。data blob 是以太坊中一种更高效和标准化的数据格式,可以承载大量数据,并被 rollups 使用以降低 gas 费用。Danksharding 利用“数据可用性抽样”使节点能够通过仅检查一小部分来验证大量数据,为未来铺平道路,使更便宜和更快的第二层网络能够蓬勃发展,同时实现以太坊的直接交易。

Danksharding 很棒,因为它将继承以太坊本身的所有安全性和去中心化。然而,这有一个缺点。由于以太坊的发展速度相对缓慢,我们可能需要数年时间才能将 Danksharding 正确实现到以太坊中。EIP-4844 计划引入 Proto-Danksharding,这是实现 Danksharding 的第一步。EIP-4844 通过引入容纳数据块的新交易来增强以太坊。这种用于Rollup数据的专门存储为更具成本效益的收费市场铺平了道路。

如果你想要一个快速的数据可用性层但又不想坐等 Danksharding 发布怎么办?Celestia 正是提供了这一功能的协议。转变以以太坊为中心的模块化视角,我们值得深入研究 Celestia,看看模块化区块链还可以如何解释。

以 Celestia 为中心的生态系统


Celestium 是一种独特的解决方案,它结合了 Celestia 的数据可用性和以太坊的结算及共识。Danksharding仍然是最安全的方法,因为它融入了以太坊、去中心化和稳健性。然而,一些Rollup 宁愿现在就寻求可扩展性,而不是等待 Danksharding 被实施到以太坊中。

对于等不及 Danksharding 的项目,一种可能的选择是利用链下数据可用性解决方案,例如 Validiums,它利用“数据可用性委员会”(DAC)来证明数据可用。然而,这种方法不那么去中心化或安全,因为它依赖于多重签名,并且无法验证 DAC 当前是否诚实或他们过去是否诚实。

Celestium 提供了比 DAC 更安全的替代方案。在 Celestium 中,数据可用的证明由整个 Celestia 验证者集的质押支持,这意味着如果 ⅔ 的验证者提供了不正确的信息,他们可能会被削减并可能损失一大笔钱。与不存在惩罚的 DAC 不同,Celestium提供了一个严厉和即时的反应。

此外,用户可以通过在区块上运行数据可用性采样并检查量子引力桥(Quantum Gravity Bridge)来验证 Celestia 的诚实性,量子引力桥是从 Celestia 到以太坊的无信任单向消息传递桥。桥通常是任何解决方案中最脆弱的部分,因此必须建立冗余。

Celestium 与 Danksharding 一起利用数据可用性采样 (DAS) 来验证所有数据的非恶意性质。DAS 允许节点通过下载随机片段并在任何部分丢失时发出警报来确保块的可用性。该警报系统只是采用欺诈证明(例如 Celestia)的 DAS 机制的一个方面。在像 Danksharding 这样的有效性证明 DAS 机制的情况下,不需要警报系统,因为有效性证明保证了纠删码和承诺的正确性。这些机制降低了隐藏区块数据的风险,并确保大量节点随机检查区块。

一个节点随机抽取一个区块来检查它的可用性。(来源:Vitalik Buterin)

数据抽样是使Celestia和Danksharding如此安全的原因。至少用户知道,如果发生损坏,他们可以很快发现。相比之下,在DAC这个黑匣子里,可能会有一年的“腐坏”,而没有人会意识到这一点。

示例:Fuel

与以太坊上的传统Rollup相比,主权Rollup的功能不同。与标准Rollup不同,主权Rollup不依赖第 1 层上的一组智能合约来验证区块并将其附加到规范链。相反,区块作为原始数据直接发布到链上,rollup 上的节点负责验证本地分叉选择规则以确定正确的链。这将结算责任从第 1 层转移到Rollup。与传统的 rollup 不同,在主权 rollup 和 Celestia 之间没有建立信任最小化的桥梁。这可以被视为一个负面因素,因为你希望桥接尽可能信任最小化,但这确实为主权Rollup提供了通过分叉独立升级路径的优势。并且比非主权Rollup提供的升级更加安全。从技术上讲,我们不应该将其视为Rollup,因为Rollup通常意味着具有统一的结算和数据可用性层。因此,主权Rollup也简称为主权区块链。

为了让开发人员更容易在 Celestia 上创建主权Rollup,Celestia 创建了 Rollmint,取代 Tendermint 作为共识机制。这使得 rollups 能够直接向Celestia发布区块 ,而不是通过 Tendermint 程序。通过这种设计,链背后的社区拥有完全的主权,不受其他状态机的权威约束。这使其他与以太坊上智能合约或Rollup背后的社区不同,后者受以太坊社区社会共识的约束。


结算链示例:Fuel、Cevmos、dYmension

结算Rollup 是指创建独立和模块化结算组件的。目前,rollups 使用以太坊主链进行结算,但除此之外还有其他解决方案。以太坊链与非Rollup应用(智能合约交易)共享网络,导致扩容受限和缺乏特定优化。

一个理想的Rollup结算层将只允许Rollup智能合约和Rollup之间的简单转账,同时禁止非Rollup应用或使其交易高昂。

Celestia 的设计为开发人员提供了一个标准的全局状态共识层,以构建作为单一信任最小化执行层Rollup。它还可以在同一全局状态共识层上的Rollup之间实现信任最小化的桥接,这是当前架构中没有的新概念。开发人员是否会采用这种新的跨Rollup范式还有待观察。

结算链的示例包括 Cevmos、Fuel 和 dYmension,其中 Polygon 通过建立其对模块化架构的解释与 Celestia 竞争。在Polygon的模块化设计中,Polygon Avail作为数据可用性和共识的模块化组件,Polygon区块链作为结算层。

单体链的案例

与较新的模块化解决方案相比,许多介绍模块化区块链的文章通常将单体第 1 层区块链称为过时的“恐龙”技术。目前,很难完全支持这钟说法,因为这些扩容解决方案的一个主要问题是它们给整个系统添加了更多的信任假设。虽然我们已经讨论了大多数 DAC 和 validium 是如何不安全的,但这甚至可以扩展到执行层(即Rollup)。

当今使用最广泛的一些Rollup仍然没有真正去中心化,即使它们获得了数十亿美元。在撰写本文时,Optimism 仍然没有功能性欺诈证明,而 Arbitrum 可以从单个多重签名中改变。这两种协议都在努力解决这些问题,作为其开发计划的一部分,但重要的是要记住,去中心化并不是因为协议使用特定架构就一定能实现。此外,所有模块化组件之间的桥梁,主要是主权Rollup,可能面临与跨链桥梁相同的不安全性。最后,一个主要问题是在模块化堆栈的基础之上进行开发会增加复杂性;对于某些开发人员来说,这可能是一个挑战。最终,我们希望 rollup 能解决这些问题并实现充分的去中心化。然而,与此同时,单体的第 1 层也可能变得同样去中心化。

我们之前的报告讨论了一些单体式第 1 层如何使用DAG 架构在内部进行扩展。这只是一个例子,表明单体区块链正在尝试在不依赖链下组件的情况下进行创新,并且正在进行无数其他优化以最大限度地提高性能。我们不能简单地诋毁旨在解决可扩展性三角难题的新区块链设计的想法。

结论

就像以前有模块化手机一样,现在也有模块化区块链。然而,看到基于 Danksharding 的以Rollup为中心的未来的潜力表明,模块化区块链架构不太可能遭受与模块化手机相同的命运。Kindelia 和 Fuel 等执行层尤其会看到用户增长,因为它们对速度和新功能的关注将使构建在它们之上的应用程序真正具有创新性。

不幸的是,其中许多模块化设计仍未经过测试,一些模块化区块链设计可能永远不会得到广泛采用。随着 Celestia 和 Danksharding 被广泛采用,Validiums 可能会被完全淘汰。Celestia 的 主权 rollups 可能面临一些与现有第 1 层相同的桥接问题,由于安全和复杂性问题而阻碍采用。

一个去中心化、模块化的区块链未来仍然是一个漫长的过程。在此期间,单体区块链将继续发挥作用并不断创新。当我们最终达到模块化区块链被广泛采用的未来时,单体区块链的格局可能也会完全不同。尽管如此,我们需要扩容解决方案来为现有区块链提供流动性和用户服务,从长远来看,模块化区块链架构可能是实现这一目标的最佳方式。

转载自:https://learnblockchain.cn/article/5413

opside 跨链测试

Opside介绍

Opside 是一个三层区块链平台,形式为“Base chains <- Opside chain <- Rollups”。 它具有资产多样性和无限可扩展性等优势,为高吞吐量的web3应用提供友好的运行环境。

三层架构

测试三层跨链转账

1.MetaMask添加Opside测试网络信息

2.MetaMask添加Opside zkEVM 测试网络信息

3. 从水龙头获取代币

领取网址:https://faucet-testnet.opside.network/
领取时需要对指定消息进行签名

交易hash

https://opside.info/address/0x10210572d6b4924Af7Ef946136295e9b209E1FA0

领取代币类型和数量

MetaMask手动添加代币合约地址到资产

USDT:0xDEd24358C0d7C96BC419F479771D1da3443Fe52d
USDC:0x407d6E2bF44A1B70eBd51CcF0aFCFBbD5F0b6d15
VTP:0x956a81C7D7031Fd123961cC69C28a2a49884FC68

4. 测试第1层->第2层桥接

跨链桥网址: https://bridge-testnet.opside.network/

sepolia转1USDT到opside

// TODO

opside转1USDT到Opside zkEVM Testnet

跨链桥网址:https://zkbridge-testnet.opside.network/

  1. Approve USDT spending cap
    0x10210572d6b4924af7ef946136295e9b209e1fa0 -> 0xded24358c0d7c96bc419f479771d1da3443fe52d
    https://opside.info/tx/0xc0d592c5edcc533abddf83f0b38c40a4099ef2ef3de0b6fd862048df24b06036
  2. Bridge Asset
    0x10210572d6b4924af7ef946136295e9b209e1fa0 -> 0xfb15d16f87ad92f0a6d54a9fbc951676701071f9
    https://opside.info/tx/0x1d2dfc8ce07e3039b46e92f27a7bbd5dc461874f4b5020ee3931b0b12804db04

备注:https://testrpc.zkevm.opside.network/bridge-service/bridges/0x10210572d6b4924af7ef946136295e9b209e1fa0?limit=10000&offset=0
测试时这个API,ssl证书有问题:ERR_SSL_VERSION_OR_CIPHER_MISMATCH

Opside zkRollup L3 测试网的RPC同样问题 ERR_SSL_VERSION_OR_CIPHER_MISMATCH
https://testrpc.zkevm.opside.network/

// TODO 等待解决后继续测试,先忙其他

opside转1USDT到sepolia

  1. Approve USDT spending cap
    0x10210572d6b4924af7ef946136295e9b209e1fa0 -> 0x10210572d6b4924af7ef946136295e9b209e1fa0
    https://opside.info/tx/0xca2d7fdf791b7432a3e2067bc814ce9ca66ad2163d55bc805743fad6996ffc98
  2. Deposit
    0x10210572d6b4924af7ef946136295e9b209e1fa0 -> 0x832e50eec9d7f9e2b5d4286a5834400dfaeafc3f
    https://opside.info/tx/0x6b6a505b4aa1aa4b314d5b69a316713d6443f0092e7e9f2b96e8b18bc8778fe6
  3. sepolia.etherscan
    0x91b126ff9af242408090a223829eb88a61724aa5 -> 0xe8b0a865e4663636bf4d6b159c57333210b0c229
    ERC-20 Tokens Transferred: 0x89a5f2c62213b18ee5f83b21cb1a323920c9b101 -> 0x10210572d6b4924af7ef946136295e9b209e1fa0
    https://sepolia.etherscan.io/tx/0x369078b11063231d35738c6afcde52477c5fe5dd78535c9f282f1e36fcac5de8
    获得1 USDT

    https://sepolia.etherscan.io/token/0x969d499507b4f437953db24a4980fdeeda6db8a1?a=0x10210572d6b4924Af7Ef946136295e9b209E1FA0

参考文档

https://docs.opside.network/
https://docs.opside.network/user-guides/video-tutorial