Skip to content
Open
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: 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 @@ -379,7 +369,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
14 changes: 9 additions & 5 deletions x/uregistry/keeper/genesis_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func (t *trackerEVMKeeper) SetCode(_ sdk.Context, codeHash, code []byte) {
// 3. The implementation address has non-empty CodeHash.
// 4. The ProxyAdmin's storage slot 0 (Ownable.owner) is set to
// PROXY_ADMIN_OWNER_ADDRESS_HEX (the F-2026-16998 EOA owner — same for all
// 46 ProxyAdmins). This is the load-bearing assertion for the
// 47 ProxyAdmins). This is the load-bearing assertion for the
// "single owner controls every system-contract upgrade" trust assumption.
// 5. The proxy's EIP-1967 admin slot points to the right ProxyAdmin
// (PROXY_ADMIN_SLOT) and impl slot points to the right implementation
Expand All @@ -164,8 +164,8 @@ func TestDeploySystemContracts_DeploysFullTripleForEveryReservedAddress(t *testi

expectedOwner := common.HexToAddress(types.PROXY_ADMIN_OWNER_ADDRESS_HEX)

// Sanity: must have processed all 46 entries (6 explicit + 40 auto-reserved).
require.Len(t, types.SYSTEM_CONTRACTS, 46, "SYSTEM_CONTRACTS size drift")
// Sanity: must have processed all 47 entries (6 explicit + 41 auto-reserved).
require.Len(t, types.SYSTEM_CONTRACTS, 47, "SYSTEM_CONTRACTS size drift")

for name, addrs := range types.SYSTEM_CONTRACTS {
proxy := common.HexToAddress(addrs.Address)
Expand Down Expand Up @@ -265,8 +265,12 @@ func TestDeploySystemContracts_AllReservedSlotsInABCRangeAreCovered(t *testing.T

// Slots in A/B/C that uregistry does NOT own:
// 0xAA — uexecutor PROXY_ADMIN (deployed by uexecutor's own genesis)
// 0xCA — USigVerifier legacy precompile (precompile dispatch beats EVM state)
uregistryDoesNotOwn := map[byte]bool{0xAA: true, 0xCA: true}
// 0xCA hosts the USigVerifier legacy precompile (testnet-live, removed on
// mainnet; the new address is 0xE1). It IS auto-reserved here: while the
// precompile is active the bytecode is shadowed by dispatch, but reserving
// the slot keeps the EOA-squatting protection (F-2026-17025) in effect
// once the precompile is removed on mainnet.
uregistryDoesNotOwn := map[byte]bool{0xAA: true}

for _, hi := range []byte{0xA, 0xB, 0xC} {
for lo := byte(0); lo < 0x10; lo++ {
Expand Down
6 changes: 3 additions & 3 deletions x/uregistry/types/constants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestReservedSlots_FullTripleDeployedForEveryUnoccupiedABCSlot(t *testing.T)
occupied := map[byte]bool{
0xAA: true,
0xB0: true, 0xB1: true, 0xB2: true, 0xBC: true,
0xC0: true, 0xC1: true, 0xCA: true,
0xC0: true, 0xC1: true,
}

for _, hi := range []byte{0xA, 0xB, 0xC} {
Expand Down Expand Up @@ -104,10 +104,10 @@ func TestReservedSlots_NoCollisionWithProxyAdminOrImpl(t *testing.T) {

// TestReservedSlots_ExpectedTotalCount fixes the count so an off-by-one in
// the loop bounds (e.g. accidentally dropping AF or CF) shows up immediately.
// Pre-existing 6 + 40 newly reserved = 46.
// Pre-existing 6 + 41 newly reserved = 47.
func TestReservedSlots_ExpectedTotalCount(t *testing.T) {
require.Len(t, SYSTEM_CONTRACTS, 47,
"expected 6 pre-existing + 41 auto-reserved (15 A + 12 B + 14 C, 0xCA now reserved) = 47 total")
"expected 6 pre-existing + 41 auto-reserved (15 A + 12 B + 14 C) = 47 total")
require.Len(t, BYTECODE, 47,
"BYTECODE must mirror SYSTEM_CONTRACTS")
}
Expand Down
Loading