Initializable.spec 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. //// ## Verification of `Initializable`
  2. ////
  3. //// `Initializable` is a contract used to make constructors for upgradable
  4. //// contracts. This is accomplished by applying the `initializer` modifier to any
  5. //// function that serves as a constructor, which makes this function only
  6. //// callable once. The secondary modifier `reinitializer` allows for upgrades
  7. //// that should run at most once after the contract is upgraded.
  8. ////
  9. ////
  10. //// ### Assumptions and Simplifications
  11. //// We assume `initializer()` and `reinitializer(1)` are equivalent if they
  12. //// both guarantee `_initialized` to be set to 1 after a successful call. This
  13. //// allows us to use `reinitializer(n)` as a general version that also handles
  14. //// the regular `initialzer` case.
  15. ////
  16. //// #### Harnessing
  17. //// Two harness versions were implemented, a simple flat contract, and a
  18. //// Multi-inheriting contract. The two versions together help us ensure there are
  19. //// No unexpected results because of different implementations. `Initializable` can
  20. //// Be used in many different ways but we believe these 2 cases provide good
  21. //// Coverage for all cases. In both harnesses we use getter functions for
  22. //// `_initialized` and `_initializing` and implement `initializer` and
  23. //// `reinitializer` functions that use their respective modifiers. We also
  24. //// Implement some versioned functions that are only callable in specific
  25. //// Versions of the contract to mimic upgrading contracts.
  26. ////
  27. //// #### Munging
  28. //// Variables `_initialized` and `_initializing` were changed to have internal
  29. //// visibility to be harnessable.
  30. methods {
  31. initialize(uint256, uint256, uint256) envfree
  32. reinitialize(uint256, uint256, uint256, uint8) envfree
  33. initialized() returns uint8 envfree
  34. initializing() returns bool envfree
  35. thisIsContract() returns bool envfree
  36. returnsV1() returns uint256 envfree
  37. returnsVN(uint8) returns uint256 envfree
  38. returnsAV1() returns uint256 envfree
  39. returnsAVN(uint8) returns uint256 envfree
  40. returnsBV1() returns uint256 envfree
  41. returnsBVN(uint8) returns uint256 envfree
  42. a() returns uint256 envfree
  43. b() returns uint256 envfree
  44. val() returns uint256 envfree
  45. }
  46. ////////////////////////////////////////////////////////////////////////////////
  47. //// #### Definitions ////
  48. ////////////////////////////////////////////////////////////////////////////////
  49. //// ***`isUninitialized:`*** A contract's `_initialized` variable is equal to 0.
  50. definition isUninitialized() returns bool = initialized() == 0;
  51. //// ***`isInitialized:`*** A contract's `_initialized` variable is greater than 0.
  52. definition isInitialized() returns bool = initialized() > 0;
  53. //// ***`isInitializedOnce:`*** A contract's `_initialized` variable is equal to 1.
  54. definition isInitializedOnce() returns bool = initialized() == 1;
  55. //// ***`isReinitialized:`*** A contract's `_initialized` variable is greater than 1.
  56. definition isReinitialized() returns bool = initialized() > 1;
  57. //// ***`isDisabled:`*** A contract's `_initialized` variable is equal to 255.
  58. definition isDisabled() returns bool = initialized() == 255;
  59. //////////////////////////////////////////////////////////////////////////////
  60. //// ### Properties ///////////////////////////
  61. //////////////////////////////////////////////////////////////////////////////
  62. //////////////////////////////////////////////////////////////////////////////
  63. // Invariants /////////////////////////////////////
  64. //////////////////////////////////////////////////////////////////////////////
  65. /// A contract must only ever be in an initializing state while in the middle
  66. /// of a transaction execution.
  67. invariant notInitializing()
  68. !initializing()
  69. //////////////////////////////////////////////////////////////////////////////
  70. // Rules /////////////////////////////////////
  71. //////////////////////////////////////////////////////////////////////////////
  72. /// @title Only initialized once
  73. /// @notice An initializable contract with a function that inherits the
  74. /// initializer modifier must be initializable only once
  75. rule initOnce() {
  76. uint256 val; uint256 a; uint256 b;
  77. require isInitialized();
  78. initialize@withrevert(val, a, b);
  79. assert lastReverted, "contract must only be initialized once";
  80. }
  81. /// Successfully calling reinitialize() with a version value of 1 must result
  82. /// in `_initialized` being set to 1.
  83. rule reinitializeEffects {
  84. uint256 val; uint256 a; uint256 b;
  85. reinitialize(val, a, b, 1);
  86. assert isInitializedOnce(), "reinitialize(1) must set _initialized to 1";
  87. }
  88. /// Successfully calling `initialize()` must result in `_initialized` being set to 1.
  89. /// @dev We assume `initialize()` and `reinitialize(1)` are equivalent if this rule
  90. /// and the [above rule][#reinitializeEffects] both pass.
  91. rule initializeEffects {
  92. uint256 val; uint256 a; uint256 b;
  93. initialize(val, a, b);
  94. assert isInitializedOnce(), "initialize() must set _initialized to 1";
  95. }
  96. /// A disabled initializable contract must always stay disabled.
  97. rule disabledStaysDisabled(method f) {
  98. env e; calldataarg args;
  99. bool disabledBefore = isDisabled();
  100. f(e, args);
  101. bool disabledAfter = isDisabled();
  102. assert disabledBefore => disabledAfter, "a disabled initializer must stay disabled";
  103. }
  104. /// The variable `_initialized` must not decrease.
  105. rule increasingInitialized(method f) {
  106. env e; calldataarg args;
  107. uint8 initBefore = initialized();
  108. f(e, args);
  109. uint8 initAfter = initialized();
  110. assert initBefore <= initAfter, "_initialized must only increase";
  111. }
  112. /// If `reinitialize(...)` was called successfully, then the variable
  113. /// `_initialized` must increase.
  114. /// @title Reinitialize increases `init`
  115. rule reinitializeIncreasesInit {
  116. uint256 val; uint8 n; uint256 a; uint256 b;
  117. uint8 initBefore = initialized();
  118. reinitialize(val, a, b, n);
  119. uint8 initAfter = initialized();
  120. assert initAfter > initBefore, "calling reinitialize must increase _initialized";
  121. }
  122. /// `reinitialize(n)` must be callable if the contract is not in an
  123. /// `_initializing` state and `n` is greater than `_initialized` and less than
  124. /// 255
  125. rule reinitializeLiveness {
  126. uint256 val; uint8 n; uint256 a; uint256 b;
  127. requireInvariant notInitializing();
  128. uint8 initVal = initialized();
  129. reinitialize@withrevert(val, a, b, n);
  130. assert n > initVal => !lastReverted, "reinitialize(n) call must succeed if n was greater than _initialized";
  131. }
  132. /// If `reinitialize(n)` was called successfully then `n` was greater than
  133. /// `_initialized`.
  134. rule reinitializeRule {
  135. uint256 val; uint8 n; uint256 a; uint256 b;
  136. uint8 initBefore = initialized();
  137. reinitialize(val, a, b, n);
  138. assert n > initBefore;
  139. }
  140. /// Functions implemented in the parent contract that require `_initialized` to
  141. /// be a certain value are only callable when it is that value.
  142. /// @title Reinitialize version check parent
  143. rule reinitVersionCheckParent {
  144. uint8 n;
  145. returnsVN(n);
  146. assert initialized() == n, "parent contract's version n functions must only be callable in version n";
  147. }
  148. /// Functions implemented in the child contract that require `_initialized` to
  149. /// be a certain value are only callable when it is that value.
  150. /// @title Reinitialize version check child
  151. rule reinitVersionCheckChild {
  152. uint8 n;
  153. returnsAVN(n);
  154. assert initialized() == n, "child contract's version n functions must only be callable in version n";
  155. }
  156. /// Functions implemented in the grandchild contract that require `_initialized`
  157. /// to be a certain value are only callable when it is that value.
  158. /// @title Reinitialize version check grandchild
  159. rule reinitVersionCheckGrandchild {
  160. uint8 n;
  161. returnsBVN(n);
  162. assert initialized() == n, "gransdchild contract's version n functions must only be callable in version n";
  163. }
  164. /// Calling parent initializer function must initialize all child contracts.
  165. rule inheritanceCheck {
  166. uint256 val; uint8 n; uint256 a; uint256 b;
  167. reinitialize(val, a, b, n);
  168. assert val() == val && a() == a && b() == b, "all child contract values must be initialized";
  169. }