Skip to content

Instantly share code, notes, and snippets.

@areshand
Last active October 16, 2025 15:00
Show Gist options
  • Select an option

  • Save areshand/76f49d32dbe94456d7fe1eedf51fc16a to your computer and use it in GitHub Desktop.

Select an option

Save areshand/76f49d32dbe94456d7fe1eedf51fc16a to your computer and use it in GitHub Desktop.
feature comparison
#!/usr/bin/env python3
"""
Feature audit script to generate CSV comparison of Aptos and Movement features for testnet and mainnet
This Python script provides the same functionality as the comprehensive feature comparison shell script
"""
import urllib.request
import urllib.error
import json
import sys
from typing import Dict, List, Optional
# ANSI color codes
class Colors:
GREEN = '\033[0;32m'
BLUE = '\033[0;34m'
CYAN = '\033[0;36m'
BOLD = '\033[1m'
RED = '\033[0;31m'
YELLOW = '\033[1;33m'
NC = '\033[0m' # No Color
# Network URLs
NETWORKS = {
'Aptos Mainnet': 'https://fullnode.mainnet.aptoslabs.com',
'Movement Mainnet': 'https://mainnet.movementnetwork.xyz',
'Aptos Testnet': 'https://fullnode.testnet.aptoslabs.com',
'Movement Testnet': 'https://full.testnet.movementinfra.xyz'
}
def get_feature_name(feature_id: int) -> str:
"""Get the feature name for a given feature ID"""
feature_names = {
1: "CODE_DEPENDENCY_CHECK",
2: "TREAT_FRIEND_AS_PRIVATE",
3: "SHA_512_AND_RIPEMD_160_NATIVES",
4: "APTOS_STD_CHAIN_ID_NATIVES",
5: "VM_BINARY_FORMAT_V6",
6: "COLLECT_AND_DISTRIBUTE_GAS_FEES",
7: "MULTI_ED25519_PK_VALIDATE_V2_NATIVES",
8: "BLAKE2B_256_NATIVE",
9: "RESOURCE_GROUPS",
10: "MULTISIG_ACCOUNTS",
11: "DELEGATION_POOLS",
12: "CRYPTOGRAPHY_ALGEBRA_NATIVES",
13: "BLS12_381_STRUCTURES",
14: "ED25519_PUBKEY_VALIDATE_RETURN_FALSE_WRONG_LENGTH",
15: "STRUCT_CONSTRUCTORS",
16: "PERIODICAL_REWARD_RATE_DECREASE",
17: "PARTIAL_GOVERNANCE_VOTING",
18: "SIGNATURE_CHECKER_V2",
19: "STORAGE_SLOT_METADATA",
20: "CHARGE_INVARIANT_VIOLATION",
21: "DELEGATION_POOL_PARTIAL_GOVERNANCE_VOTING",
22: "GAS_PAYER_ENABLED",
23: "APTOS_UNIQUE_IDENTIFIERS",
24: "BULLETPROOFS_NATIVES",
25: "SIGNER_NATIVE_FORMAT_FIX",
26: "MODULE_EVENT",
27: "EMIT_FEE_STATEMENT",
28: "STORAGE_DELETION_REFUND",
29: "SIGNATURE_CHECKER_V2_SCRIPT_FIX",
30: "AGGREGATOR_V2_API",
31: "SAFER_RESOURCE_GROUPS",
32: "SAFER_METADATA",
33: "SINGLE_SENDER_AUTHENTICATOR",
34: "SPONSORED_AUTOMATIC_ACCOUNT_V1_CREATION",
35: "FEE_PAYER_ACCOUNT_OPTIONAL",
36: "AGGREGATOR_V2_DELAYED_FIELDS",
37: "CONCURRENT_TOKEN_V2",
38: "LIMIT_MAX_IDENTIFIER_LENGTH",
39: "OPERATOR_BENEFICIARY_CHANGE",
40: "VM_BINARY_FORMAT_V7",
41: "RESOURCE_GROUPS_SPLIT_IN_VM_CHANGE_SET",
42: "COMMISSION_CHANGE_DELEGATION_POOL",
43: "BN254_STRUCTURES",
44: "WEBAUTHN_SIGNATURE",
45: "RECONFIGURE_WITH_DKG",
46: "KEYLESS_ACCOUNTS",
47: "KEYLESS_BUT_ZKLESS_ACCOUNTS",
48: "REMOVE_DETAILED_ERROR_FROM_HASH",
49: "JWK_CONSENSUS",
50: "CONCURRENT_FUNGIBLE_ASSETS",
51: "REFUNDABLE_BYTES",
52: "OBJECT_CODE_DEPLOYMENT",
53: "MAX_OBJECT_NESTING_CHECK",
54: "KEYLESS_ACCOUNTS_WITH_PASSKEYS",
55: "MULTISIG_V2_ENHANCEMENT",
56: "DELEGATION_POOL_ALLOWLISTING",
57: "MODULE_EVENT_MIGRATION",
58: "REJECT_UNSTABLE_BYTECODE",
59: "TRANSACTION_CONTEXT_EXTENSION",
60: "COIN_TO_FUNGIBLE_ASSET_MIGRATION",
61: "PRIMARY_APT_FUNGIBLE_STORE_AT_USER_ADDRESS",
62: "OBJECT_NATIVE_DERIVED_ADDRESS",
63: "DISPATCHABLE_FUNGIBLE_ASSET",
64: "NEW_ACCOUNTS_DEFAULT_TO_FA_APT_STORE",
65: "OPERATIONS_DEFAULT_TO_FA_APT_STORE",
66: "AGGREGATOR_V2_IS_AT_LEAST_API",
67: "CONCURRENT_FUNGIBLE_BALANCE",
68: "DEFAULT_TO_CONCURRENT_FUNGIBLE_BALANCE",
69: "LIMIT_VM_TYPE_SIZE",
70: "ABORT_IF_MULTISIG_PAYLOAD_MISMATCH",
71: "DISALLOW_USER_NATIVES",
72: "ALLOW_SERIALIZED_SCRIPT_ARGS",
73: "USE_COMPATIBILITY_CHECKER_V2",
74: "ENABLE_ENUM_TYPES",
75: "ENABLE_RESOURCE_ACCESS_CONTROL",
76: "REJECT_UNSTABLE_BYTECODE_FOR_SCRIPT",
77: "FEDERATED_KEYLESS",
78: "TRANSACTION_SIMULATION_ENHANCEMENT",
79: "COLLECTION_OWNER",
80: "NATIVE_MEMORY_OPERATIONS",
81: "ENABLE_LOADER_V2",
82: "DISALLOW_INIT_MODULE_TO_PUBLISH_MODULES",
83: "ENABLE_CALL_TREE_AND_INSTRUCTION_VM_CACHE",
84: "PERMISSIONED_SIGNER",
85: "ACCOUNT_ABSTRACTION",
86: "VM_BINARY_FORMAT_V8",
87: "BULLETPROOFS_BATCH_NATIVES",
88: "DOMAIN_ACCOUNT_ABSTRACTION",
89: "ENABLE_FUNCTION_VALUES",
90: "NEW_ACCOUNTS_DEFAULT_TO_FA_STORE",
91: "DEFAULT_ACCOUNT_RESOURCE",
92: "JWK_CONSENSUS_PER_KEY_MODE",
93: "TRANSACTION_PAYLOAD_V2",
94: "ORDERLESS_TRANSACTIONS",
95: "ENABLE_LAZY_LOADING",
96: "CALCULATE_TRANSACTION_FEE_FOR_DISTRIBUTION",
97: "DISTRIBUTE_TRANSACTION_FEE",
}
return feature_names.get(feature_id, f"UNKNOWN_FEATURE_{feature_id}")
def fetch_and_parse_features(network_name: str, network_url: str) -> List[int]:
"""
Fetch and parse features from a network using the Features resource
Returns a list of enabled feature IDs
"""
print(f"Fetching features from {network_name}...", file=sys.stderr)
try:
url = f"{network_url}/v1/accounts/0x1/resource/0x1::features::Features"
# Add User-Agent header to avoid 403 errors from some networks
req = urllib.request.Request(url)
req.add_header('User-Agent', 'Mozilla/5.0 (compatible; Aptos-Feature-Checker/1.0)')
with urllib.request.urlopen(req, timeout=10) as response:
data = json.loads(response.read().decode())
features_hex = data['data']['features']
if not features_hex or features_hex == "null":
return []
# Parse the hex string into bytes (matching Rust Vec<u8>)
if features_hex.startswith('0x'):
features_hex = features_hex[2:]
# Convert hex to bytes array (little-endian like Rust)
features_bytes = bytes.fromhex(features_hex)
enabled_features = []
# Check features 1-97 using the same logic as Rust is_enabled function
for flag_val in range(1, 98):
byte_index = flag_val // 8
bit_mask = 1 << (flag_val % 8)
# Check if byte_index is within bounds and bit is set
if byte_index < len(features_bytes) and (features_bytes[byte_index] & bit_mask != 0):
enabled_features.append(flag_val)
return sorted(enabled_features)
except Exception as e:
print(f"Error fetching features from {network_name}: {e}", file=sys.stderr)
return []
def generate_csv(network_features: Dict[str, List[int]]) -> None:
"""Generate CSV output comparing features across networks"""
# Show ALL features 1-97, not just enabled ones
all_features = list(range(1, 98))
# Print CSV header
print("Feature_ID,Feature_Name,Aptos_Mainnet,Movement_Mainnet,Aptos_Testnet,Movement_Testnet")
# Print each feature row
for feature_id in all_features:
feature_name = get_feature_name(feature_id)
# Check status for each network
statuses = []
for network in ['Aptos Mainnet', 'Movement Mainnet', 'Aptos Testnet', 'Movement Testnet']:
status = "ENABLED" if feature_id in network_features[network] else "DISABLED"
statuses.append(status)
print(f"{feature_id},{feature_name},{','.join(statuses)}")
def print_summary(network_features: Dict[str, List[int]]) -> None:
"""Print summary of enabled features per network"""
print(f"\n{Colors.CYAN}========================================================")
print("SUMMARY")
print(f"========================================================{Colors.NC}")
for network_name, features in network_features.items():
count = len(features)
print(f"{Colors.BLUE}{network_name.upper()}: {count} features enabled{Colors.NC}")
def main():
"""Main function"""
print(f"{Colors.CYAN}========================================================")
print("GENERATING FEATURE COMPARISON CSV")
print(f"========================================================{Colors.NC}")
print(f"{Colors.YELLOW}Fetching feature data from all networks...{Colors.NC}")
# Fetch features from all networks
network_features = {}
for network_name, network_url in NETWORKS.items():
network_features[network_name] = fetch_and_parse_features(network_name, network_url)
print(f"\n{Colors.CYAN}========================================================")
print("CSV OUTPUT")
print(f"========================================================{Colors.NC}")
# Generate and display CSV
generate_csv(network_features)
# Print summary
print_summary(network_features)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment