Last active
October 16, 2025 15:00
-
-
Save areshand/76f49d32dbe94456d7fe1eedf51fc16a to your computer and use it in GitHub Desktop.
feature comparison
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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