Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
ceapayloadverificationfix "github.com/pushchain/push-chain-node/app/upgrades/cea-payload-verification-fix"
chainmeta "github.com/pushchain/push-chain-node/app/upgrades/chain-meta"
chainmetavotegasless "github.com/pushchain/push-chain-node/app/upgrades/chain-meta-vote-gasless"
contractauditchanges "github.com/pushchain/push-chain-node/app/upgrades/contract-audit-changes"
ethhashfix "github.com/pushchain/push-chain-node/app/upgrades/eth-hash-fix"
evmrpcfix "github.com/pushchain/push-chain-node/app/upgrades/evm-rpc-fix"
feeabs "github.com/pushchain/push-chain-node/app/upgrades/fee-abs"
Expand Down Expand Up @@ -65,6 +66,7 @@ var Upgrades = []upgrades.Upgrade{
purgeexpiredoutbounds.NewUpgrade(),
removeutxverifier.NewUpgrade(),
tssfundmigrationfixes.NewUpgrade(),
contractauditchanges.NewUpgrade(),
}

// RegisterUpgradeHandlers registers the chain upgrade handlers
Expand Down
56 changes: 56 additions & 0 deletions app/upgrades/contract-audit-changes/upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package contractauditchanges

import (
"context"
"fmt"

storetypes "cosmossdk.io/store/types"
upgradetypes "cosmossdk.io/x/upgrade/types"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"

"github.com/pushchain/push-chain-node/app/upgrades"
)

// UpgradeName matches the chain-side change set that adapts the chain's
// UniversalCore ABI + gas-fee read path to the post-audit smart-contract
// No module ConsensusVersion is bumped for this upgrade — none of the chain
// changes touch module storage schemas, so RunMigrations is a no-op for the
// version map; this handler exists primarily as a coordination point so all
// validators flip to the new ABI / gas-fee read at the same height.
const UpgradeName = "contract-audit-changes"

func NewUpgrade() upgrades.Upgrade {
return upgrades.Upgrade{
UpgradeName: UpgradeName,
CreateUpgradeHandler: CreateUpgradeHandler,
StoreUpgrades: storetypes.StoreUpgrades{
Added: []string{},
Deleted: []string{},
},
}
}

func CreateUpgradeHandler(
mm upgrades.ModuleManager,
configurator module.Configurator,
_ *upgrades.AppKeepers,
) upgradetypes.UpgradeHandler {
return func(ctx context.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
logger := sdkCtx.Logger().With("upgrade", UpgradeName)
logger.Info("Starting upgrade handler")

// RunMigrations is a no-op for this upgrade (no module ConsensusVersion
// bumped) but we still call it so the version map is materialised
// correctly for any modules whose code may have changed underneath.
versionMap, err := mm.RunMigrations(ctx, configurator, fromVM)
if err != nil {
return nil, fmt.Errorf("RunMigrations: %w", err)
}

logger.Info("Upgrade complete", "upgrade", UpgradeName)
return versionMap, nil
}
}
2 changes: 1 addition & 1 deletion test/utils/bytecode.go

Large diffs are not rendered by default.

43 changes: 0 additions & 43 deletions x/uexecutor/keeper/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,49 +299,6 @@ func (k Keeper) CallPRC20Deposit(
)
}

// Calls UniversalCore Contract to set gas price
func (k Keeper) CallUniversalCoreSetGasPrice(
ctx sdk.Context,
chainID string,
price *big.Int,
) (*evmtypes.MsgEthereumTxResponse, error) {
handlerAddr := common.HexToAddress(uregistrytypes.SYSTEM_CONTRACTS["UNIVERSAL_CORE"].Address)

abi, err := types.ParseUniversalCoreABI()
if err != nil {
return nil, errors.Wrap(err, "failed to parse Handler Contract ABI")
}

ueModuleAccAddress, _ := k.GetUeModuleAddress(ctx)

// Before sending an EVM tx from module
nonce, err := k.GetModuleAccountNonce(ctx)
if err != nil {
return nil, err
}

// increment first (safe for internal modules)
if _, err := k.IncrementModuleAccountNonce(ctx); err != nil {
return nil, err
}

return k.evmKeeper.DerivedEVMCall(
ctx,
abi,
ueModuleAccAddress, // who is sending the transaction
handlerAddr, // destination: Handler contract
big.NewInt(0),
nil,
true, // commit = true (real tx, not simulation)
false, // gasless = false (@dev: we need gas to be emitted in the tx receipt)
true, // module sender = true
&nonce, // manual nonce of module
"setGasPrice",
chainID,
price,
)
}

// Calls UniversalCore Contract to set chain metadata (gas price + chain height).
// The contract uses block.timestamp for the observed-at value.
func (k Keeper) CallUniversalCoreSetChainMeta(
Expand Down
14 changes: 6 additions & 8 deletions x/uexecutor/keeper/gas_fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,12 @@ func (k Keeper) GetOutboundTxGasAndFees(ctx sdk.Context, prc20 common.Address, g
gasFee := results[1].(*big.Int)
// protocolFee := results[2].(*big.Int) — not needed for outbound fields
gasPrice := results[3].(*big.Int)

// Derive gasLimit from gasFee / gasPrice
var gasLimit *big.Int
if gasPrice.Sign() > 0 {
gasLimit = new(big.Int).Div(gasFee, gasPrice)
} else {
gasLimit = big.NewInt(0)
}
// chainNamespace := results[4].(string) — not needed for outbound fields
// gasLimitUsed (results[5]) is the exact gas limit the contract resolved
// (caller-supplied or per-chain baseGasLimitByChainNamespace fallback).
// Reading it directly avoids the gasFee/gasPrice round-trip and keeps us
// in lock-step with the contract's own resolution.
gasLimit := results[5].(*big.Int)

return &GasFeeInfo{
GasToken: gasToken,
Expand Down
82 changes: 82 additions & 0 deletions x/uexecutor/keeper/gas_fee_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package keeper_test

import (
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/pushchain/push-chain-node/x/uexecutor/types"
"github.com/stretchr/testify/require"
)

// TestUniversalCoreABI_GetOutboundTxGasAndFees_Has6Outputs locks in the new
// post-audit schema (the contract added gasLimitUsed as a 6th output).
// Catches accidental ABI reverts and proves Pack/Unpack round-trips.
func TestUniversalCoreABI_GetOutboundTxGasAndFees_Has6Outputs(t *testing.T) {
abi, err := types.ParseUniversalCoreABI()
require.NoError(t, err)

method, ok := abi.Methods["getOutboundTxGasAndFees"]
require.True(t, ok, "getOutboundTxGasAndFees missing from ABI")
require.Len(t, method.Outputs, 6, "expected 6 outputs (post-audit schema added gasLimitUsed)")

// Output names must match the contract field names so future readers can
// map results[i] back to the contract source unambiguously.
wantNames := []string{"gasToken", "gasFee", "protocolFee", "gasPrice", "chainNamespace", "gasLimitUsed"}
for i, want := range wantNames {
require.Equal(t, want, method.Outputs[i].Name, "output[%d] name mismatch", i)
}

// Round-trip: pack a fake response, unpack it, get the same values back.
// This is the contract that GetOutboundTxGasAndFees in keeper/gas_fee.go
// relies on (results[0]=gasToken, results[1]=gasFee, results[3]=gasPrice,
// results[5]=gasLimit).
wantGasToken := common.HexToAddress("0x0000000000000000000000000000000000001111")
wantGasFee := big.NewInt(123_456)
wantProtocolFee := big.NewInt(789)
wantGasPrice := big.NewInt(10)
wantChainNs := "eip155:1"
wantGasLimit := big.NewInt(50_000) // intentionally != gasFee/gasPrice (=12345)

encoded, err := method.Outputs.Pack(
wantGasToken,
wantGasFee,
wantProtocolFee,
wantGasPrice,
wantChainNs,
wantGasLimit,
)
require.NoError(t, err)

results, err := method.Outputs.Unpack(encoded)
require.NoError(t, err)
require.Len(t, results, 6)

require.Equal(t, wantGasToken, results[0].(common.Address))
require.Equal(t, 0, wantGasFee.Cmp(results[1].(*big.Int)))
require.Equal(t, 0, wantProtocolFee.Cmp(results[2].(*big.Int)))
require.Equal(t, 0, wantGasPrice.Cmp(results[3].(*big.Int)))
require.Equal(t, wantChainNs, results[4].(string))
require.Equal(t, 0, wantGasLimit.Cmp(results[5].(*big.Int)),
"results[5] (gasLimitUsed) must be the value the contract returned, "+
"not derived from gasFee/gasPrice")

// Belt-and-suspenders: the post-audit chain code reads gasLimit from
// results[5] directly. If anyone ever regresses to the old
// `gasLimit = gasFee/gasPrice` derivation, the value would be 12345,
// not 50000. Encode that expectation explicitly.
derived := new(big.Int).Div(wantGasFee, wantGasPrice)
require.NotEqual(t, 0, derived.Cmp(results[5].(*big.Int)),
"gasLimit must come from results[5], NOT from gasFee/gasPrice division")
}

// TestUniversalCoreABI_SetGasPrice_Removed locks in that the deprecated
// setGasPrice function has been removed from the ABI (deleted in the
// post-audit contract; chain wrapper CallUniversalCoreSetGasPrice was
// removed as dead code).
func TestUniversalCoreABI_SetGasPrice_Removed(t *testing.T) {
abi, err := types.ParseUniversalCoreABI()
require.NoError(t, err)
_, exists := abi.Methods["setGasPrice"]
require.False(t, exists, "setGasPrice must be removed from ABI (deleted from contract post-audit)")
}
13 changes: 2 additions & 11 deletions x/uexecutor/types/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,16 +290,6 @@ const UNIVERSAL_CORE_ABI = `[
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "setGasPrice",
"inputs": [
{ "name": "chainID", "type": "string", "internalType": "string" },
{ "name": "price", "type": "uint256", "internalType": "uint256" }
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "setChainMeta",
Expand Down Expand Up @@ -423,7 +413,8 @@ const UNIVERSAL_CORE_ABI = `[
{ "name": "gasFee", "type": "uint256", "internalType": "uint256" },
{ "name": "protocolFee", "type": "uint256", "internalType": "uint256" },
{ "name": "gasPrice", "type": "uint256", "internalType": "uint256" },
{ "name": "chainNamespace", "type": "string", "internalType": "string" }
{ "name": "chainNamespace", "type": "string", "internalType": "string" },
{ "name": "gasLimitUsed", "type": "uint256", "internalType": "uint256" }
],
"stateMutability": "view"
},
Expand Down
Loading