UDC Appchain 部署

虽然通用部署合约 (UDC) 部署在 Starknet 公共网络上,但应用链可能需要部署它们自己的 UDC 实例以供自己使用。本指南将引导您完成此过程,同时在所有网络上保持相同的最终地址。

前提条件

本指南假定您已具备以下条件:

  • 熟悉 Scarb 和 Starknet 开发环境。

  • 在您要部署到的网络上有一个可用的功能帐户。

  • 熟悉通过 声明交易 声明合约的过程。

注意:要在 Starknet 上声明合约,您可以使用 starknet-foundry 项目中的 sncast 工具。

关于 UDC 最终地址的说明

重要的是,Starknet 中的通用部署合约 (UDC) 在所有网络上保持 相同的地址,因为诸如 starklisncast 之类的基本开发者工具在部署合约时默认依赖于此地址。这些工具在 Starknet 生态系统中被广泛使用,以简化和标准化合约部署工作流程。

如果 UDC 地址是一致的,开发者可以编写部署脚本、CI/CD 管道和集成,这些脚本、管道和集成可以在测试网、主网和应用链上无缝地工作,而无需更新配置文件或处理每个环境的特殊情况。

在以下部分中,我们将引导您完成在应用链上部署 UDC 的过程,同时保持相同的地址,但有一个重要的假设:声明的 UDC 类哈希在所有网络上必须相同。不同的编译器版本可能会为相同的合约生成不同的类哈希,因此您需要确保使用相同的编译器版本来构建 UDC 类(以及发布配置文件)。

openzeppelin_presets 包中提供的最新版本的 UDC 是使用 Cairo v2.11.4(发布配置文件)编译的,生成的类哈希是 0x01b2df6d8861670d4a8ca4670433b2418d78169c2947f46dc614e69f333745c8

重要提示:如果您使用不同的编译器版本,则需要确保类哈希与上面的类哈希相同,以便在所有网络上保持相同的地址。

为了避免因使用不同版本的编译器而导致的问题,您可以直接导入部署在 Starknet 主网上的合约类,并在您的应用链上声明它。在撰写本文时,使用 sncast 工具无法轻松实现此目的,但您可以利用 starkli 来实现。

快速参考:

starkli class-by-hash --parse \
    0x01b2df6d8861670d4a8ca4670433b2418d78169c2947f46dc614e69f333745c8 \
    --network mainnet \
    > udc.json

这将输出一个 udc.json 文件,您可以使用该文件在您的应用链上声明 UDC。

starkli declare udc.json --rpc <rpc-url>

Madara 应用链

Madara 是一种流行的 Starknet 节点实现,它具有友好而强大的界面,用于构建应用链。如果您将其用于此目的,您可能熟悉 Madara 引导程序,当您创建新的应用链时,它已经为您声明和部署了一些合约,包括帐户和 UDC。

但是,由于 UDC 已在 2025 年 6 月迁移到新版本,因此应用链可能是在此更改之前创建的,这意味着应用链上的 UDC 是旧版本。如果是这种情况,您可以按照以下步骤部署新的 UDC。

1. 声明并部署引导程序

在 Starknet 生态系统中,合约需要在部署之前声明,并且部署只能通过 deploy_syscall 或使用 deploy_account 交易来完成。后者需要向 UDC 添加帐户功能,这不是最佳选择,因此我们将使用 deploy_syscall,这需要拥有一个启用了此功能的帐户。

注意:Madara 声明一个启用了此功能的帐户作为引导过程的一部分。您或许可以直接使用该实现来跳过此步骤。

引导程序合约

引导程序合约是一个简单的合约,它声明 UDC 并允许通过 deploy_syscall 部署它。您可以在下面找到一个参考实现:

注意:此参考实现面向 Cairo v2.11.4。如果您使用的是不同版本的 Cairo,您可能需要更新代码以匹配您的编译器版本。

#[starknet::contract(account)]
mod UniversalDeployerBootstrapper {
    use core::num::traits::Zero;
    use openzeppelin_account::AccountComponent;
    use openzeppelin_introspection::src5::SRC5Component;
    use openzeppelin_utils::deployments::calculate_contract_address_from_deploy_syscall;
    use starknet::{ClassHash, ContractAddress, SyscallResultTrait};

    component!(path: AccountComponent, storage: account, event: AccountEvent);
    component!(path: SRC5Component, storage: src5, event: SRC5Event);

    //
    // Account features (deployable, declarer, and invoker)
    //

    #[abi(embed_v0)]
    pub(crate) impl DeployableImpl =
        AccountComponent::DeployableImpl<ContractState>;
    #[abi(embed_v0)]
    impl DeclarerImpl = AccountComponent::DeclarerImpl<ContractState>;
    #[abi(embed_v0)]
    impl SRC6Impl = AccountComponent::SRC6Impl<ContractState>;
    impl AccountInternalImpl = AccountComponent::InternalImpl<ContractState>;

    #[storage]
    struct Storage {
        #[substorage(v0)]
        pub account: AccountComponent::Storage,
        #[substorage(v0)]
        pub src5: SRC5Component::Storage,
    }

    #[event]
    #[derive(Drop, starknet::Event)]
    pub(crate) enum Event {
        #[flat]
        AccountEvent: AccountComponent::Event,
        #[flat]
        SRC5Event: SRC5Component::Event,
    }

    #[constructor]
    pub fn constructor(ref self: ContractState, public_key: felt252) {
        self.account.initializer(public_key);
    }

    #[abi(per_item)]
    #[generate_trait]
    impl ExternalImpl of ExternalTrait {
        #[external(v0)]
        fn deploy_udc(ref self: ContractState, udc_class_hash: ClassHash) {
            self.account.assert_only_self();
            starknet::syscalls::deploy_syscall(udc_class_hash, 0, array![].span(), true)
                .unwrap_syscall();
        }

        #[external(v0)]
        fn get_udc_address(ref self: ContractState, udc_class_hash: ClassHash) -> ContractAddress {
            calculate_contract_address_from_deploy_syscall(
                0, udc_class_hash, array![].span(), Zero::zero(),
            )
        }
    }
}

部署引导程序

本指南假定您在要部署到的网络上有一个可用的功能帐户,并且熟悉通过 declare 交易声明合约的过程。回顾一下,我们部署此引导程序帐户合约的原因是为了能够通过 deploy_syscall 部署 UDC。

提示:以下示例中使用了 sncast v0.45.0。

作为一个快速示例,如果您的帐户已配置为 sncast,您可以使用以下命令声明引导程序合约:

sncast -p <profile-name> declare \
    --contract-name UniversalDeployerBootstrapper

引导程序实现了 IDeployable 特性,这意味着它可以进行反事实部署。查阅反事实部署指南。继续使用 sncast 示例,您可以使用以下命令创建并部署引导程序:

创建帐户
sncast account create --name bootstrapper \
    --network <network-name> \
    --class-hash <declared-class-hash> \
    --type oz
将其部署到网络

注意:您需要预先为帐户提供足够的资金,然后才能部署它。

sncast account deploy \
    --network <network-name> \
    --name bootstrapper

2. 声明并部署 UDC

部署引导程序后,您可以通过它声明并部署 UDC。

声明 UDC

UDC 源代码可在 openzeppelin_presets 包中找到。您可以将其复制到您的项目并使用以下命令声明它:

sncast -p <profile-name> declare \
    --contract-name UniversalDeployer

注意:如果您遵循了 关于 UDC 最终地址的说明 部分,您的声明类哈希应为 0x01b2df6d8861670d4a8ca4670433b2418d78169c2947f46dc614e69f333745c8

预览 UDC 地址

您可以使用以下命令预览 UDC 地址:

sncast call \
  --network <network-name> \
  --contract-address <bootstrapper-address> \
  --function "get_udc_address" \
  --arguments '<udc-class-hash>'

如果 UDC 类哈希与 关于 UDC 最终地址的说明 部分中的类哈希相同,则输出应为 0x2ceed65a4bd731034c01113685c831b01c15d7d432f71afb1cf1634b53a2125

部署 UDC

现在一切都已设置为部署 UDC。您可以使用以下命令部署它:

重要提示:请注意,引导程序合约必须调用自身才能成功部署 UDC,因为 deploy_udc 函数受到保护。

sncast \
  --account bootstrapper \
  invoke \
  --network <network-name> \
  --contract-address <bootstrapper-address> \
  --function "deploy_udc" \
  --arguments '<udc-class-hash>'

其他 Appchain 提供商

如果您使用的应用链提供商与 Madara 不同,只要您可以访问可以声明合约的帐户,您就可以按照相同的步骤部署 UDC。

总结一下,要遵循的步骤是:

  1. 声明引导程序

  2. 反事实部署引导程序

  3. 声明 UDC

  4. 预览 UDC 地址

  5. 从引导程序部署 UDC

结论

通过遵循本指南,您已成功地在您的应用链上部署了通用部署合约,同时确保与 Starknet 的公共网络保持一致。在所有环境中维护相同的 UDC 地址和类哈希对于无缝合约部署和工具兼容性至关重要,从而使开发者能够利用 sncaststarkli 等工具而无需额外的配置。此过程不仅提高了您的部署工作流程的可靠性,而且确保您的应用链与更广泛的 Starknet 生态系统保持兼容。正确部署 UDC 后,您现在可以充分利用简化后的合约部署和应用链上强大的开发工具。