Skip to content

Commit

Permalink
feat(e2e): add gas pump deployments and tests (#1805)
Browse files Browse the repository at this point in the history
Adds gas pump / station deployments to e2e app / tests.

issue: #1686
  • Loading branch information
kevinhalliday authored Sep 4, 2024
1 parent 5f7b815 commit d282d92
Show file tree
Hide file tree
Showing 10 changed files with 351 additions and 34 deletions.
2 changes: 1 addition & 1 deletion contracts/bindings/omnigaspump.go

Large diffs are not rendered by default.

34 changes: 20 additions & 14 deletions contracts/bindings/omnigasstation.go

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions contracts/core/.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ OmniGasPump_Test:test_setMaxSwap() (gas: 34771)
OmniGasPump_Test:test_setOmniGasStation() (gas: 36697)
OmniGasPump_Test:test_setOracle() (gas: 35014)
OmniGasPump_Test:test_setToll() (gas: 34880)
OmniGasStation_Test:test_pause() (gas: 65258)
OmniGasStation_Test:test_setPump() (gas: 83832)
OmniGasStation_Test:test_settleUp() (gas: 364838)
OmniGasStation_Test:test_pause() (gas: 65220)
OmniGasStation_Test:test_setPump() (gas: 83966)
OmniGasStation_Test:test_settleUp() (gas: 365260)
OmniPortal_admin_Test:test_pauseAll() (gas: 66009349)
OmniPortal_admin_Test:test_pauseXCall() (gas: 269828)
OmniPortal_admin_Test:test_pauseXSubmit() (gas: 231714)
Expand Down
2 changes: 1 addition & 1 deletion contracts/core/src/token/OmniGasPump.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ contract OmniGasPump is XAppUpgradeable, OwnableUpgradeable, PausableUpgradeable
event FilledUp(address indexed recipient, uint256 owed, uint256 amtETH, uint256 fee, uint256 toll, uint256 amtOMNI);

/// @notice Gas limit passed to OmniGasStation.settleUp xcall
uint64 public constant SETTLE_GAS = 100_000;
uint64 public constant SETTLE_GAS = 140_000;

/// @notice Denominator for toll percentage calculations
uint256 public constant TOLL_DENOM = 1000;
Expand Down
26 changes: 20 additions & 6 deletions contracts/core/src/token/OmniGasStation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,19 @@ contract OmniGasStation is XAppUpgradeable, OwnableUpgradeable, PausableUpgradea
_disableInitializers();
}

function initialize(address portal, address owner) external initializer {
/// @dev GasPump struct, just used in initialize params
struct GasPump {
uint64 chainID;
address addr;
}

function initialize(address portal, address owner, GasPump[] calldata pumps_) external initializer {
__XApp_init(portal, ConfLevel.Finalized);
__Ownable_init(owner);

for (uint256 i = 0; i < pumps_.length; i++) {
_setPump(pumps_[i].chainID, pumps_[i].addr);
}
}

/**
Expand All @@ -63,11 +73,7 @@ contract OmniGasStation is XAppUpgradeable, OwnableUpgradeable, PausableUpgradea

/// @notice Set the pump addr for a chain
function setPump(uint64 chainId, address addr) external onlyOwner {
require(addr != address(0), "GasStation: zero addr");
require(chainId != 0, "GasStation: zero chainId");

pumps[chainId] = addr;
emit GasPumpAdded(chainId, addr);
_setPump(chainId, addr);
}

/// @notice Return true if `chainID` has a registered pump at `addr`
Expand All @@ -85,5 +91,13 @@ contract OmniGasStation is XAppUpgradeable, OwnableUpgradeable, PausableUpgradea
_unpause();
}

function _setPump(uint64 chainId, address addr) internal {
require(addr != address(0), "GasStation: zero addr");
require(chainId != 0, "GasStation: zero chainId");

pumps[chainId] = addr;
emit GasPumpAdded(chainId, addr);
}

receive() external payable { }
}
6 changes: 5 additions & 1 deletion contracts/core/test/token/OmniGasStation.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ contract OmniGasStation_Test is Test {
payable(
address(
new TransparentUpgradeableProxy(
impl, makeAddr("admin"), abi.encodeCall(OmniGasStation.initialize, (address(portal), owner))
impl,
makeAddr("admin"),
abi.encodeCall(
OmniGasStation.initialize, (address(portal), owner, new OmniGasStation.GasPump[](0))
)
)
)
)
Expand Down
228 changes: 228 additions & 0 deletions e2e/app/gaspump.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package app

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/anvil"
"github.com/omni-network/omni/lib/contracts"
"github.com/omni-network/omni/lib/contracts/gaspump"
"github.com/omni-network/omni/lib/contracts/gasstation"
"github.com/omni-network/omni/lib/errors"
"github.com/omni-network/omni/lib/log"
"github.com/omni-network/omni/lib/txmgr"

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

// deployGasApp deploys OmniGasPump and OmniGasStation contracts.
func DeployGasApp(ctx context.Context, def Definition) error {
if err := deployGasPumps(ctx, def); err != nil {
return errors.Wrap(err, "deploy gas pumps")
}

if err := deployGasStation(ctx, def); err != nil {
return errors.Wrap(err, "deploy gas station")
}

if err := fundGasStation(ctx, def); err != nil {
return errors.Wrap(err, "fund gas station")
}

return nil
}

// deployGasPumps deploys OmniGasPump contracts to all chains except Omni's EVM.
func deployGasPumps(ctx context.Context, def Definition) error {
network := networkFromDef(def)
omniEVM, ok := network.OmniEVMChain()
if !ok {
return errors.New("no omni evm chain")
}

for _, chain := range network.EVMChains() {
// GasPump not deployed on OmniEVM
if chain.ID == omniEVM.ID {
continue
}

backend, err := def.Backends().Backend(chain.ID)
if err != nil {
return errors.Wrap(err, "backend", "chain", chain.Name)
}

addr, receipt, err := gaspump.DeployIfNeeded(ctx, def.Testnet.Network, backend)
if err != nil {
return errors.Wrap(err, "deploy", "chain", chain.Name, "tx", maybeTxHash(receipt))
}

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

return nil
}

// deployGasStation deploys OmniGasStation contract to Omni's EVM.
func deployGasStation(ctx context.Context, def Definition) error {
network := networkFromDef(def)
omniEVM, ok := network.OmniEVMChain()
if !ok {
return errors.New("no omni evm chain")
}

backend, err := def.Backends().Backend(omniEVM.ID)
if err != nil {
return errors.Wrap(err, "backend")
}

gasPumps := make([]bindings.OmniGasStationGasPump, 0, len(network.EVMChains())-1)
for _, chain := range network.EVMChains() {
if chain.ID == omniEVM.ID {
continue
}

gasPumps = append(gasPumps, bindings.OmniGasStationGasPump{
ChainID: chain.ID,
Addr: contracts.GasPump(network.ID),
})
}

addr, receipt, err := gasstation.DeployIfNeeded(ctx, def.Testnet.Network, backend, gasPumps)
if err != nil {
return errors.Wrap(err, "deploy", "tx", maybeTxHash(receipt))
}

log.Info(ctx, "Gas station deployed", "address", addr.Hex(), "tx", maybeTxHash(receipt))

return nil
}

// fundGasStation funds a network's OmniGasStation contract on Omni's EVM.
//
// TODO: handle funding / monitoring properly.
// consider joining with e2e/app/eoa, or introduce something similar for contracts.
func fundGasStation(ctx context.Context, def Definition) error {
network := networkFromDef(def)
omniEVM, ok := network.OmniEVMChain()
if !ok {
return errors.New("no omni evm chain")
}

backend, err := def.Backends().Backend(omniEVM.ID)
if err != nil {
return errors.Wrap(err, "backend")
}

funder := eoa.Funder()

// use dev account for ephemeral networks
if network.ID.IsEphemeral() {
funder = anvil.DevAccount8()
}

addr := contracts.GasStation(network.ID)

// 1000 OMNI
amt := new(big.Int).Mul(big.NewInt(1000), big.NewInt(params.Ether))

tx, rec, err := backend.Send(ctx, funder, txmgr.TxCandidate{
To: &addr,
GasLimit: 0,
Value: amt,
})
if err != nil {
return errors.Wrap(err, "send tx")
} else if rec.Status != ethtypes.ReceiptStatusSuccessful {
return errors.New("fund tx failed", "tx", tx.Hash())
}

log.Info(ctx, "Funded gas station", "tx", tx.Hash(), "amount", amt)

return nil
}

func maybeTxHash(receipt *ethtypes.Receipt) string {
if receipt != nil {
return receipt.TxHash.Hex()
}

return "nil"
}

type GasPumpTest struct {
Recipient common.Address
AmountETH *big.Int
}

var (
GasPumpTests = []GasPumpTest{
{
Recipient: common.HexToAddress("0x0000000000000000000000000000000000001111"),
AmountETH: big.NewInt(5000000000000000), // 0.005 ETH
},
{
Recipient: common.HexToAddress("0x0000000000000000000000000000000000002222"),
AmountETH: big.NewInt(10000000000000000), // 0.01 ETH
},
{
Recipient: common.HexToAddress("0x0000000000000000000000000000000000003333"),
AmountETH: big.NewInt(15000000000000000), // 0.015 ETH
},
}
)

func testGasPumps(ctx context.Context, def Definition) error {
networkID := def.Testnet.Network
network := networkFromDef(def)
pumpAddr := contracts.GasPump(networkID)

omniEVM, ok := network.OmniEVMChain()
if !ok {
return errors.New("no omni evm chain")
}

if !networkID.IsEphemeral() {
log.Warn(ctx, "Skipping bridge test", errors.New("only ephemeral networks"))
return nil
}

// just need an account with funds on ephemeral network chains
payor := anvil.DevAccount7()

for _, chain := range network.EVMChains() {
if chain.ID == omniEVM.ID {
continue
}

backend, err := def.Backends().Backend(chain.ID)
if err != nil {
return errors.Wrap(err, "backend", "chain", chain.Name)
}

gasPump, err := bindings.NewOmniGasPump(pumpAddr, backend)
if err != nil {
return errors.Wrap(err, "new gas pump")
}

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

for _, test := range GasPumpTests {
txOpts.Value = test.AmountETH
tx, err := gasPump.FillUp(txOpts, test.Recipient)
if err != nil {
return errors.Wrap(err, "pump", "chain", chain.Name)
}

log.Info(ctx, "Pumped gas", "chain", chain.Name, "tx", tx.Hash(), "recipient", test.Recipient.Hex(), "amount", test.AmountETH)
}
}

return nil
}
8 changes: 8 additions & 0 deletions e2e/app/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ func Deploy(ctx context.Context, def Definition, cfg DeployConfig) (*pingpong.XD
return nil, err
}

if err := DeployGasApp(ctx, def); err != nil {
return nil, err
}

if err := setupTokenBridge(ctx, def); err != nil {
return nil, errors.Wrap(err, "setup token bridge")
}
Expand Down Expand Up @@ -170,6 +174,10 @@ func E2ETest(ctx context.Context, def Definition, cfg E2ETestConfig) error {
return err
}

if err := testGasPumps(ctx, def); err != nil {
return errors.Wrap(err, "test gas app")
}

if err := testBridge(ctx, def); err != nil {
return errors.Wrap(err, "test bridge")
}
Expand Down
41 changes: 41 additions & 0 deletions e2e/test/gaspump_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package e2e_test

import (
"context"
"testing"

"github.com/omni-network/omni/e2e/app"
"github.com/omni-network/omni/lib/ethclient"
"github.com/omni-network/omni/lib/netconf"
"github.com/omni-network/omni/lib/xchain"

"github.com/stretchr/testify/require"
)

// TestGasPumps ensures that bridge tests cases defined in e2e/app/gaspump.go were successful.
func TestGasPumps(t *testing.T) {
t.Parallel()
testNetwork(t, func(t *testing.T, network netconf.Network, endpoints xchain.RPCEndpoints) {
t.Helper()
ctx := context.Background()

omniEVM, ok := network.OmniEVMChain()
require.True(t, ok)

omniRPC, err := endpoints.ByNameOrID(omniEVM.Name, omniEVM.ID)
require.NoError(t, err)

omniClient, err := ethclient.Dial(omniEVM.Name, omniRPC)
require.NoError(t, err)

for _, test := range app.GasPumpTests {
balance, err := omniClient.BalanceAt(ctx, test.Recipient, nil)
require.NoError(t, err)

// Just test that balance > 0 for now
// TODO: assert that amount is equal to sum of AmountETH spent converted to OMNI
// Should account for the xcall fee, gas pump toll, and fee oracle conversion rates
require.Positive(t, balance.Uint64(), "recipient: %s", test.Recipient)
}
})
}
Loading

0 comments on commit d282d92

Please sign in to comment.