您正在查看: Ethereum 分类下的文章

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的转账跟踪

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

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

介绍

大约十年前,世界见证了移动智能手机的增长。当时,一些大公司认为他们可以通过引入模块化架构来彻底改变智能手机。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

分析0xSplits - 收入拆分协议

技术背景

很多时候,我们需要把一份收入(金额)拆分到不同的地址,而又不想让付款人(通常是用户)承担拆分收入部分的 GAS费用, 0xSplits 的架构及解决思路值得我们参考。
本文先介绍了0xSplits 是什么,0xSplits 采用的架构是怎么样的,同时介绍了几种其他的拆分方式可能带来的问题,另外还有一些0xSplits在 Gas 优化上的做法。

0xSplits是什么

0xSplits 是链上收入拆分的协议, 它是一个智能合约,允许你将任何付款拆分到许多收款人,例如,它可以用于NFT版税。EIP-2918允许你只指定一个地址作为收款人,但如果你把这个地址设置为0xSplits地址,你就可以有很多个收款人。
0xSplits由一个主合约组成,可以创建许多子克隆合约(SplitWallet)。

  1. 你通过调用主合约创建一个新的拆分(split)
  2. 它创建一个负责接收ETH的子合约
  3. 在需要接收ETH的地方使用子合约的地址

译者注:克隆合约是使用同一份代码,创建(复制)多个出合约, 每个合约都有自己的状态。

这里是子克隆合约的代码,你可以看到,它非常简单。它可以接收ETH,并且有一个函数可以将ETH转移到主合约, 0xSplits也支持ERC-20,但省略了这部分代码

但收到的ETH如何拆分给接收者?这就是主合约的任务(为了便于阅读,代码做了修改):


所以,资金是静止不动的,直到有人明确地调用 distributeETH。但为什么有人会浪费Gas来调用这个函数呢?答案是distributorFee(分配费用)。这是在创建拆分时设置的,谁调用distributionETH就归谁。

这种激励性的代码调用是必要的,因为EVM没有周期任务(cron )或回调来定期分配资金,一切都需要由个人或链外机器人发起,distributorFee有点类似于CryptoKitties合约中的autoBirthFee。

为什么0xSplits会选择这种架构?还有,为什么不在收到资金的时候立即分发?这是我们这篇文章的主要想解决的问题。

0xSplits 架构

你可以在Etherscan找到0xSplits的代码

克隆SplitWallet合约接收付款并持有资金,直到它们被调用,将资金转移到主合约。

分销商(distributor)被激励在主合约中调用distributeEth,这将把资金从克隆合约转移到主合约中,同时也将支付/分成。

但是在这一点上,资金仍然存放在主合约中(合约有一个从收款人地址到余额的映射)。为了实际接收资金,用户必须调用主合约中的withdraw函数。

此架构的可能替代方案
现有的设定是相当复杂的:克隆合约接收付款,分销商(distributor)将这些资金从克隆合约转移到主合约,并将资金分给接收者。但是收款人仍然需要调用withdraw来接收实际拆分到的资金。所以,端到端的用户流程并不是完全自动化的(另外,有些钱会被分销商收取)。

为什么0xSplits需要如此复杂的设置?让我们来看看其他的方法,以及它们为什么不可行。

若在收到资金时进行拆分?

为什么不在收到资金的同一函数中进行拆分呢?也就是说,不在合约中保存资金, 而是立即分配,这里有2个原因:

  1. 付款人需要提供额外的Gas
  2. 它破坏了互操作性。付款人不能再直接将ETH转账(transfer)到克隆合约中。而是要求付款人将不得不调用合约上的一个特定函数(来进行拆分),这会破坏互操作性。你可能会说只要重写receive函数,但receive函数经常受2300个Gas限制,这意味着你不能在其函数中进行大量的转账。

若使用共享合约而不是克隆合约呢?

为什么没有一个共享的合约,为所有可能的拆分接收所有的付款?这将摆脱克隆(创建新合约),但它并不奏效,因为当共享合约收到付款时,它不知道付款是要进行怎样的拆分。

将distributeEth和withdraw合二为一呢?

为什么不把整个过程端到端自动化,让分销商在一个函数中执行这2个操作?主要是因为安全问题。强烈建议使用pull over push - 即让用户手动拉取他们的自己资金,而不是把资金自动推送他们。

为什么 "推(push)"被认为是一种安全风险?阅读下面的链接以获得完整的答案,但简而言之,这是因为不是所有的接收者都能保证正确处理ETH的接收。一些恶意的行为者可能会部署一个智能合约,在 receive 功能中进行回退(revert)。因此,如果至少有一个接收者回退(revert),整个操作就会回退(revert)。
参考阅读文章:Pull over Push 将转移以太坊的相关风险转移到用户身上。

如果你想出另一种替代架构,请告诉我(在评论中这里)。

我认为0xSplits为他们所针对的使用场景选择了正确的架构--这就是成为DeFi生态系统中的一个基本构件。还有其他类似的支付拆分器,它们是针对不同的使用场景,它们使用不同的架构。例如,disperse.app在收到资金时进行拆分,它可能是一个更好的一次性付款拆分工具。

0xSplits 代码

我对合约进行了重组,并将所有内容按功能分组,以方便阅读。
现在我们来看看现有的架构是如何在代码中实现的。
这里只有2个合约:SplitMain.sol和SplitWallet.sol(克隆)。其余的是库和接口。
你已经在前面看到了SplitWallet.sol的代码,但我把再次复制到这里只是为了参考。

这很简单。它可以接收ETH并将资金转账到主合约。你可能会问,如果没有 receive 功能,它如何接收ETH?答案是,"克隆 "库创建该合约的克隆,神奇地插入 receive 功能的汇编代码。

现在到了主合约 - SplitMain.sol, 这是所有行动发生的地方。

创建一个拆分合约

SplitMain.sol合约开头的函数,用于创建新的拆分。

validSplit只是验证一些东西,如:

  • 百分比的总和应该是1。
  • 收款人和百分比数组具有相同的长度。
  • 收款人地址数组需要被排序。为什么?我们很快就会知道。

如果controller是零地址,这意味着拆分没有所有者,它变得不可改变。Clones库将创建一个SplitWallet合约的克隆,并保存在构造函数中。

clone和cloneDeterministic(在上面的createSplit函数中)的区别是,确定性的部署到一个预先定义的地址(由传入的splitHash决定)。不可变(Immutable)的拆分合约使用确定性的克隆,以避免有人创建完全相同的拆分合约时时发生碰撞。

拆分(split)是由这些数据结构表示的:

使用哈希值的Gas优化

请注意,上面只保存了拆分的哈希值,而没有保存地址、收款人和distributorFee。为什么我们需要哈希值?

哈希值是用来将所有关于拆分的信息(收款人、百分比、distributorFee)合成一个字符串。

通过只存储哈希值而不是函数的所有参数,我们节省了大量的存储空间,从而节省了Gas。

但是,我们如何查找在哈希过程中丢失的信息,如recipients?我们要求这些信息被传递到需要它们的函数中。在合约中,我们只需再次对传入的参数进行散列,并与存储的散列进行比较。如果它们匹配,传入的参数就是正确的。

分销商被激励在链外记录recipients, percentages等,并将这些信息传递给所有需要它们的函数。分销商费用则是支付他们的服务的费用。

另外,我们现在明白为什么收款人地址数组需要在createSplit函数中进行排序。因为否则,哈希值将无法重现。

更新拆分的内容

用哈希值更新拆分也变得非常有效。只要更新哈希值即可。

(onlySplitController确保msg.sender == split.controller)

所有权的转移

如果一个拆分是可变的,你可以转移其所有权。

这是有两步过程:

为什么是两步程序?为了防止意外转移到一个错误的地址。两步程序使你的合约更安全一些(代价是略微多一点Gas)。

资金如何拆分分配

资金是如何分配的?让我们来看看:

我们首先通过哈希算法验证传入的args,并与存储的哈希算法进行比较。然后我们将资金转移到主合约,为分销商预留奖励,最后分配资金。

这个函数为了可读性做了大量的修改。请阅读原始的源代码以了解实际的实现。

终端用户提取资金

主合约的最后一个功能是收款人提取资金的能力。这是一个非常简单的功能。


由于ethBalances中的资金来于所有的克隆拆分合约。但是withdraw需要手动调用,机器人/分销商没有受到激励进行这个动作。

同样有趣的是,有人可以代表你调用withdraw(为你的Gas付钱)。

其他的想法

  • 0xSplits 实际上允许你有嵌套的拆分--指定一个拆分为另一个拆分的收款人。
  • 0xSplits 也适用于 ERC-20 资金的拆分。我只是省略了代码以方便阅读。
  • 不小心发送到主合约的资金是无法恢复的,因为没有办法提取多余的ETH。
  • OpenZeppelin也有一个支付拆分器,但我还没有研究过它是如何实现的,还有disperse.app,对于一次性的拆分来说可能更好。
  • 用比特币而不是用以太坊来拆分支付要容易得多。由于UTXO架构的存在,比特币几乎拥有这种开箱即用的功能。(见 Solidity Fridays)

就这样吧!让我知道你对0xSplits的看法,在评论区

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

Solidity Gas 优化 - 理解不同变量 Gas 差异

本文我们来尝试进行 RareSkills Gas 优化的一个挑战,完成这个挑战可以学习到更多 Gas 技巧。
以下是 Distribute 合约,合约的作用是向 4 个贡献者平均分配 1 ETH, 代码如下

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.15;

contract Distribute {
    address[4] public contributors;
    uint256 public createTime;

    constructor(address[4] memory _contributors) payable {
        contributors = _contributors;
        createTime = block.timestamp;
    }

    function distribute() external {
        require(
            block.timestamp > createTime + 1 weeks,
            "cannot distribute yet"
        );

        uint256 amount = address(this).balance / 4;
        payable(contributors[0]).transfer(amount);
        payable(contributors[1]).transfer(amount);
        payable(contributors[2]).transfer(amount);
        payable(contributors[3]).transfer(amount);
    }
}

合约代码:https://github.com/RareSkills/gas-puzzles/blob/main/contracts/Distribute.sol

这个合约默认情况下,所需 GAS 是 71953, 需要优化到 57044, 以便通过测试用例,测试用例代码在这里。
这是默认运行的截图:

在原始合约上运行测试结果

挑战的要求是仅修改合约Solidity代码来使 distribute 下降到57044, 因此不可以修改优化器或solidity版本、不可以修改测试文件,也不使用huff/yul/bytecode 层面的优化,也不变更函数属性(不能使函数为 "payable")。
在进一步阅读之前,请确保你自己先尝试解决这个难题,它真的很有趣,而且能让你学到东西。

让我们来优化吧!

让我们先做一些明显的事情。

  • 变更变量为不可变(immutable)变量,而不是存储变量(节省 10710Gas--因为不可变变量是作为常量嵌入合约字节码的,而不是从存储中读取)。
  • 用 send 取代 transfer(节省108 Gas--因为send没有检查转账是否成功)。
  • 用endTime代替currentTime (节省了69个Gas - 在构造函数中进行时间计算)

以下是修改后的合约:

这样我们就可以让Gas 节约到61066,已经比原来的好了10887,但仍比目标值高出 4千, 需继续努力。

那么,还有什么诀窍呢?

这个挑战有一个特别的技巧,应该教给你。而这是一种通过SELFDESTRUCT向地址发送ETH的老方法:

通过Selfdestruct 解谜, 仅仅这一招就为你节省了4066*的Gas,并且达到了目标!

但如果继续深入呢?

每一个理智的优化总是有其疯狂的邪恶兄弟(作弊)......让我们看看我们能把这个推到什么程度!
让合约自毁(selfdestruct)感觉像是在作弊 -- 为什么你会有一个这样的合约,在第一次调用分发后就不存在了呢?不过测试通过,所以我想这是允许的......?

让我们看看还有什么(真正的作弊)能让测试通过,但又能优化更多Gas。

  • 让contributors成为常数,而不是不可变(immutable)(节省24个Gas - 因为Hardhat地址总是相同的,所以可以这样做,对吧?
  • 使金额恒定为0.25ETH,而不是从余额中计算(节省了106个Gas -- 因为在测试中金额总是相同的,所以为什么不这样做呢?)
  • 使用Assembly来做call,而不是通常的solidityaddress.send(节省9 0 Gas)。


到目前为止使用的 Gas 是 56780

还可以更进一步吗?

把地址作为字节32的数字直接放在调用中,可以为我们多节省9个Gas!

在调用中的手动用零填充地址, 可以节约到 56771 Gas

我们继续走下去如何?

既然我们已经针对测试上做优化,那我们就进一步
为什么我们不直接返回,如果测试是 "Gas目标"?
知道Hardhat总是在相同的区块链中运行测试,我们可以把这个代码放在 distribute()函数的开头:

if (block.number == 5) return;

这样在测试中,测量 gas 时,直接返回,而不会消耗很多Gas,但仍将在 "逻辑检查" 测试中完成了的功能检查:)
不明白的同学可以回顾这里的测试用例代码,测试用例会先检查 Gas 是否达标再检查是否"逻辑"满足。


这给了我们一个惊人的21207Gas! 令人难以置信吧,不过你明白这里发生了什么......

但,这就是作弊!

是的,但谨记,这样的作弊经常在链上发生,MEV,漏洞,代码即法律,以及所有其他的东西。

还记得最近的Gas挑战比赛吗,最佳优化者获得了NFT?在那里,没有人在解决原来的问题--一切都在 "欺骗 "智能合约,以接受所需的值,并通过所需的测试,以最低的字节码和最低的Gas--你就能得到NFT。

这也给我们了一些启示,更好的测试和更好的条件可以规避这些作弊,同时让程序更安全。
对于开发我们更应该注重有效的程序,而不仅仅是巧妙地入侵系统。

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