mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
tests: pin EVM upgrade fixtures to canonical genesis
Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -137,16 +137,10 @@ func TestMainnetUpgradeJSON_PrecompileTimestampsAreMonotonic(t *testing.T) {
|
||||
// warp message is signed by primary-network validators (not just a subnet
|
||||
// quorum).
|
||||
//
|
||||
// Warp lives in the genesis chainConfig (cchain.json config.warpConfig at
|
||||
// the genesis timestamp, with requirePrimaryNetworkSigners=false during the
|
||||
// classical era). The upgrade schedule then carries a two-step toggle at the
|
||||
// strict-PQ fork: disable@strictPQ-1, then re-enable@strictPQ with the
|
||||
// PQ-hardened policy. Both toggles are strictly AFTER genesis time, so this
|
||||
// is a genuine future upgrade — NOT a reschedule of the genesis warp (that
|
||||
// would be the relaunch fork hazard; see relaunch_safety_test.go). The
|
||||
// policy assertion therefore targets the RE-ENABLE entry (the one that
|
||||
// carries quorumNumerator + requirePrimaryNetworkSigners); the disable entry
|
||||
// carries only {blockTimestamp, disable:true}.
|
||||
// Warp lives in the genesis chainConfig with the classical-era policy. The
|
||||
// upgrade schedule contains one replacement at the strict-PQ fork with the
|
||||
// hardened signer policy. Keeping a single entry avoids two competing ways
|
||||
// to describe the same activation.
|
||||
func TestMainnetUpgradeJSON_WarpRequiresPrimaryNetworkSigners(t *testing.T) {
|
||||
const strictPQ uint64 = 1766708400
|
||||
|
||||
@@ -158,15 +152,15 @@ func TestMainnetUpgradeJSON_WarpRequiresPrimaryNetworkSigners(t *testing.T) {
|
||||
require.NoError(t, json.Unmarshal(raw, &doc))
|
||||
|
||||
var (
|
||||
foundDisable bool
|
||||
foundReEnable bool
|
||||
prevWarpTs *uint64
|
||||
foundWarp bool
|
||||
warpCount int
|
||||
)
|
||||
for i, entry := range doc.PrecompileUpgrades {
|
||||
warpRaw, ok := entry["warpConfig"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
warpCount++
|
||||
var warp struct {
|
||||
BlockTimestamp uint64 `json:"blockTimestamp"`
|
||||
Disable bool `json:"disable"`
|
||||
@@ -182,37 +176,18 @@ func TestMainnetUpgradeJSON_WarpRequiresPrimaryNetworkSigners(t *testing.T) {
|
||||
"warpConfig upgrade entry at index %d has blockTimestamp 0 — warp is declared in the genesis chainConfig; an upgrade entry pinned to 0 would collide with the genesis warp and fail verifyPrecompileUpgrades",
|
||||
i,
|
||||
)
|
||||
// Same-key strict increase (mirrors verifyPrecompileUpgrades).
|
||||
if prevWarpTs != nil {
|
||||
require.Greaterf(t, warp.BlockTimestamp, *prevWarpTs,
|
||||
"warpConfig entries must strictly increase in blockTimestamp (got %d after %d)",
|
||||
warp.BlockTimestamp, *prevWarpTs,
|
||||
)
|
||||
}
|
||||
ts := warp.BlockTimestamp
|
||||
prevWarpTs = &ts
|
||||
|
||||
if warp.Disable {
|
||||
foundDisable = true
|
||||
require.Equalf(t, strictPQ-1, warp.BlockTimestamp,
|
||||
"warpConfig disable must fire one second before the strict-PQ fork (%d), got %d",
|
||||
strictPQ-1, warp.BlockTimestamp,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
// Re-enable entry: carries the PQ-hardened policy.
|
||||
foundReEnable = true
|
||||
foundWarp = true
|
||||
require.False(t, warp.Disable, "the canonical warp activation must not be disabled")
|
||||
require.Equalf(t, strictPQ, warp.BlockTimestamp,
|
||||
"warpConfig re-enable must fire at the strict-PQ fork (%d), got %d", strictPQ, warp.BlockTimestamp,
|
||||
"warpConfig must activate at the strict-PQ fork (%d), got %d", strictPQ, warp.BlockTimestamp,
|
||||
)
|
||||
require.Equal(t, uint64(67), warp.QuorumNumerator, "warpConfig quorumNumerator must be 67")
|
||||
require.Truef(t, warp.RequirePrimaryNetworkSigners,
|
||||
"warpConfig.requirePrimaryNetworkSigners must be true on lux-mainnet — every cross-chain warp message MUST be signed by primary-network validators (red-review finding #8)",
|
||||
)
|
||||
}
|
||||
require.True(t, foundDisable, "warpConfig disable entry must be present in canonical upgrade.json")
|
||||
require.True(t, foundReEnable, "warpConfig re-enable entry (with PQ signer policy) must be present in canonical upgrade.json")
|
||||
require.True(t, foundWarp, "warpConfig entry with the PQ signer policy must be present")
|
||||
require.Equal(t, 1, warpCount, "canonical upgrade.json must contain exactly one warpConfig entry")
|
||||
}
|
||||
|
||||
// readPrecompileUpgradeTimestamps decodes the precompileUpgrades array
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
{
|
||||
"networkUpgradeOverrides": {
|
||||
"evmTimestamp": 0,
|
||||
"durangoTimestamp": 0,
|
||||
"quasarTimestamp": 0,
|
||||
"fortunaTimestamp": 0,
|
||||
"graniteTimestamp": 0
|
||||
},
|
||||
"precompileUpgrades": [
|
||||
{
|
||||
"aiMiningConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"blake3Config": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"cggmp21Verify": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"deadZeroConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"deadConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"deadFullConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"routerConfig": {
|
||||
"blockTimestamp": 1766708400,
|
||||
"v3QuoterAddress": "0x0000000000000000000000000000000000000000",
|
||||
"v3FactoryAddress": "0x0000000000000000000000000000000000000000",
|
||||
"v2RouterAddress": "0x0000000000000000000000000000000000000000",
|
||||
"v2FactoryAddress": "0x0000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fheConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"frostVerify": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"graphConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"hpkeConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"mldsaVerify": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"mlkemConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"ringConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"slhdsaVerify": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"zkConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"bls12381G1AddConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"bls12381G1MulConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"bls12381G1MSMConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"bls12381G2AddConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"bls12381G2MulConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"bls12381G2MSMConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"bls12381PairingConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"warpConfig": {
|
||||
"blockTimestamp": 1766708400,
|
||||
"quorumNumerator": 67,
|
||||
"requirePrimaryNetworkSigners": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"dexPositionManagerConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"dexStateViewConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"dexQuoterConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"dexSettleConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"pulsarVerify": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"magnetarVerify": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"p3qVerify": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"coronaThreshold": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"hqcEncapsulate": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"curve25519Config": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"x25519Config": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"sr25519Verify": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"babyjubjubConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"pedersenConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"poseidonConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"pastaConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"xwingConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"computeMarketConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"bridgeRegistrarConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"stableSwapConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"vrfConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"attestationConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"anchorConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixedPointMathConfig": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"rewardManagerConfig": {
|
||||
"blockTimestamp": 1766708400,
|
||||
"adminAddresses": [
|
||||
"0x9011E888251AB053B7bD1cdB598Db4f9DEd94714"
|
||||
],
|
||||
"initialRewardConfig": {
|
||||
"rewardAddress": "0x8E29b816c6C35b13cE1ff68D33E245C2bda8ac3D"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"stateUpgrades": [
|
||||
{
|
||||
"blockTimestamp": 1766708400,
|
||||
"accounts": {
|
||||
"0x80bBc7C4C7a59C899D1B37BC14539A22D5830a84": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000003": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0xD173926A10A0C4eCd3A51B1422270b65Df0551c1": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0xeac0a50112b5ee20cc18e42ba4d37777012afd0d": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0xaa6a41cacb18bed5b98059a5fa30f9dbabe0cc64": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0xdf1de693c31e2a5eb869c329529623556b20abf3": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce212"
|
||||
}
|
||||
},
|
||||
"0x8031e9b0d02a792cfefaa2bdca6e1289d385426f": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce212"
|
||||
}
|
||||
},
|
||||
"0x79608e442d046ea6d3125931a33ce971ad99c6f9": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce212"
|
||||
}
|
||||
},
|
||||
"0xa85eb8e163f4d70bfc43a4f3fcc9a8dfc42b1ae4": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce212"
|
||||
}
|
||||
},
|
||||
"0x724f4ef0400386fc8c1011788af733bd367ac00f": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce212"
|
||||
}
|
||||
},
|
||||
"0x3569c06a80c148d7b4b67f8a06125f6d59774243": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce212"
|
||||
}
|
||||
},
|
||||
"0x60E0a8167FC13dE89348978860466C9ceC24B9ba": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0x1E48D32a4F5e9f08DB9aE4959163300FaF8A6C8e": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0x848Cff46eb323f323b6Bbe1Df274E40793d7f2c2": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0x5E5290f350352768bD2bfC59c2DA15DD04A7cB88": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0x26B40f650156C7EbF9e087Dd0dca181Fe87625B7": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0x6EdcF3645DeF09DB45050638c41157D8B9FEa1cf": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0x28BfC5DD4B7E15659e41190983e5fE3df1132bB9": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0x3078847F879A33994cDa2Ec1540ca52b5E0eE2e5": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0x8B982132d639527E8a0eAAD385f97719af8f5e04": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0x3141b94b89691009b950c96e97Bff48e0C543E3C": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0x0e4bd0dd67c15DECFbBBDBbE07Fc9d51D737693D": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0xf5a313885832d4fc71d1ef80115197c4479b58c8": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
},
|
||||
"0x1af00a2590a834d14f4a8a26d1b03ebba8cf7961": {
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000051284dc2133e8d3a8e213dca6a6fa768cfdfcce2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -54,15 +54,13 @@ const (
|
||||
testnetCChainGenesisTime uint64 = 0x67259912 // 1730517266 = 2024-11-02 03:14:26 UTC
|
||||
)
|
||||
|
||||
// liveAtBlockZero is the exact set of precompile config keys that the
|
||||
// running mainnet AND testnet C-Chains have active at block 0. Source of
|
||||
// truth: the UPGRADE_JSON heredoc in luxfi/universe
|
||||
// k8s/lux-{mainnet,testnet}/luxd-startup.yaml (both carry these 17 at
|
||||
// blockTimestamp:0). warpConfig is NOT in this set: it is declared in the
|
||||
// genesis chainConfig itself (cchain.json config.warpConfig), not in
|
||||
// upgrade.json's precompileUpgrades, so it is handled by the genesis path
|
||||
// rather than the upgrade schedule.
|
||||
var liveAtBlockZero = map[string]bool{
|
||||
// Mainnet deliberately keeps every upgrade entry after its genesis timestamp
|
||||
// so replaying the imported history reproduces the original block-0 root.
|
||||
var mainnetLiveAtBlockZero = map[string]bool{}
|
||||
|
||||
// Testnet was launched with these precompiles active at block 0. Its relaunch
|
||||
// schedule must continue to pin exactly this set to timestamp zero.
|
||||
var testnetLiveAtBlockZero = map[string]bool{
|
||||
"aiMiningConfig": true,
|
||||
"blake3Config": true,
|
||||
"cggmp21Verify": true,
|
||||
@@ -82,16 +80,16 @@ var liveAtBlockZero = map[string]bool{
|
||||
}
|
||||
|
||||
func TestMainnetUpgradeJSON_RelaunchSafe(t *testing.T) {
|
||||
assertRelaunchSafe(t, readCanonicalMainnetUpgradeJSONRaw(t), mainnetCChainGenesisTime)
|
||||
assertRelaunchSafe(t, readCanonicalMainnetUpgradeJSONRaw(t), mainnetCChainGenesisTime, mainnetLiveAtBlockZero)
|
||||
}
|
||||
|
||||
func TestTestnetUpgradeJSON_RelaunchSafe(t *testing.T) {
|
||||
assertRelaunchSafe(t, readCanonicalTestnetUpgradeJSONRaw(t), testnetCChainGenesisTime)
|
||||
assertRelaunchSafe(t, readCanonicalTestnetUpgradeJSONRaw(t), testnetCChainGenesisTime, testnetLiveAtBlockZero)
|
||||
}
|
||||
|
||||
// assertRelaunchSafe parses the canonical upgrade.json and proves the
|
||||
// relaunch-safety invariant using the node's own IsForkTransition predicate.
|
||||
func assertRelaunchSafe(t *testing.T, raw []byte, genesisTime uint64) {
|
||||
func assertRelaunchSafe(t *testing.T, raw []byte, genesisTime uint64, liveAtBlockZero map[string]bool) {
|
||||
t.Helper()
|
||||
|
||||
var cfg extras.UpgradeConfig
|
||||
|
||||
+49
-57
@@ -4,8 +4,7 @@
|
||||
"durangoTimestamp": 0,
|
||||
"quasarTimestamp": 0,
|
||||
"fortunaTimestamp": 0,
|
||||
"graniteTimestamp": 0,
|
||||
"strictPQTimestamp": 1766708400
|
||||
"graniteTimestamp": 0
|
||||
},
|
||||
"precompileUpgrades": [
|
||||
{
|
||||
@@ -43,8 +42,8 @@
|
||||
"blockTimestamp": 0,
|
||||
"v3QuoterAddress": "0x0000000000000000000000000000000000000000",
|
||||
"v3FactoryAddress": "0x0000000000000000000000000000000000000000",
|
||||
"v2RouterAddress": "0x0000000000000000000000000000000000009999",
|
||||
"v2FactoryAddress": "0x0000000000000000000000000000000000009999"
|
||||
"v2RouterAddress": "0x0000000000000000000000000000000000000000",
|
||||
"v2FactoryAddress": "0x0000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -77,11 +76,6 @@
|
||||
"blockTimestamp": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"pqcryptoConfig": {
|
||||
"blockTimestamp": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"ringConfig": {
|
||||
"blockTimestamp": 0
|
||||
@@ -98,16 +92,28 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"warpConfig": {
|
||||
"blockTimestamp": 1766708399,
|
||||
"disable": true
|
||||
"pulsarVerify": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"warpConfig": {
|
||||
"blockTimestamp": 1766708400,
|
||||
"quorumNumerator": 67,
|
||||
"requirePrimaryNetworkSigners": true
|
||||
"magnetarVerify": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"p3qVerify": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"coronaThreshold": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"hqcEncapsulate": {
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -145,106 +151,92 @@
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"pulsarVerify": {
|
||||
"blockTimestamp": 1782864000
|
||||
}
|
||||
},
|
||||
{
|
||||
"magnetarVerify": {
|
||||
"blockTimestamp": 1782864000
|
||||
}
|
||||
},
|
||||
{
|
||||
"p3qVerify": {
|
||||
"blockTimestamp": 1782864000
|
||||
}
|
||||
},
|
||||
{
|
||||
"coronaThreshold": {
|
||||
"blockTimestamp": 1782864000
|
||||
}
|
||||
},
|
||||
{
|
||||
"hqcEncapsulate": {
|
||||
"blockTimestamp": 1782864000
|
||||
}
|
||||
},
|
||||
{
|
||||
"curve25519Config": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"x25519Config": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"sr25519Verify": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"babyjubjubConfig": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"pedersenConfig": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"poseidonConfig": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"pastaConfig": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"xwingConfig": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"computeMarketConfig": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"bridgeRegistrarConfig": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"stableSwapConfig": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"vrfConfig": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"attestationConfig": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"anchorConfig": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixedPointMathConfig": {
|
||||
"blockTimestamp": 1782864000
|
||||
"blockTimestamp": 1766708400
|
||||
}
|
||||
},
|
||||
{
|
||||
"rewardManagerConfig": {
|
||||
"blockTimestamp": 1766708400,
|
||||
"adminAddresses": [
|
||||
"0x9011E888251AB053B7bD1cdB598Db4f9DEd94714"
|
||||
],
|
||||
"initialRewardConfig": {
|
||||
"rewardAddress": "0xEAbCC110fAcBfebabC66Ad6f9E7B67288e720B59"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"stateUpgrades": []
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Testnet-side regression gate. Mirrors mainnet_upgrade_v46.json's
|
||||
// Testnet-side regression gate. Mirrors mainnet_upgrade.json's
|
||||
// upgrade_unmarshal_test.go (task #99) for the Lux primary testnet
|
||||
// C-Chain upgrade schedule. The mainnet test wouldn't catch a testnet
|
||||
// drift because the canonical fixture vendored there is only mainnet;
|
||||
@@ -25,28 +25,17 @@ import (
|
||||
_ "github.com/luxfi/evm/precompile/registry"
|
||||
)
|
||||
|
||||
// canonicalTestnetUpgradeV47 is the byte-for-byte vendored copy of
|
||||
// luxfi/genesis configs/testnet/upgrade.json at the v47 precompile-set
|
||||
// freeze. Layout (44 entries, monotonic by blockTimestamp):
|
||||
// - 17 already-live precompiles pinned to blockTimestamp:0 — these are
|
||||
// active at block 0 on the running testnet (see the UPGRADE_JSON in
|
||||
// luxfi/universe k8s/lux-testnet/luxd-startup.yaml). They MUST stay at
|
||||
// 0 so a relaunch from genesis treats them as already-applied and
|
||||
// checkPrecompileCompatible does not refuse boot.
|
||||
// - 27 net-new safe-subset cryptography precompiles forward-dated to the
|
||||
// strict-PQ timestamp 1766708400 (strictly after testnet genesis time
|
||||
// 1730517266), so the strict-PQ gate is in force when they activate.
|
||||
// Unlike mainnet there is no separate Quasar-Edition tier — testnet
|
||||
// front-loads its net-new activations to the strict-PQ fork because it's
|
||||
// the experimentation lane.
|
||||
// canonicalTestnetUpgrade is the byte-for-byte vendored copy of
|
||||
// luxfi/genesis configs/testnet/upgrade.json. A workspace drift test keeps
|
||||
// this hermetic copy synchronized.
|
||||
//
|
||||
// Sync contract: if luxfi/genesis configs/testnet/upgrade.json changes,
|
||||
// regenerate this file:
|
||||
//
|
||||
// cp ../../../../genesis/configs/testnet/upgrade.json testnet_upgrade_v47.json
|
||||
// cp ../../../../genesis/configs/testnet/upgrade.json testnet_upgrade.json
|
||||
//
|
||||
//go:embed testnet_upgrade_v47.json
|
||||
var canonicalTestnetUpgradeV47 []byte
|
||||
//go:embed testnet_upgrade.json
|
||||
var canonicalTestnetUpgrade []byte
|
||||
|
||||
// TestTestnetUpgradeJSON_UnmarshalsAgainstRegistry mirrors the mainnet
|
||||
// regression gate. Every key in the canonical testnet upgrade.json
|
||||
@@ -87,8 +76,8 @@ func TestTestnetUpgradeJSON_UnmarshalsAgainstRegistry(t *testing.T) {
|
||||
// testnet upgrade.json. Mirrors the mainnet helper.
|
||||
func readCanonicalTestnetUpgradeJSONRaw(t *testing.T) []byte {
|
||||
t.Helper()
|
||||
if len(canonicalTestnetUpgradeV47) == 0 {
|
||||
t.Fatal("embedded canonical testnet upgrade.json is empty — //go:embed testnet_upgrade_v47.json failed at build time")
|
||||
if len(canonicalTestnetUpgrade) == 0 {
|
||||
t.Fatal("embedded canonical testnet upgrade.json is empty — //go:embed testnet_upgrade.json failed at build time")
|
||||
}
|
||||
return canonicalTestnetUpgradeV47
|
||||
return canonicalTestnetUpgrade
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
{
|
||||
"networkUpgradeOverrides": {
|
||||
"evmTimestamp": 0,
|
||||
"durangoTimestamp": 0,
|
||||
"quasarTimestamp": 0,
|
||||
"fortunaTimestamp": 0,
|
||||
"graniteTimestamp": 0,
|
||||
"strictPQTimestamp": 1766708400
|
||||
},
|
||||
"precompileUpgrades": [
|
||||
{"aiMiningConfig": {"blockTimestamp": 0}},
|
||||
{"blake3Config": {"blockTimestamp": 0}},
|
||||
{"cggmp21Verify": {"blockTimestamp": 0}},
|
||||
{"deadZeroConfig": {"blockTimestamp": 0}},
|
||||
{"deadConfig": {"blockTimestamp": 0}},
|
||||
{"deadFullConfig": {"blockTimestamp": 0}},
|
||||
{"routerConfig": {
|
||||
"blockTimestamp": 0,
|
||||
"v3QuoterAddress": "0x0000000000000000000000000000000000000000",
|
||||
"v3FactoryAddress": "0x0000000000000000000000000000000000000000",
|
||||
"v2RouterAddress": "0x0000000000000000000000000000000000009999",
|
||||
"v2FactoryAddress": "0x0000000000000000000000000000000000009999"
|
||||
}},
|
||||
{"fheConfig": {"blockTimestamp": 0}},
|
||||
{"frostVerify": {"blockTimestamp": 0}},
|
||||
{"graphConfig": {"blockTimestamp": 0}},
|
||||
{"hpkeConfig": {"blockTimestamp": 0}},
|
||||
{"mldsaVerify": {"blockTimestamp": 0}},
|
||||
{"mlkemConfig": {"blockTimestamp": 0}},
|
||||
{"pqcryptoConfig": {"blockTimestamp": 0}},
|
||||
{"ringConfig": {"blockTimestamp": 0}},
|
||||
{"slhdsaVerify": {"blockTimestamp": 0}},
|
||||
{"zkConfig": {"blockTimestamp": 0}},
|
||||
{"pulsarVerify": {"blockTimestamp": 1766708400}},
|
||||
{"magnetarVerify": {"blockTimestamp": 1766708400}},
|
||||
{"p3qVerify": {"blockTimestamp": 1766708400}},
|
||||
{"coronaThreshold": {"blockTimestamp": 1766708400}},
|
||||
{"hqcEncapsulate": {"blockTimestamp": 1766708400}},
|
||||
{"bls12381G1AddConfig": {"blockTimestamp": 1766708400}},
|
||||
{"bls12381G1MulConfig": {"blockTimestamp": 1766708400}},
|
||||
{"bls12381G1MSMConfig": {"blockTimestamp": 1766708400}},
|
||||
{"bls12381G2AddConfig": {"blockTimestamp": 1766708400}},
|
||||
{"bls12381G2MulConfig": {"blockTimestamp": 1766708400}},
|
||||
{"bls12381G2MSMConfig": {"blockTimestamp": 1766708400}},
|
||||
{"bls12381PairingConfig": {"blockTimestamp": 1766708400}},
|
||||
{"curve25519Config": {"blockTimestamp": 1766708400}},
|
||||
{"x25519Config": {"blockTimestamp": 1766708400}},
|
||||
{"sr25519Verify": {"blockTimestamp": 1766708400}},
|
||||
{"babyjubjubConfig": {"blockTimestamp": 1766708400}},
|
||||
{"pedersenConfig": {"blockTimestamp": 1766708400}},
|
||||
{"poseidonConfig": {"blockTimestamp": 1766708400}},
|
||||
{"pastaConfig": {"blockTimestamp": 1766708400}},
|
||||
{"xwingConfig": {"blockTimestamp": 1766708400}},
|
||||
{"computeMarketConfig": {"blockTimestamp": 1766708400}},
|
||||
{"bridgeRegistrarConfig": {"blockTimestamp": 1766708400}},
|
||||
{"stableSwapConfig": {"blockTimestamp": 1766708400}},
|
||||
{"vrfConfig": {"blockTimestamp": 1766708400}},
|
||||
{"attestationConfig": {"blockTimestamp": 1766708400}},
|
||||
{"anchorConfig": {"blockTimestamp": 1766708400}},
|
||||
{"fixedPointMathConfig": {"blockTimestamp": 1766708400}}
|
||||
],
|
||||
"stateUpgrades": []
|
||||
}
|
||||
@@ -32,8 +32,10 @@
|
||||
package extras_e2e_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -46,35 +48,21 @@ import (
|
||||
_ "github.com/luxfi/evm/precompile/registry"
|
||||
)
|
||||
|
||||
// canonicalMainnetUpgradeV46 is the byte-for-byte vendored copy of
|
||||
// luxfi/genesis configs/mainnet/upgrade.json at the v46 precompile-set
|
||||
// freeze. Layout (46 entries, monotonic by blockTimestamp):
|
||||
// - 17 already-live precompiles pinned to blockTimestamp:0 — these are
|
||||
// active at block 0 on the running mainnet (see the UPGRADE_JSON in
|
||||
// luxfi/universe k8s/lux-mainnet/luxd-startup.yaml). They MUST stay at
|
||||
// 0 so a relaunch from genesis treats them as already-applied and
|
||||
// checkPrecompileCompatible does not refuse boot.
|
||||
// - warpConfig disable@1766708399 then re-enable@1766708400 (PQ warp
|
||||
// params at the strict-PQ fork; both strictly after genesis time).
|
||||
// - 7 bls12381 family @1766708400.
|
||||
// - 20 forward-dated @1782864000 (Quasar Edition activation; strictly
|
||||
// after the strict-PQ gate so every classical primitive is gated).
|
||||
// The dead dexConfig 0x9010 key was removed when node v1.30.27 dropped
|
||||
// that precompile (its config key is no longer registered → it would
|
||||
// brick the C-Chain on boot). Vendoring it removes runtime CWD-walking
|
||||
// and makes this regression-proof hermetic in CI runners that clone only
|
||||
// luxfi/evm.
|
||||
// canonicalMainnetUpgrade is the byte-for-byte vendored copy of
|
||||
// luxfi/genesis configs/mainnet/upgrade.json. Vendoring it removes runtime
|
||||
// CWD-walking and makes this regression proof hermetic in CI runners that
|
||||
// clone only luxfi/evm. A workspace drift test keeps the copy synchronized.
|
||||
//
|
||||
// Sync contract: if luxfi/genesis configs/mainnet/upgrade.json changes,
|
||||
// regenerate this file:
|
||||
//
|
||||
// cp ../../../../genesis/configs/mainnet/upgrade.json mainnet_upgrade_v46.json
|
||||
// cp ../../../../genesis/configs/mainnet/upgrade.json mainnet_upgrade.json
|
||||
//
|
||||
// The TestVendoredFixtureMatchesCanonical guard (build-tag: requires the
|
||||
// sibling genesis checkout) detects drift before merge.
|
||||
//
|
||||
//go:embed mainnet_upgrade_v46.json
|
||||
var canonicalMainnetUpgradeV46 []byte
|
||||
//go:embed mainnet_upgrade.json
|
||||
var canonicalMainnetUpgrade []byte
|
||||
|
||||
// TestMainnetUpgradeJSON_UnmarshalsAgainstRegistry is the end-to-end
|
||||
// regression gate from Red's MEDIUM (vector 9) finding. It enforces
|
||||
@@ -198,11 +186,8 @@ func TestMainnetUpgradeJSON_RegistryRejectsUnregisteredKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegressionProof_SimulatedFortyNineEntryCanonicalFails is the
|
||||
// explicit regression proof requested in Red's MEDIUM (vector 9)
|
||||
// remediation. It simulates a pre-patch oversized canonical by
|
||||
// extending the current 45-entry canonical with three unregistered
|
||||
// keys and asserts the parser refuses the result.
|
||||
// TestRegressionProof_SimulatedUnregisteredEntriesFail extends the canonical
|
||||
// schedule with unregistered keys and asserts the parser refuses it.
|
||||
//
|
||||
// Concretely: if a future regression introduces any unregistered
|
||||
// precompile config key into the canonical upgrade.json (a module
|
||||
@@ -217,24 +202,20 @@ func TestMainnetUpgradeJSON_RegistryRejectsUnregisteredKey(t *testing.T) {
|
||||
// which luxfi/precompile v0.5.38 now registers — see the history note on
|
||||
// TestMainnetUpgradeJSON_RegistryRejectsUnregisteredKey. Sentinels keep
|
||||
// the rejection contract decoupled from real-module registration churn.
|
||||
func TestRegressionProof_SimulatedFortyNineEntryCanonicalFails(t *testing.T) {
|
||||
func TestRegressionProof_SimulatedUnregisteredEntriesFail(t *testing.T) {
|
||||
raw := readCanonicalMainnetUpgradeJSONRaw(t)
|
||||
|
||||
// Sanity-check: the post-patch canonical accepted as-is.
|
||||
var ok extras.UpgradeConfig
|
||||
require.NoError(t, json.Unmarshal(raw, &ok),
|
||||
"post-patch canonical (45 entries) must parse cleanly — see TestMainnetUpgradeJSON_UnmarshalsAgainstRegistry",
|
||||
"canonical upgrade schedule must parse cleanly — see TestMainnetUpgradeJSON_UnmarshalsAgainstRegistry",
|
||||
)
|
||||
require.Lenf(t, ok.PrecompileUpgrades, 46,
|
||||
"canonical entry count drifted: this regression-proof test was authored against 46 entries "+
|
||||
"(17 live-at-block-0 pinned to blockTimestamp:0 + warpConfig disable@1766708399 + "+
|
||||
"warpConfig re-enable@1766708400 + 7 bls12381 family@1766708400 + 20 forward-dated@1782864000, "+
|
||||
"after the dead dexConfig 0x9010 key was removed). If the canonical count legitimately "+
|
||||
"changed, update this assertion alongside.",
|
||||
require.Lenf(t, ok.PrecompileUpgrades, 49,
|
||||
"canonical entry count drifted; synchronize the fixture with luxfi/genesis and review the change",
|
||||
)
|
||||
|
||||
// Build a "pre-patch" 49-entry probe by injecting three
|
||||
// guaranteed-unregistered sentinel keys at a forward-date (46 + 3 = 49).
|
||||
// Inject three guaranteed-unregistered sentinel keys into the canonical
|
||||
// schedule. The parser must reject the first unknown key.
|
||||
var asObj map[string]any
|
||||
require.NoError(t, json.Unmarshal(raw, &asObj))
|
||||
upgrades, _ := asObj["precompileUpgrades"].([]any)
|
||||
@@ -247,12 +228,12 @@ func TestRegressionProof_SimulatedFortyNineEntryCanonicalFails(t *testing.T) {
|
||||
probe, err := json.Marshal(asObj)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The pre-patch 49-entry shape MUST fail the parser.
|
||||
// The augmented schedule MUST fail the parser.
|
||||
var bad extras.UpgradeConfig
|
||||
err = json.Unmarshal(probe, &bad)
|
||||
require.Errorf(t, err,
|
||||
"simulated 49-entry canonical (the pre-patch shape Red flagged in vector 8) was accepted by the parser — the regression guard is broken. "+
|
||||
"Expected one of the injected unregistered sentinel keys to be rejected as 'unknown precompile config'.",
|
||||
"canonical schedule with injected unregistered entries was accepted — the regression guard is broken. "+
|
||||
"Expected one of the injected keys to be rejected as 'unknown precompile config'.",
|
||||
)
|
||||
require.Containsf(t, err.Error(), "unknown precompile config",
|
||||
"expected the canonical 'unknown precompile config' error class, got: %v", err,
|
||||
@@ -261,7 +242,7 @@ func TestRegressionProof_SimulatedFortyNineEntryCanonicalFails(t *testing.T) {
|
||||
|
||||
// readCanonicalMainnetUpgradeJSONRaw returns the vendored canonical
|
||||
// mainnet upgrade.json. The fixture is embedded at build time via
|
||||
// //go:embed (see canonicalMainnetUpgradeV46) so the test is hermetic
|
||||
// //go:embed (see canonicalMainnetUpgrade) so the test is hermetic
|
||||
// in CI runners that clone only luxfi/evm — closing Red round-2 vector
|
||||
// V12 (silent t.Skip on relative-path miss).
|
||||
//
|
||||
@@ -270,10 +251,39 @@ func TestRegressionProof_SimulatedFortyNineEntryCanonicalFails(t *testing.T) {
|
||||
// next to the embed directive.
|
||||
func readCanonicalMainnetUpgradeJSONRaw(t *testing.T) []byte {
|
||||
t.Helper()
|
||||
if len(canonicalMainnetUpgradeV46) == 0 {
|
||||
if len(canonicalMainnetUpgrade) == 0 {
|
||||
// The embed directive guarantees the file is present at build
|
||||
// time; a zero-length read here would be a build-system bug.
|
||||
t.Fatal("embedded canonical mainnet upgrade.json is empty — //go:embed mainnet_upgrade_v46.json failed at build time")
|
||||
t.Fatal("embedded canonical mainnet upgrade.json is empty — //go:embed mainnet_upgrade.json failed at build time")
|
||||
}
|
||||
return canonicalMainnetUpgrade
|
||||
}
|
||||
|
||||
// TestVendoredFixturesMatchCanonicalWorkspace catches stale embedded fixtures
|
||||
// whenever evm and genesis are checked out as siblings. Standalone module CI
|
||||
// still exercises the embedded files through the parsing tests above.
|
||||
func TestVendoredFixturesMatchCanonicalWorkspace(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture []byte
|
||||
path string
|
||||
}{
|
||||
{"mainnet", canonicalMainnetUpgrade, "../../../genesis/configs/mainnet/upgrade.json"},
|
||||
{"testnet", canonicalTestnetUpgrade, "../../../genesis/configs/testnet/upgrade.json"},
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, test := range tests {
|
||||
canonical, err := os.ReadFile(test.path)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
require.NoError(t, err)
|
||||
found = true
|
||||
require.Truef(t, bytes.Equal(test.fixture, canonical),
|
||||
"%s embedded upgrade fixture drifted from %s", test.name, test.path)
|
||||
}
|
||||
if !found {
|
||||
t.Skip("sibling luxfi/genesis checkout not present")
|
||||
}
|
||||
return canonicalMainnetUpgradeV46
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user