该文章介绍了Thanos网络主网启动前,预先部署USDC Bridge合约的过程,以支持Layer 1和Layer 2之间无缝的资产转移。文章详细描述了每个合约的功能,以及开发过程中使用的相关软件包和代码仓库,包括op-bindings和op-chain-ops等。

在 Thanos 网络主网启动之前,TOP 项目团队正在努力为用户提供改进的和更可靠的服务。
为此,我们正在预部署 USDC Bridge 合约,以支持 Layer 1 和 Layer 2 之间的无缝资产转移。这种预部署确保了在网络上线之前,必要的功能已经到位并保持稳定,从而使我们能够立即为用户提供服务。
这个开发故事涵盖了在 Thanos 网络上预部署合约以支持 USDC Bridge 的过程、每个合约的描述,以及关于开发这些合约的软件包和仓库的信息。
Thanos 开发故事系列
关于 Thanos 网络的详细信息和源代码可以在这里找到,USDC Bridge 预部署的源代码也可以找到。用于预部署的合约位于 tokamak-thanos/packages/tokamak/contracts-bedrock/src/tokamak-contracts/USDC。
为 USDC Bridge 预部署开发的代码使用了 op-bindings 和 op-chain-ops 软件包。
op-bindings 软件包还包括一个 addresses.go 文件,该文件定义了所有地址以支持鲁棒性和可维护性。op-chains-ops 软件包包含诸如 config.go、immutables.go 和 setters.go 之类的文件,这些文件在预部署期间与位于 op-bindings 软件包中的绑定和 addresses.go 结合使用。为了支持 USDC Bridge,需要各种合约。我们部署了 USDC Bridge 合约以在每一层上交易 USDC,并在 Layer 2 上部署 USDC 以与 Layer 1 上的 USDC 无缝交易。
Layer 1
Layer 2
Layer 2 - USDC 合约
上述合约的预部署在网络上线之前提供了必要的功能,从而实现了可靠而高效的 USDC 交易和资产管理。
预部署合约涉及几个步骤。
首先,我们将描述在 Layer 2 预部署 L2UsdcBridge、L2UsdcBridgeProxy 和 USDC 合约的过程,以及随之而来的代码更改。
将合约预部署到 Layer 2 涉及预定义和初始化每个合约的地址。在 Layer 2 预部署的所有合约的地址均以 "0x42" 开头,以方便识别和管理。
此外,合约的处理方式取决于它们是否使用代理模式。不使用代理模式的合约设置 ProxyDisabled: true。涉及 SignatureChecker 和 MasterMinter,并且 ProxyDisabled: true 设置将应用于这些合约。
addresses.go – Medium
| const{ | |
| L2UsdcBridge="0x4200000000000000000000000000000000000775" | |
| SignatureChecker="0x4200000000000000000000000000000000000776" | |
| MasterMinter="0x4200000000000000000000000000000000000777" | |
| FiatTokenV2_2="0x4200000000000000000000000000000000000778" | |
| } | |
| funcinit() { | |
| Predeploys["L2UsdcBridge"] =&Predeploy{Address: L2UsdcBridgeAddr} | |
| Predeploys["SignatureChecker"] =&Predeploy{Address: SignatureCheckerAddr, ProxyDisabled: true} | |
| Predeploys["MasterMinter"] =&Predeploy{Address: MasterMinterAddr, ProxyDisabled: true} | |
| Predeploys["FiatTokenV2_2"] =&Predeploy{Address: FiatTokenV2_2Addr} | |
| } |
// addresses.go 代码示例
const {
L2UsdcBridge="0x4200000000000000000000000000000000000775"
SignatureChecker="0x4200000000000000000000000000000000000776"
MasterMinter="0x4200000000000000000000000000000000000777"
FiatTokenV2_2="0x4200000000000000000000000000000000000778"
}
func init() {
Predeploys["L2UsdcBridge"] = &Predeploy{Address: L2UsdcBridgeAddr}
Predeploys["SignatureChecker"] = &Predeploy{Address: SignatureCheckerAddr, ProxyDisabled: true}
Predeploys["MasterMinter"] = &Predeploy{Address: MasterMinterAddr, ProxyDisabled: true}
Predeploys["FiatTokenV2_2"] = &Predeploy{Address: FiatTokenV2_2Addr}
}
此过程在定义 Layer 2 网络的配置设置方面起着重要作用。如果合约具有构造函数,则通过 NewL2ImmutableConfig 定义它,并通过 NewL2StorageConfig 设置需要存储初始化的地址和值。
构造函数特定于 MasterMinter,并且存储设置定义了除了 SignatureChecker 以外的合约所需的地址和初始值。
具体来说,MasterMinter 的构造函数是 _minterManager,它设置了预部署的 FiatTokenV2_2 的地址。
存储定义了初始地址值,例如 l1Usdc、l2Usdc 等,以供 L2UsdcBridge 与 Layer 1 上的 UsdcBridge 交互,而 MasterMinter 定义了用于提供权限的地址值,例如 owner、controllers 等。最后,FiatTokenV2_2 初始化 USDC 的基本信息,例如 name、symbol、decimals 、currency 等。
这些设置为每个合约的初始状态及其与 Layer 1 的交互奠定了基础。
config.go – Medium
| funcNewL2ImmutableConfig(config*DeployConfig, block*types.Block) (immutables.ImmutableConfig, error) { | |
| immutable:=make(immutables.ImmutableConfig) | |
| immutable["L2UsdcBridge"] = immutables.ImmutableValues{} | |
| immutable["SignatureChecker"] = immutables.ImmutableValues{} | |
| immutable["MasterMinter"] = immutables.ImmutableValues{ | |
| "_minterManager": predeploys.FiatTokenV2_2Addr, | |
| } | |
| immutable["FiatTokenV2_2"] = immutables.ImmutableValues{} | |
| returnimmutable, nil | |
| } | |
| ... | |
| funcNewL2StorageConfig(config*DeployConfig, block*types.Block) (state.StorageConfig, error) { | |
| storage:=make(state.StorageConfig) | |
| storage["L2UsdcBridge"] = state.StorageValues{ | |
| "messenger": predeploys.L2CrossDomainMessengerAddr, | |
| "otherBridge": config.L1UsdcBridgeProxy, | |
| "l1Usdc": config.L1UsdcAddr, | |
| "l2Usdc": predeploys.FiatTokenV2_2Addr, | |
| "l2UsdcMasterMinter": predeploys.MasterMinterAddr, | |
| } | |
| storage["MasterMinter"] = state.StorageValues{ | |
| "_owner": config.MasterMinterOwner, | |
| "controllers": map[any]any{ | |
| predeploys.L2UsdcBridgeAddr: predeploys.L2UsdcBridgeAddr, | |
| }, | |
| "minterManager": predeploys.FiatTokenV2_2Addr, | |
| } | |
| storage["FiatTokenV2_2"] = state.StorageValues{ | |
| "_owner": config.FiatTokenOwner, | |
| "pauser": config.NewPauser, | |
| "blacklister": config.NewBlacklister, | |
| "name": config.UsdcTokenName, | |
| "symbol": "USDC.e", | |
| "decimals": 6, | |
| "currency": "USD", | |
| "masterMinter": predeploys.MasterMinterAddr, | |
| "initialized": true, | |
| "_initializedVersion": 3, | |
| } | |
| returnstorage, nil | |
| } |
// config.go 代码示例
func NewL2ImmutableConfig(config *DeployConfig, block *types.Block) (immutables.ImmutableConfig, error) {
immutable := make(immutables.ImmutableConfig)
immutable["L2UsdcBridge"] = immutables.ImmutableValues{}
immutable["SignatureChecker"] = immutables.ImmutableValues{}
immutable["MasterMinter"] = immutables.ImmutableValues{
"_minterManager": predeploys.FiatTokenV2_2Addr,
}
immutable["FiatTokenV2_2"] = immutables.ImmutableValues{}
return immutable, nil
}
func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.StorageConfig, error) {
storage := make(state.StorageConfig)
storage["L2UsdcBridge"] = state.StorageValues{
"messenger": predeploys.L2CrossDomainMessengerAddr,
"otherBridge": config.L1UsdcBridgeProxy,
"l1Usdc": config.L1UsdcAddr,
"l2Usdc": predeploys.FiatTokenV2_2Addr,
"l2UsdcMasterMinter": predeploys.MasterMinterAddr,
}
storage["MasterMinter"] = state.StorageValues{
"_owner": config.MasterMinterOwner,
"controllers": map[any]any{
predeploys.L2UsdcBridgeAddr: predeploys.L2UsdcBridgeAddr,
},
"minterManager": predeploys.FiatTokenV2_2Addr,
}
storage["FiatTokenV2_2"] = state.StorageValues{
"_owner": config.FiatTokenOwner,
"pauser": config.NewPauser,
"blacklister": config.NewBlacklister,
"name": config.UsdcTokenName,
"symbol": "USDC.e",
"decimals": 6,
"currency": "USD",
"masterMinter": predeploys.MasterMinterAddr,
"initialized": true,
"_initializedVersion": 3,
}
return storage, nil
}
immutables.go 负责处理将在 Layer 2 网络上预部署的合约中的不可变变量。
PredeploysImmutableConfig 结构定义了在 Layer 2 使用的预部署合约初始化所需的数据结构。
对于 MasterMinter,它被定义为具有 MinterManager 字段的结构,该字段表示 MasterMinter 合约初始化所需的地址。其他合约被定义为空结构。
immutables.go – Medium
| typePredeploysImmutableConfigstruct { | |
| L2UsdcBridgestruct{} | |
| SignatureCheckerstruct{} | |
| MasterMinterstruct { | |
| MinterManager common.Address | |
| } | |
| FiatTokenV2_2struct{} | |
| } |
// immutables.go 代码示例
type PredeploysImmutableConfig struct {
L2UsdcBridge struct{}
SignatureChecker struct{}
MasterMinter struct {
MinterManager common.Address
}
FiatTokenV2_2 struct{}
}
l2ImmutableDeployer 函数负责部署预部署合约中包含不可变变量的合约。
deployment.Args[0] 表示构造函数,它检查 _minterManager(MasterMinter 的构造函数)的地址是否为 common.Address 类型。
如果类型正确,它将调用 bindings.DeployMasterMinter 函数来部署 MasterMinter 合约,使用 _minterManager 地址作为初始化参数。
immutables.go – Medium
| // l2ImmutableDeployer will deploy L2 predeploys that contain immutables to the simulated backend. | |
| // It only needs to care about the predeploys that have immutables so that the deployed bytecode | |
| // has the dynamic value set at the correct location in the bytecode. | |
| funcl2ImmutableDeployer(backend*backends.SimulatedBackend, opts*bind.TransactOpts, deployment deployer.Constructor) (*types.Transaction, error) { | |
| case"MasterMinter": | |
| _minterManager, ok:=deployment.Args[0].(common.Address) | |
| if!ok { | |
| returnnil, fmt.Errorf("invalid type for _minterManager") | |
| } | |
| _, tx, _, err=bindings.DeployMasterMinter(opts, backend, _minterManager) | |
| } |
// immutables.go 代码示例
// l2ImmutableDeployer 将把包含 immutables 的 L2 预部署合约部署到模拟后端。
// 它只需要关心那些具有 immutables 的预部署合约,以便部署的字节码
// 在字节码中的正确位置设置了动态值。
func l2ImmutableDeployer(backend *backends.SimulatedBackend, opts *bind.TransactOpts, deployment deployer.Constructor) (*types.Transaction, error) {
case "MasterMinter":
_minterManager, ok := deployment.Args[0].(common.Address)
if !ok {
return nil, fmt.Errorf("invalid type for _minterManager")
}
_, tx, _, err = bindings.DeployMasterMinter(opts, backend, _minterManager)
}
}
setters.go 负责特定代理合约的管理设置。
L2UsdcBridgeProxy 和 FiatTokenProxy 使用不同的代理实现,因此 AdminSlot 和 ImplementationSlot 的地址不同。L2UsdcBridgeProxy 使用与 Thanos 网络使用的代理相同的插槽地址,而 FiatTokenProxy 使用与 Zepplin 库关联的插槽地址。
setters.go – Medium
| var ( | |
| // ImplementationSlot represents the EIP 1967 implementation storage slot | |
| ImplementationSlot=common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc") | |
| // AdminSlot represents the EIP 1967 admin storage slot | |
| AdminSlot=common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103") | |
| // implementationSlot represents the org.zeppelinos.proxy.implementation storage slot | |
| ImplementationSlotForZepplin=common.HexToHash("0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3") | |
| // implementationSlot represents org.zeppelinos.proxy.admin storage slot | |
| AdminSlotForZepplin=common.HexToHash("0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b") | |
| ) |
// setters.go 代码示例
var (
// ImplementationSlot represents the EIP 1967 implementation storage slot
ImplementationSlot = common.HexToHash("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")
// AdminSlot represents the EIP 1967 admin storage slot
AdminSlot = common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")
// implementationSlot represents the org.zeppelinos.proxy.implementation storage slot
ImplementationSlotForZepplin = common.HexToHash("0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3")
// implementationSlot represents org.zeppelinos.proxy.admin storage slot
AdminSlotForZepplin = common.HexToHash("0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b")
)
setProxies 函数在 setters.go 中将代理合约的字节码部署到 addresses.go 中定义的预部署地址。这将设置代理模式以将调用委托给实际的实现合约。
具体来说,predeploys.L2UsdcBridgeAddr 地址使用 SetCode 函数定义了 L2UsdcBridgeProxy 的字节码,并使用 SetState 函数存储了 AdminSlot 地址,以授予管理合约的权限。类似地,对于 predeploys.FiatTokenV2_2Addr 地址,我们设置了 FiatTokenProxy 的字节码,并为其提供了 AdminSlotForZepplin 地址以设置其代理权限。
这就是每个代理合约的字节码如何分发、进行初始设置和授权管理的方式。
setters.go – Medium
| funcsetProxies(db vm.StateDB, proxyAdminAddr common.Address, namespace*big.Int, countuint64) error { | |
| depBytecode, err:=bindings.GetDeployedBytecode("Proxy") | |
| iferr!=nil { | |
| returnerr | |
| } | |
| iflen(depBytecode) ==0 { | |
| returnerrors.New("Proxy has empty bytecode") | |
| } | |
| l2UsdcBridgeProxyBytecode, err:=bindings.GetDeployedBytecode("L2UsdcBridgeProxy") | |
| iferr!=nil { | |
| returnerr | |
| } | |
| iflen(l2UsdcBridgeProxyBytecode) ==0 { | |
| returnerrors.New("the contract L2UsdcBridgeProxy has empty bytecode") | |
| } | |
| fiatTokenProxyBytecode, err:=bindings.GetDeployedBytecode("FiatTokenProxy") | |
| iferr!=nil { | |
| returnerr | |
| } | |
| iflen(fiatTokenProxyBytecode) ==0 { | |
| returnerrors.New("the contract FiatTokenProxy has empty bytecode") | |
| } | |
| fori:=uint64(0); i<=count; i++ { | |
| bigAddr:=new(big.Int).Or(namespace, new(big.Int).SetUint64(i)) | |
| addr:=common.BigToAddress(bigAddr) | |
| if!db.Exist(addr) { | |
| db.CreateAccount(addr) | |
| } | |
| switchaddr { | |
| casepredeploys.L2UsdcBridgeAddr: | |
| db.SetCode(addr, l2UsdcBridgeProxyBytecode) | |
| db.SetState(addr, AdminSlot, eth.AddressAsLeftPaddedHash(proxyAdminAddr)) | |
| casepredeploys.FiatTokenV2_2Addr: | |
| db.SetCode(addr, fiatTokenProxyBytecode) | |
| db.SetState(addr, AdminSlotForZepplin, eth.AddressAsLeftPaddedHash(proxyAdminAddr)) | |
| default: | |
| db.SetCode(addr, depBytecode) | |
| db.SetState(addr, AdminSlot, eth.AddressAsLeftPaddedHash(proxyAdminAddr)) | |
| } | |
| log.Trace("Set proxy", "address", addr, "admin", proxyAdminAddr) | |
| } | |
| returnnil | |
| } |
// setters.go 代码示例
func setProxies(db vm.StateDB, proxyAdminAddr common.Address, namespace *big.Int, count uint64) error {
depBytecode, err := bindings.GetDeployedBytecode("Proxy")
if err != nil {
return err
}
if len(depBytecode) == 0 {
return errors.New("Proxy has empty bytecode")
}
l2UsdcBridgeProxyBytecode, err := bindings.GetDeployedBytecode("L2UsdcBridgeProxy")
if err != nil {
return err
}
if len(l2UsdcBridgeProxyBytecode) == 0 {
return errors.New("the contract L2UsdcBridgeProxy has empty bytecode")
}
fiatTokenProxyBytecode, err := bindings.GetDeployedBytecode("FiatTokenProxy")
if err != nil {
return err
}
if len(fiatTokenProxyBytecode) == 0 {
return errors.New("the contract FiatTokenProxy has empty bytecode")
}
for i := uint64(0); i <= count; i++ {
bigAddr := new(big.Int).Or(namespace, new(big.Int).SetUint64(i))
addr := common.BigToAddress(bigAddr)
if !db.Exist(addr) {
db.CreateAccount(addr)
}
switch addr {
case predeploys.L2UsdcBridgeAddr:
db.SetCode(addr, l2UsdcBridgeProxyBytecode)
db.SetState(addr, AdminSlot, eth.AddressAsLeftPaddedHash(proxyAdminAddr))
case predeploys.FiatTokenV2_2Addr:
db.SetCode(addr, fiatTokenProxyBytecode)
db.SetState(addr, AdminSlotForZepplin, eth.AddressAsLeftPaddedHash(proxyAdminAddr))
default:
db.SetCode(addr, depBytecode)
db.SetState(addr, AdminSlot, eth.AddressAsLeftPaddedHash(proxyAdminAddr))
}
log.Trace("Set proxy", "address", addr, "admin", proxyAdminAddr)
}
return nil
}
在 layer_two.go 中,我们定义了构建 Layer 2 创世区块的过程涉及的函数。
如你在下面的代码中看到的,layer_two.go 调用 NewL2StorageConfig 和 NewL2ImmutableConfig 函数来配置存储和配置 immutable 的设置。它还以规定的格式定义了每个代理合约和实现合约的地址。实现合约以 “0xc0D3C0d3C0d3C0d3C0d3C0d3C0d3C0d3C0d3C0d3C0d30000” 这样的地址格式部署。
L2UsdcBridge: 0xC0D3c0D3c0D3C0D3c0D3c0d3C0D3c0d3c0D30775
L2UsdcBridgeProxy: 0x4200000000000000000000000000000000000775
FiatTokenV2_2: 0xc0D3c0d3C0D3C0d3C0D3C0D3C0D3c0D3C0D30778
FiatTokenProxy: 0x4200000000000000000000000000000000000778
SignatureChecker 是 FiatTokenV2_2 中的一个库。库的特点是在分发时,它们的字节码中包含了它们的分发地址。但是,无法在预部署过程中修改库的字节码,因此我们需要一种将 SignatureChecker 的预部署地址硬编码到字节码中的方法。为此,我们使用 case 语句将 SignatureChecker 的预发布地址插入到字节码中。
layer_two.go – Medium
| // BuildL2Genesis will build the L2 genesis block. | ||
| funcBuildL2Genesis(config*DeployConfig, l1StartBlock*types.Block) (*core.Genesis, error) { | ||
| genspec, err:=NewL2Genesis(config, l1StartBlock) | ||
| iferr!=nil { | ||
| returnnil, err | ||
| } | ||
| db:=state.NewMemoryStateDB(genspec) | ||
| ifconfig.FundDevAccounts { | ||
| log.Info("Funding developer accounts in L2 genesis") | ||
| FundDevAccounts(db) | ||
| } | ||
| ifconfig.SetPrecompileBalances { | ||
| log.Info("Setting precompile balances in L2 genesis") | ||
| SetPrecompileBalances(db) | ||
| } | ||
| storage, err:=NewL2StorageConfig(config, l1StartBlock) | ||
| iferr!=nil { | ||
| returnnil, err | ||
| } | ||
| immutableConfig, err:=NewL2ImmutableConfig(config, l1StartBlock) | ||
| iferr!=nil { | ||
| returnnil, err | ||
| } | ||
| // Set up the proxies | ||
| err=setProxies(db, predeploys.ProxyAdminAddr, BigL2PredeployNamespace, 2048) | ||
| iferr!=nil { | ||
| returnnil, err | ||
| } | ||
| // Set up the implementations that contain immutables | ||
| deployResults, err:=immutables.Deploy(immutableConfig) | ||
| iferr!=nil { | ||
| returnnil, err | ||
| } | ||
| forname, predeploy:=rangepredeploys.Predeploys { | ||
| ifpredeploy.Enabled!=nil&&!predeploy.Enabled(config) { | ||
| log.Warn("Skipping disabled predeploy.", "name", name, "address", predeploy.Address) | ||
| continue | ||
| } | ||
| codeAddr:=predeploy.Address | ||
| switchname { | ||
| case"SignatureChecker": | ||
| bytecode, err:=bindings.GetDeployedBytecode(name) | ||
| iferr!=nil { | ||
| returnnil, err | ||
| } | ||
| a:=bytecode[:1] | ||
| b:=bytecode[21:] | ||
| c:= hexutil.Bytes{} | ||
| d, _:=hex.DecodeString("4200000000000000000000000000000000000776") | ||
| c=append(c, a...) | ||
| c=append(c, d...) | ||
| c=append(c, b...) | ||
| bytecode=c | ||
| deployResults[name] =bytecode | ||
| case"FiatTokenV2_2": | ||
| codeAddr, err=AddressToCodeNamespace(predeploy.Address) | ||
| iferr!=nil { | ||
| returnnil, fmt.Errorf("error converting to code namespace: %w", err) | ||
| } | ||
| db.CreateAccount(codeAddr) | ||
| db.SetState(predeploy.Address, ImplementationSlotForZepplin, eth.AddressAsLeftPaddedHash(codeAddr)) | ||
| log.Info("Set proxy for FiatTokenV2_2", "name", name, "address", predeploy.Address, "implementation", codeAddr) | ||
| default: | ||
| if!predeploy.ProxyDisabled { | ||
| codeAddr, err=AddressToCodeNamespace(predeploy.Address) | ||
| iferr!=nil { | ||
| returnnil, fmt.Errorf("error converting to code namespace: %w", err) | ||
| } | ||
| db.CreateAccount(codeAddr) | ||
| db.SetState(predeploy.Address, ImplementationSlot, eth.AddressAsLeftPaddedHash(codeAddr)) | ||
| log.Info("Set proxy", "name", name, "address", predeploy.Address, "implementation", codeAddr) | ||
| } | 最后,我们使用 L1UsdcBridgeProxy 的 setAddress 函数来初始化代理需要引用的其他合约的地址,这需要Layer2的 USDC 地址和 UsdcBridge 地址,以便与Layer2的预部署合约交互并向其传输数据。 |
在Layer1,部署 USDC 桥接完成了该过程。在这些层之间预部署 USDC 桥接的好处是,涉及 USDC 的交易可以高效处理,并且可以更无缝地跟踪 USDC 存款和取款事件。
以上描述了在Layer1和Layer2上预部署 USDC 桥接和 USDC 相关合约的过程,从而可以通过 USDC 桥接在 Thanos 网络上实现 USDC 资产交易。
Thanos 开发故事的下一部分是关于 UniswapV3 的预部署开发,我们将讨论在 Thanos 网络上预部署 UniswapV3 的好处以及开发过程。
- 原文链接: medium.com/tokamak-netwo...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!