deployer.move 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. /// This is a deployer module that enables deploying a package to an autonomous
  2. /// account, or a "resource account".
  3. ///
  4. /// By default, when an account publishes a package, the modules get deployed at
  5. /// the deployer account's address, and retains authority over the package.
  6. /// For applications that want to guard their upgradeability by some other
  7. /// mechanism (such as decentralized governance), this setup is inadequate.
  8. ///
  9. /// The solution is to generate an autonomous account whose signer is controlled
  10. /// by the runtime, as opposed to a private key (think program-derived addresses
  11. /// in Solana). The package is then deployed at this address, guaranteeing that
  12. /// effectively the program can upgrade itself in whatever way it wishes, and no
  13. /// one else can.
  14. ///
  15. /// The `aptos_framework::account` module provides a way to generate such
  16. /// resource accounts, where the account's pubkey is derived from the
  17. /// transaction signer's account hashed together with some seed. In pseudocode:
  18. ///
  19. /// resource_address = sha3_256(tx_sender + seed)
  20. ///
  21. /// The `aptos_framework::account::create_resource_account` function creates such an
  22. /// account, and returns a newly created signer and a `SignerCapability`, which
  23. /// is an affine resource (i.e. can be dropped but not copied). Holding a
  24. /// `SignerCapability` (which can be stored) grants the ability to recover the
  25. /// signer (which cannot be stored). Thus, the program will need to hold its own
  26. /// `SignerCapability` in storage. It is crucial that this capability is kept
  27. /// "securely", i.e. gated behind proper access control, which essentially means
  28. /// in a struct with a private field. This capability is retrieved and stored in
  29. /// the module's initializer.
  30. ///
  31. /// So the strategy is as follows:
  32. ///
  33. /// 1. An account calls `deploy_derived` with the bytecode and the address seed
  34. /// The function will create the resource account and deploy the bytecode at the
  35. /// resource account's address. It will then temporarily lock up the
  36. /// `SignerCapability` in `DeployingSignerCapability` together with the deployer
  37. /// account's address.
  38. ///
  39. /// Then there are two options:
  40. /// 2.a. The module has an `init_module` entry point. This is a special function
  41. /// that gets called by the runtime immediately after the module is deployed.
  42. /// The only argument passed to this function is the module account's signer, in
  43. /// this case the resource account itself. The resource account can call
  44. /// `claim_signer_capability` and retrieve the signer capability to store it in
  45. /// storage for later. This destroys the `DeployingSignerCapability`.
  46. ///
  47. /// 2.b The module has a custom initializer function. This might be necessary
  48. /// if the initializer needs additional arguments, which is not supported by
  49. /// `init_module`. This initializer will have to be called in a separate
  50. /// transaction (since after deploying a module, it cannot be called in the same
  51. /// transaction). The initializer may call `claim_signer_capability` which
  52. /// destroys the `DeployingSignerCability` and extracts the `SignerCapability`
  53. /// from it. Note that this can _only_ be called by the deployer account.
  54. ///
  55. /// The `claim_signer_capability` function checks that it's called _either_ by
  56. /// the resource account itself or the deployer of the resource account.
  57. ///
  58. /// 3. After the `SignerCapability` is extracted, the program can now recover
  59. /// the signer from it and store the capability in its own storage in a secure
  60. /// resource type.
  61. ///
  62. /// Note that the fact that `SignerCapability` has no copy ability means that
  63. /// it's guaranteed to be globally unique for a given resource account (since
  64. /// the function that creates it can only be called once as it implements replay
  65. /// protection). Thanks to this, as long as the deployed program is successfully
  66. /// initialized and stores its signer capability, we can be sure that only the
  67. /// program can authorize its own upgrades.
  68. ///
  69. module deployer::deployer {
  70. use aptos_framework::account;
  71. use aptos_framework::code;
  72. use aptos_framework::signer;
  73. const E_NO_DEPLOYING_SIGNER_CAPABILITY: u64 = 0;
  74. const E_INVALID_DEPLOYER: u64 = 1;
  75. /// Resource for temporarily holding on to the signer capability of a newly
  76. /// deployed program before the program claims it.
  77. struct DeployingSignerCapability has key {
  78. signer_cap: account::SignerCapability,
  79. deployer: address,
  80. }
  81. public entry fun deploy_derived(
  82. deployer: &signer,
  83. metadata_serialized: vector<u8>,
  84. code: vector<vector<u8>>,
  85. seed: vector<u8>
  86. ) acquires DeployingSignerCapability {
  87. let deployer_address = signer::address_of(deployer);
  88. let resource = account::create_resource_address(&deployer_address, seed);
  89. let resource_signer: signer;
  90. if (exists<DeployingSignerCapability>(resource)) {
  91. // if the deploying signer capability already exists, it means that
  92. // the resource account hasn't claimed it. This code path allows the
  93. // deployer to upgrade the resource account's contract, but only
  94. // before the resource account is initialised.
  95. // You might think that this is a very niche use-case, but this
  96. // happened when trying to deploy wormhole to aptos testnet, as the
  97. // bytecode we published had been compiled with an older version of
  98. // the stdlib, and had native dependency issues. These are checked
  99. // lazily (i.e. at runtime, and not deployment time), which meant
  100. // that the contract was effectively broken, i.e. unable to
  101. // initialise itself, and therefore unable to upgrade.
  102. let deploying_cap = borrow_global<DeployingSignerCapability>(resource);
  103. resource_signer = account::create_signer_with_capability(&deploying_cap.signer_cap);
  104. } else {
  105. // if it doesn't exist, it means that either
  106. // a) the account hasn't been created yet at all
  107. // b) the account has already claimed the signer capability
  108. //
  109. // in the case of a), we just create it. In case of b), the account
  110. // creation will fail, since the resource account already exist,
  111. // effectively providing replay protection.
  112. let signer_cap: account::SignerCapability;
  113. (resource_signer, signer_cap) = account::create_resource_account(deployer, seed);
  114. move_to(&resource_signer, DeployingSignerCapability { signer_cap, deployer: deployer_address });
  115. };
  116. code::publish_package_txn(&resource_signer, metadata_serialized, code);
  117. }
  118. public fun claim_signer_capability(
  119. caller: &signer,
  120. resource: address
  121. ): account::SignerCapability acquires DeployingSignerCapability {
  122. assert!(exists<DeployingSignerCapability>(resource), E_NO_DEPLOYING_SIGNER_CAPABILITY);
  123. let DeployingSignerCapability { signer_cap, deployer } = move_from(resource);
  124. let caller_addr = signer::address_of(caller);
  125. assert!(
  126. caller_addr == deployer || caller_addr == resource,
  127. E_INVALID_DEPLOYER
  128. );
  129. signer_cap
  130. }
  131. }