Skip to content

Commit

Permalink
feat(e2e): create FeeOracleV2 deployer (#2477)
Browse files Browse the repository at this point in the history
Created a FeeOracleV2 deployment routine that uses Create3. We cannot
upgrade FeeOracleV1 as V2 has a different storage layout. This routine
does not currently include upgrade logic.

issue: #1951
  • Loading branch information
Zodomo authored Nov 14, 2024
1 parent 283c20e commit daed86a
Show file tree
Hide file tree
Showing 7 changed files with 2,402 additions and 1 deletion.
2 changes: 1 addition & 1 deletion contracts/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ all: install-deps build bindings allocs ## Build contracts, generate bindings an
CORE_CONTRACTS := OmniPortal FeeOracleV1 Create3 TransparentUpgradeableProxy \
Staking Slashing OmniBridgeL1 OmniBridgeNative Omni WOmni \
PortalRegistry AllocPredeploys PingPong ProxyAdmin Admin \
OmniGasPump OmniGasStation
OmniGasPump OmniGasStation FeeOracleV2

SOLVE_CONTRACTS := SolveInbox SolveOutbox MockToken MockVault

Expand Down
1,993 changes: 1,993 additions & 0 deletions contracts/bindings/feeoraclev2.go

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions e2e/app/feeoraclev2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package app

import (
"context"

"github.com/omni-network/omni/lib/contracts/feeoraclev2"
"github.com/omni-network/omni/lib/errors"
"github.com/omni-network/omni/lib/log"
)

// DeployFeeOracleV2 deploys fee oracle v2.
func DeployFeeOracleV2(ctx context.Context, def Definition) error {
_, ok := def.Testnet.OmniEVMChain()
if !ok {
return errors.New("no omni evm chain")
}

allChains := def.Testnet.EVMChains()
chainIDs := getChainIDs(def)
backends := def.Backends()

for _, chain := range allChains {
addr, receipt, err := feeoraclev2.DeployIfNeeded(ctx, def.Testnet.Network, chain.ChainID, chainIDs, backends)
if err != nil {
return errors.Wrap(err, "deploy", "chain", chain.Name, "tx", maybeTxHash(receipt))
}

log.Info(ctx, "FeeOracleV2 deployed", "chain", chain.Name, "address", addr.Hex(), "tx", maybeTxHash(receipt))
}

return nil
}

func getChainIDs(def Definition) []uint64 {
allChains := def.Testnet.EVMChains()

chainIDs := make([]uint64, 0, len(allChains))
for _, chain := range allChains {
chainIDs = append(chainIDs, chain.ChainID)
}

return chainIDs
}
13 changes: 13 additions & 0 deletions e2e/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func New() *cobra.Command {
newERC20FaucetCmd(&def),
newDeployGasAppCmd(&def),
newDeployBridgeCmd(&def),
newDeployFeeOracleV2Cmd(&def),
fundAccounts(&def),
)

Expand Down Expand Up @@ -274,3 +275,15 @@ func newDeployBridgeCmd(def *app.Definition) *cobra.Command {

return cmd
}

func newDeployFeeOracleV2Cmd(def *app.Definition) *cobra.Command {
cmd := &cobra.Command{
Use: "deploy-feeoraclev2",
Short: "Deploys the FeeOracleV2 contract",
RunE: func(cmd *cobra.Command, _ []string) error {
return app.DeployFeeOracleV2(cmd.Context(), *def)
},
}

return cmd
}
11 changes: 11 additions & 0 deletions lib/contracts/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ type Addresses struct {
Token common.Address
SolveOutbox common.Address
SolveInbox common.Address
FeeOracleV2 common.Address
}

type Salts struct {
Expand All @@ -97,6 +98,7 @@ type Salts struct {
Token string
SolveOutbox string
SolveInbox string
FeeOracleV2 string
}

type cache[T any] struct {
Expand Down Expand Up @@ -141,6 +143,7 @@ func GetAddresses(ctx context.Context, network netconf.ID) (Addresses, error) {
GasStation: gasStation(network, ver),
SolveInbox: solveInbox(network, ver),
SolveOutbox: solveOutbox(network, ver),
FeeOracleV2: feeOracleV2(network, ver),
}

addrsCache.cache[network] = addrs
Expand Down Expand Up @@ -232,6 +235,10 @@ func solveOutbox(network netconf.ID, version string) common.Address {
return create3.Address(Create3Factory(network), solveOutboxSalt(network, version), eoa.MustAddress(network, eoa.RoleDeployer))
}

func feeOracleV2(network netconf.ID, version string) common.Address {
return create3.Address(Create3Factory(network), feeOracleV2Salt(network, version), eoa.MustAddress(network, eoa.RoleDeployer))
}

//
// Salts.
//
Expand Down Expand Up @@ -269,6 +276,10 @@ func solveOutboxSalt(network netconf.ID, version string) string {
return salt(network, "solve-outbox-"+version)
}

func feeOracleV2Salt(network netconf.ID, version string) string {
return salt(network, "fee-oracle-v2-"+version)
}

//
// Utils.
//
Expand Down
209 changes: 209 additions & 0 deletions lib/contracts/feeoraclev2/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package feeoraclev2

import (
"context"
"math/big"

"github.com/omni-network/omni/contracts/bindings"
"github.com/omni-network/omni/e2e/app/eoa"
"github.com/omni-network/omni/lib/contracts"
"github.com/omni-network/omni/lib/create3"
"github.com/omni-network/omni/lib/errors"
"github.com/omni-network/omni/lib/ethclient/ethbackend"
"github.com/omni-network/omni/lib/netconf"
"github.com/omni-network/omni/lib/tokens/coingecko"

"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
)

type DeploymentConfig struct {
Create3Salt string
Create3Factory common.Address
ExpectedAddr common.Address
ProxyAdminOwner common.Address
Owner common.Address
Deployer common.Address
Manager common.Address // manager is the address that can set fee parameters (gas price, conversion rates)
BaseGasLimit uint32 // must fit in uint24 (max: 16,777,215)
ProtocolFee *big.Int // must fit in uint72 (max: 4,722,366,482,869,645,213,695)
}

func isEmpty(addr common.Address) bool {
return addr == common.Address{}
}

var maxUint72 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 72), big.NewInt(1))

func (cfg DeploymentConfig) Validate() error {
if cfg.Create3Salt == "" {
return errors.New("create3 salt is empty")
}
if isEmpty(cfg.Create3Factory) {
return errors.New("create3 factory is zero")
}
if isEmpty(cfg.ExpectedAddr) {
return errors.New("expected address is zero")
}
if isEmpty(cfg.ProxyAdminOwner) {
return errors.New("proxy admin is zero")
}
if isEmpty(cfg.Owner) {
return errors.New("owner is zero")
}
if isEmpty(cfg.Deployer) {
return errors.New("deployer is zero")
}
if isEmpty(cfg.Manager) {
return errors.New("manager is zero")
}
if cfg.BaseGasLimit > (1<<24 - 1) {
return errors.New("base gas limit too high")
}
if cfg.ProtocolFee.Cmp(maxUint72) > 0 {
return errors.New("protocol fee too high")
}

return nil
}

// isDeployed returns true if the oracle contract is already deployed to its expected address.
func isDeployed(ctx context.Context, network netconf.ID, backend *ethbackend.Backend) (bool, common.Address, error) {
addrs, err := contracts.GetAddresses(ctx, network)
if err != nil {
return false, common.Address{}, errors.Wrap(err, "get addrs")
}

code, err := backend.CodeAt(ctx, addrs.FeeOracleV2, nil)
if err != nil {
return false, addrs.FeeOracleV2, errors.Wrap(err, "code at", "address", addrs.FeeOracleV2)
}

if len(code) == 0 {
return false, addrs.FeeOracleV2, nil
}

return true, addrs.FeeOracleV2, nil
}

// DeployIfNeeded deploys a new oracle contract if it is not already deployed.
// If the contract is already deployed, the receipt is nil.
func DeployIfNeeded(ctx context.Context, network netconf.ID, chainID uint64, destChainIDs []uint64, backends ethbackend.Backends) (common.Address, *ethtypes.Receipt, error) {
backend, err := backends.Backend(chainID)
if err != nil {
return common.Address{}, nil, errors.Wrap(err, "get backend")
}

deployed, addr, err := isDeployed(ctx, network, backend)
if err != nil {
return common.Address{}, nil, errors.Wrap(err, "is deployed")
}
if deployed {
return addr, nil, nil
}

return Deploy(ctx, network, chainID, destChainIDs, backend, backends)
}

// Deploy deploys a new FeeOracleV2 contract and returns the address and receipt.
func Deploy(ctx context.Context, network netconf.ID, chainID uint64, destChainIDs []uint64, backend *ethbackend.Backend, backends ethbackend.Backends) (common.Address, *ethtypes.Receipt, error) {
addrs, err := contracts.GetAddresses(ctx, network)
if err != nil {
return common.Address{}, nil, errors.Wrap(err, "get addresses")
}

salts, err := contracts.GetSalts(ctx, network)
if err != nil {
return common.Address{}, nil, errors.Wrap(err, "get salts")
}

cfg := DeploymentConfig{
Create3Salt: salts.FeeOracleV2,
Create3Factory: addrs.Create3Factory,
ExpectedAddr: addrs.FeeOracleV2,
ProxyAdminOwner: eoa.MustAddress(network, eoa.RoleUpgrader),
Owner: eoa.MustAddress(network, eoa.RoleManager),
Deployer: eoa.MustAddress(network, eoa.RoleDeployer),
Manager: eoa.MustAddress(network, eoa.RoleMonitor), // NOTE: monitor is owner of fee oracle contracts, because monitor manages on chain gas prices / conversion rates
BaseGasLimit: 100_000,
ProtocolFee: big.NewInt(0),
}

return deploy(ctx, chainID, destChainIDs, cfg, backend, backends)
}

func deploy(ctx context.Context, chainID uint64, destChainIDs []uint64, cfg DeploymentConfig, backend *ethbackend.Backend, backends ethbackend.Backends) (common.Address, *ethtypes.Receipt, error) {
if err := cfg.Validate(); err != nil {
return common.Address{}, nil, errors.Wrap(err, "validate config")
}

txOpts, err := backend.BindOpts(ctx, cfg.Deployer)
if err != nil {
return common.Address{}, nil, errors.Wrap(err, "bind opts")
}

factory, err := bindings.NewCreate3(cfg.Create3Factory, backend)
if err != nil {
return common.Address{}, nil, errors.Wrap(err, "new create3")
}

salt := create3.HashSalt(cfg.Create3Salt)

addr, err := factory.GetDeployed(nil, txOpts.From, salt)
if err != nil {
return common.Address{}, nil, errors.Wrap(err, "get deployed")
} else if (cfg.ExpectedAddr != common.Address{}) && addr != cfg.ExpectedAddr {
return common.Address{}, nil, errors.New("unexpected address", "expected", cfg.ExpectedAddr, "actual", addr)
}

impl, tx, _, err := bindings.DeployFeeOracleV2(txOpts, backend)
if err != nil {
return common.Address{}, nil, errors.Wrap(err, "deploy implementation")
}

_, err = backend.WaitMined(ctx, tx)
if err != nil {
return common.Address{}, nil, errors.Wrap(err, "wait mined implementation")
}

initCode, err := packInitCode(ctx, chainID, destChainIDs, cfg, backends, impl)
if err != nil {
return common.Address{}, nil, errors.Wrap(err, "pack init code")
}

tx, err = factory.DeployWithRetry(txOpts, salt, initCode) //nolint:contextcheck // Context is txOpts
if err != nil {
return common.Address{}, nil, errors.Wrap(err, "deploy proxy")
}

receipt, err := backend.WaitMined(ctx, tx)
if err != nil {
return common.Address{}, nil, errors.Wrap(err, "wait mined proxy")
}

return addr, receipt, nil
}

func packInitCode(ctx context.Context, chainID uint64, destChainIDs []uint64, cfg DeploymentConfig, backends ethbackend.Backends, impl common.Address) ([]byte, error) {
feeOracleAbi, err := bindings.FeeOracleV2MetaData.GetAbi()
if err != nil {
return nil, errors.Wrap(err, "get fee oracle abi")
}

proxyAbi, err := bindings.TransparentUpgradeableProxyMetaData.GetAbi()
if err != nil {
return nil, errors.Wrap(err, "get proxy abi")
}

feeparams, err := feeParams(ctx, chainID, destChainIDs, backends, coingecko.New())
if err != nil {
return nil, errors.Wrap(err, "fee params")
}

initializer, err := feeOracleAbi.Pack("initialize", cfg.Owner, cfg.Manager, cfg.BaseGasLimit, cfg.ProtocolFee, feeparams)
if err != nil {
return nil, errors.Wrap(err, "pack initialize")
}

return contracts.PackInitCode(proxyAbi, bindings.TransparentUpgradeableProxyBin, impl, cfg.ProxyAdminOwner, initializer)
}
Loading

0 comments on commit daed86a

Please sign in to comment.