Actualizaciones de Paquetes
El código Move (ej., módulos Move) en la blockchain de Aptos puede actualizarse. Esto permite a los propietarios de código y desarrolladores de módulos actualizar y evolucionar sus contratos bajo una sola dirección de cuenta estable, bien conocida que no cambia. Si ocurre una actualización de módulo, todos los consumidores de ese módulo recibirán automáticamente la última versión del código (ej., la próxima vez que interactúen con él).
La blockchain de Aptos soporta nativamente diferentes políticas de actualización, que permiten a los desarrolladores move definir explícitamente las restricciones sobre cómo su código move puede actualizarse. La política por defecto es compatible hacia atrás. Esto significa que las actualizaciones de código se aceptan solo si garantizan que no se rompa el almacenamiento de recursos existente o las APIs públicas por la actualización (incluyendo funciones públicas). Esta verificación de compatibilidad es posible debido a la semántica de bytecode fuertemente tipada de Move.
Notamos, sin embargo, que incluso las actualizaciones compatibles pueden tener efectos peligrosos en aplicaciones y código Move dependiente (por ejemplo, si se modifican las semánticas del módulo subyacente). Como resultado, los desarrolladores deben tener cuidado al depender de código Move de terceros que puede actualizarse en la cadena. Ver Consideraciones de seguridad para dependencias para más detalles.
Cómo funciona
Sección titulada «Cómo funciona»Las actualizaciones de código Move en la blockchain de Aptos ocurren a nivel de granularidad de paquete Move. Un paquete especifica una política de actualización en el manifiesto Move.toml
:
[package]name = "MiApp"version = "0.0.1"upgrade_policy = "compatible"...
Cómo actualizar
Sección titulada «Cómo actualizar»Para actualizar código Move ya publicado, simplemente intenta republicar el código en la misma dirección donde fue publicado previamente. Esto puede hacerse siguiendo las instrucciones para compilación de código y publicación usando el CLI de Aptos. Para un ejemplo, ver el tutorial Tu Primer Módulo Move.
Políticas de actualización
Sección titulada «Políticas de actualización»Hay dos políticas de actualización diferentes actualmente soportadas por Aptos:
compatible
: estas actualizaciones deben ser compatibles hacia atrás, específicamente:- Para almacenamiento, todas las declaraciones de struct antiguas deben ser iguales en el nuevo código. Esto asegura que el estado existente del almacenamiento sea correctamente interpretado por el nuevo código. Sin embargo, nuevas declaraciones de struct pueden agregarse.
- Para APIs, todas las funciones públicas existentes deben tener la misma firma que antes. Nuevas funciones, incluyendo funciones públicas y de entrada, pueden agregarse.
immutable
: el código no es actualizable y está garantizado a permanecer igual para siempre.
Esas políticas están ordenadas respecto a la fuerza tal que compatible < immutable
, es decir, compatible es más débil que immutable. La política de un paquete en la cadena solo puede volverse más fuerte, no más débil. Además, la política de todas las dependencias de un paquete debe ser más fuerte o igual a la política del paquete dado. Por ejemplo, un paquete immutable
no puede referirse directa o indirectamente a un paquete compatible
. Esto da a los usuarios la garantía de que no pueden ocurrir actualizaciones inesperadas bajo el capó.
Nota que hay una excepción a la regla anterior: los paquetes de framework instalados en direcciones 0x1
a 0xa
están exentos de la verificación de dependencias. Esto es necesario para poder definir un paquete immutable
basado en las bibliotecas estándar, que tienen la política compatible
para permitir actualizaciones críticas y arreglos.
Reglas de compatibilidad
Sección titulada «Reglas de compatibilidad»Cuando se usa la política de actualización compatible
, un paquete de módulos puede actualizarse. Sin embargo, las actualizaciones a módulos existentes ya publicados previamente necesitan ser compatibles y seguir las reglas a continuación:
- Todos los campos de structs existentes no pueden actualizarse. Esto significa que no se pueden agregar nuevos campos y los campos existentes no pueden modificarse.
- Todas las funciones públicas y de entrada no pueden cambiar su firma (tipos de argumento, argumento de tipo, tipos de retorno). Sin embargo, los nombres de argumentos pueden cambiar.
- Las funciones
public(friend)
se tratan como privadas y por lo tanto su firma puede cambiar arbitrariamente. Esto es seguro ya que solo los módulos en el mismo paquete pueden llamar funciones friend de todos modos, y necesitan actualizarse si la firma cambia. - Reglas de compatibilidad de actualización de tipos enum.
- Las habilidades existentes en un tipo struct/enum no pueden removerse (pero las habilidades pueden agregarse).
Al actualizar tus módulos, si ves un error de incompatibilidad, asegúrate de verificar las reglas anteriores y arreglar cualquier violación.
Consideraciones de seguridad para dependencias
Sección titulada «Consideraciones de seguridad para dependencias»Como se mencionó anteriormente, incluso las actualizaciones compatibles pueden tener efectos desastrosos para aplicaciones que dependen del código actualizado. Estos efectos pueden venir de bugs, pero también pueden ser el resultado de actualizaciones maliciosas. Por ejemplo, una dependencia actualizada puede repentinamente hacer que todas las funciones aborten, rompiendo la aplicación dependiente.
Por esta razón, es importante que los desarrolladores consideren cuidadosamente si quieren depender de código que puede actualizarse, y de ser así, qué tan confiable es el mantenedor de ese código. En algunos casos, puede ser preferible hacer fork del código y usar una versión inmutable, o solicitar al mantenedor original que cambie la política de actualización a immutable
.
Estrategias de Mitigación
Sección titulada «Estrategias de Mitigación»1. Usar Paquetes Inmutables
Sección titulada «1. Usar Paquetes Inmutables»[package]name = "MiAplicacionSegura"version = "1.0.0"upgrade_policy = "immutable"
[dependencies]# Preferir dependencias inmutables cuando sea posibleBibliotecaSegura = { git = "https://github.com/ejemplo/biblioteca-segura.git", rev = "v1.0.0-immutable"}
2. Versionado Explícito
Sección titulada «2. Versionado Explícito»[dependencies]# Fijar versiones específicas para predictibilidadDependenciaExterna = { git = "https://github.com/tercero/dependencia.git", rev = "commit-hash-específico" # Usar hash específico, no rama}
3. Auditoría de Dependencias
Sección titulada «3. Auditoría de Dependencias»# Verificar políticas de actualización de dependenciasaptos move show --upgrade-policy
# Listar todas las dependencias y sus políticasaptos move list-dependencies --with-policies
4. Fork para Control
Sección titulada «4. Fork para Control»[dependencies]# Fork de dependencia para control totalMiForkBiblioteca = { local = "../mi-fork-biblioteca", # o git = "https://github.com/mi-org/fork-biblioteca.git"}
Monitoreo de Actualizaciones
Sección titulada «Monitoreo de Actualizaciones»module 0x42::dependency_monitor { struct DependencyVersion has key { expected_version: vector<u8>, last_checked: u64, }
public fun verify_dependency_version( account: &signer, expected: vector<u8> ) acquires DependencyVersion { let addr = signer::address_of(account);
if (exists<DependencyVersion>(addr)) { let version_info = borrow_global_mut<DependencyVersion>(addr); assert!(version_info.expected_version == expected, 1); version_info.last_checked = timestamp::now_seconds(); } else { move_to(account, DependencyVersion { expected_version: expected, last_checked: timestamp::now_seconds(), }); } }}
Mejores Prácticas para Actualizaciones
Sección titulada «Mejores Prácticas para Actualizaciones»1. Planificación de Versiones
Sección titulada «1. Planificación de Versiones»# Usar versionado semántico[package]name = "MiContrato"version = "1.2.3" # MAJOR.MINOR.PATCHupgrade_policy = "compatible"
# Documentar cambios en CHANGELOG.md
2. Testing de Compatibilidad
Sección titulada «2. Testing de Compatibilidad»# Verificar compatibilidad antes de publicaraptos move test --compatibility-check
# Simular actualización en testnet primeroaptos move publish --network testnet --simulate
3. Migración Gradual
Sección titulada «3. Migración Gradual»module 0x42::versioned_module { const VERSION: u64 = 2;
struct DataV1 has key { value: u64, }
struct DataV2 has key { value: u64, extra_field: vector<u8>, // Nuevo campo version: u64, }
public fun migrate_to_v2(account: &signer, extra_data: vector<u8>) acquires DataV1 { let addr = signer::address_of(account);
if (exists<DataV1>(addr)) { let DataV1 { value } = move_from<DataV1>(addr); move_to(account, DataV2 { value, extra_field: extra_data, version: VERSION, }); } }
public fun get_data_value(addr: address): u64 acquires DataV1, DataV2 { if (exists<DataV2>(addr)) { borrow_global<DataV2>(addr).value } else if (exists<DataV1>(addr)) { borrow_global<DataV1>(addr).value } else { abort 1 } }}
4. Comunicación con Usuarios
Sección titulada «4. Comunicación con Usuarios»module 0x42::upgrade_notifications { use std::event;
struct UpgradeEvent has drop, store { old_version: vector<u8>, new_version: vector<u8>, breaking_changes: bool, migration_required: bool, upgrade_timestamp: u64, }
struct UpgradeNotifier has key { events: event::EventHandle<UpgradeEvent>, }
public fun notify_upgrade( admin: &signer, old_version: vector<u8>, new_version: vector<u8>, breaking_changes: bool, migration_required: bool ) acquires UpgradeNotifier { let notifier = borrow_global_mut<UpgradeNotifier>(signer::address_of(admin)); event::emit_event(&mut notifier.events, UpgradeEvent { old_version, new_version, breaking_changes, migration_required, upgrade_timestamp: timestamp::now_seconds(), }); }}
Herramientas de Desarrollo
Sección titulada «Herramientas de Desarrollo»Verificación de Compatibilidad
Sección titulada «Verificación de Compatibilidad»# Verificar si una actualización es compatibleaptos move check-compatibility --old-package path/to/old --new-package path/to/new
# Generar reporte de diferenciasaptos move diff --old-package path/to/old --new-package path/to/new --output diff-report.md
Simulación de Actualizaciones
Sección titulada «Simulación de Actualizaciones»# Simular actualización sin ejecutaraptos move publish --simulate --upgrade
# Testing con estado de mainnetaptos move test --network mainnet-fork
Automatización de CI/CD
Sección titulada «Automatización de CI/CD»name: Upgrade Compatibility Check
on: pull_request: paths: ['sources/**/*.move']
jobs: compatibility-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install Aptos CLI run: | curl -fsSL https://aptos.dev/scripts/install_cli.py | python3 - name: Check compatibility run: | aptos move check-compatibility \ --old-package production-version \ --new-package current-branch
Casos de Uso Avanzados
Sección titulada «Casos de Uso Avanzados»Actualizaciones Condicionales
Sección titulada «Actualizaciones Condicionales»module 0x42::conditional_upgrade { struct UpgradeConditions has key { min_timestamp: u64, required_approvals: vector<address>, approval_count: u64, }
public fun approve_upgrade(approver: &signer) acquires UpgradeConditions { let addr = signer::address_of(approver); let conditions = borrow_global_mut<UpgradeConditions>(@deployer);
assert!(vector::contains(&conditions.required_approvals, &addr), 1); conditions.approval_count = conditions.approval_count + 1; }
public fun can_upgrade(): bool acquires UpgradeConditions { let conditions = borrow_global<UpgradeConditions>(@deployer); timestamp::now_seconds() >= conditions.min_timestamp && conditions.approval_count >= vector::length(&conditions.required_approvals) }}
Rollback de Emergencia
Sección titulada «Rollback de Emergencia»module 0x42::emergency_rollback { struct EmergencyState has key { is_emergency: bool, emergency_admin: address, rollback_version: vector<u8>, }
public fun declare_emergency(admin: &signer) acquires EmergencyState { let addr = signer::address_of(admin); let emergency = borrow_global_mut<EmergencyState>(@contract); assert!(emergency.emergency_admin == addr, 1); emergency.is_emergency = true; }
public fun emergency_function_only() acquires EmergencyState { let emergency = borrow_global<EmergencyState>(@contract); if (emergency.is_emergency) { // Lógica de emergencia simplificada return }; // Lógica normal }}
Conclusión
Sección titulada «Conclusión»Las actualizaciones de paquetes en Move proporcionan un mecanismo poderoso para la evolución de smart contracts manteniendo garantías de seguridad. Las características clave incluyen:
- Políticas flexibles que balancean upgradeabilidad y inmutabilidad
- Verificación de compatibilidad automática en tiempo de publicación
- Consideraciones de seguridad para gestión de dependencias
- Herramientas robustas para testing y validación
Puntos clave para recordar:
- Las actualizaciones compatibles preservan APIs y layout de storage
- Las dependencias deben tener políticas iguales o más fuertes
- Siempre testa actualizaciones en testnet primero
- Considera cuidadosamente las implicaciones de seguridad
- Usa versionado semántico y documentación clara
- Implementa migración gradual para cambios complejos
Las actualizaciones bien planificadas permiten la evolución continua de aplicaciones Move mientras mantienen la confianza del usuario y la integridad del sistema.