该文章介绍了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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!