github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/test/kontrol/pausability-lemmas.md (about)

     1  Kontrol Lemmas
     2  ==============
     3  
     4  Lemmas are K rewrite rules that enhance the reasoning power of Kontrol. For more information on lemmas, please consult [this section](https://docs.runtimeverification.com/kontrol/guides/advancing-proofs) of the Kontrol documentation.
     5  
     6  This file contains the lemmas required to run the proofs included in the [proofs](./proofs) folder. Some of these lemmas are general enough to likely be incorporated into future versions of Kontrol, while others are specific to the challenges presented by the proofs.
     7  
     8  Similarly to other files such as [`cheatcodes.md`](https://github.com/runtimeverification/kontrol/blob/master/src/kontrol/kdist/cheatcodes.md), we use the idiomatic way of programming in Kontrol, which is [literate programming](https://en.wikipedia.org/wiki/Literate_programming), allowing for better documentation of the code.
     9  
    10  ## Imports
    11  
    12  For writing the lemmas, we use the [`foundry.md`](https://github.com/runtimeverification/kontrol/blob/master/src/kontrol/kdist/foundry.md) file. This file contains and imports all of the definitions from KEVM and Kontrol on top of which we write the lemmas.
    13  
    14  ```k
    15  requires "foundry.md"
    16  
    17  module PAUSABILITY-LEMMAS
    18      imports BOOL
    19      imports FOUNDRY
    20      imports INT-SYMBOLIC
    21  ```
    22  
    23  ## Arithmetic
    24  
    25  Lemmas on arithmetic reasoning. Specifically, on: cancellativity, inequalites in which the two sides are of different signs; and the rounding-up mechanism of the Solidity compiler (expressed through `notMaxUInt5 &Int ( X +Int 31 )`, which rounds up `X` to the nearest multiple of 32).
    26  
    27  ```k
    28      // Cancellativity #1
    29      rule A +Int ( (B -Int A) +Int C ) => B +Int C [simplification]
    30  
    31      // Cancellativity #2
    32      rule (A -Int B) -Int (C -Int B) => A -Int C [simplification]
    33  
    34      // Cancellativity #3
    35      rule A -Int (A +Int B) => 0 -Int B [simplification]
    36  
    37      // Various inequalities
    38      rule X  <Int A &Int B => true requires X <Int 0 andBool 0 <=Int A andBool 0 <=Int B [concrete(X), simplification]
    39      rule X  <Int A +Int B => true requires X <Int 0 andBool 0 <=Int A andBool 0 <=Int B [concrete(X), simplification]
    40      rule X <=Int A +Int B => true requires X <Int 0 andBool 0 <=Int A andBool 0 <=Int B [concrete(X), simplification]
    41  
    42      // Upper bound on (pow256 - 32) &Int lengthBytes(X)
    43      rule notMaxUInt5 &Int Y <=Int Y => true
    44        requires 0 <=Int Y
    45        [simplification]
    46  
    47      // Bounds on notMaxUInt5 &Int ( X +Int 31 )
    48      rule X <=Int   notMaxUInt5 &Int ( X +Int 31 )          => true requires 0 <=Int X                   [simplification]
    49      rule X <=Int   notMaxUInt5 &Int ( Y +Int 31 )          => true requires X <=Int 0 andBool 0 <=Int Y [simplification, concrete(X)]
    50      rule X <=Int ( notMaxUInt5 &Int ( X +Int 31 ) ) +Int Y => true requires 0 <=Int X andBool 0 <=Int Y [simplification, concrete(Y)]
    51  
    52      rule notMaxUInt5 &Int X +Int 31 <Int Y => true requires 0 <=Int X andBool X +Int 32 <=Int Y [simplification, concrete(Y)]
    53  
    54      rule notMaxUInt5 &Int X +Int 31 <Int X +Int 32 => true requires 0 <=Int X [simplification]
    55  ```
    56  
    57  ## `#asWord`
    58  
    59  Lemmas about [`#asWord`](https://github.com/runtimeverification/evm-semantics/blob/master/kevm-pyk/src/kevm_pyk/kproj/evm-semantics/evm-types.md#bytes-helper-functions). `#asWord(B)` interprets the byte array `B` as a single word (with MSB first).
    60  
    61  ```k
    62      // Move to function parameters
    63      rule { #asWord ( BA1 ) #Equals #asWord ( BA2 ) } => #Top
    64        requires BA1 ==K BA2
    65        [simplification]
    66  
    67      // #asWord ignores leading zeros
    68      rule #asWord ( BA1 +Bytes BA2 ) => #asWord ( BA2 )
    69        requires #asInteger(BA1) ==Int 0
    70        [simplification, concrete(BA1)]
    71  
    72      // `#asWord` of a byte array cannot equal a number that cannot fit within the byte array
    73      rule #asWord ( BA ) ==Int Y => false
    74          requires lengthBytes(BA) <=Int 32
    75           andBool (2 ^Int (8 *Int lengthBytes(BA))) <=Int Y
    76          [concrete(Y), simplification]
    77  ```
    78  
    79  ## `#asInteger`
    80  
    81  Lemmas about [`#asInteger`](https://github.com/runtimeverification/evm-semantics/blob/master/kevm-pyk/src/kevm_pyk/kproj/evm-semantics/evm-types.md#bytes-helper-functions). `#asInteger(X)` interprets the byte array `X` as a single arbitrary-precision integer (with MSB first).
    82  
    83  ```k
    84      // Conversion from bytes always yields a non-negative integer
    85      rule 0 <=Int #asInteger ( _ ) => true [simplification]
    86  ```
    87  
    88  ## `#padRightToWidth`
    89  
    90  Lemmas about [`#padRightToWidth`](https://github.com/runtimeverification/evm-semantics/blob/master/kevm-pyk/src/kevm_pyk/kproj/evm-semantics/evm-types.md#bytes-helper-functions). `#padRightToWidth(W, BA)` right-pads the byte array `BA` with zeros so that the resulting byte array has length `W`.
    91  
    92  ```k
    93      // Definitional expansion
    94      rule #padRightToWidth (W, BA) => BA +Bytes #buf(W -Int lengthBytes(BA), 0)
    95        [concrete(W), simplification]
    96  ```
    97  
    98  ## `#range(BA, START, WIDTH)`
    99  
   100  Lemmas about [`#range(BA, START, WIDTH)`](https://github.com/runtimeverification/evm-semantics/blob/master/kevm-pyk/src/kevm_pyk/kproj/evm-semantics/evm-types.md#bytes-helper-functions). `#range(BA, START, WIDTH)` returns the range of `BA` from index `START` of width `WIDTH`.
   101  
   102  ```k
   103      // Parameter equality
   104      rule { #range (BA, S, W1) #Equals #range (BA, S, W2) } => #Top
   105        requires W1 ==Int W2
   106        [simplification]
   107  ```
   108  
   109  ## Byte array indexing and update
   110  
   111  Lemmas about [`BA [ I ]` and `BA1 [ S := BA2 ]`](https://github.com/runtimeverification/evm-semantics/blob/master/kevm-pyk/src/kevm_pyk/kproj/evm-semantics/evm-types.md#element-access). `BA [ I ]` returns the integer representation of the `I`-th byte of byte array `BA`. `BA1 [ S := BA2 ]` updates the byte array `BA1` with byte array `BA2` from index `S`.
   112  
   113  
   114  ```k
   115      // Byte indexing in terms of #asWord
   116      rule BA [ X ] => #asWord ( #range (BA, X, 1) )
   117        requires X <=Int lengthBytes(BA)
   118        [simplification(40)]
   119  
   120      // Empty update has no effect
   121      rule BA [ START := b"" ] => BA
   122        requires 0 <=Int START andBool START <=Int lengthBytes(BA)
   123        [simplification]
   124  
   125      // Update passes to right operand of concat if start position is beyond the left operand
   126      rule ( BA1 +Bytes BA2 ) [ S := BA ] => BA1 +Bytes ( BA2 [ S -Int lengthBytes(BA1) := BA ] )
   127        requires lengthBytes(BA1) <=Int S
   128        [simplification]
   129  
   130      // Consecutive quasi-contiguous byte-array update
   131      rule BA [ S1 := BA1 ] [ S2 := BA2 ] => BA [ S1 := #range(BA1, 0, S2 -Int S1) +Bytes BA2 ]
   132        requires 0 <=Int S1 andBool S1 <=Int S2 andBool S2 <=Int S1 +Int lengthBytes(BA1)
   133        [simplification]
   134  
   135      // Parameter equality: byte-array update
   136      rule { BA1:Bytes [ S1 := BA2 ] #Equals BA3:Bytes [ S2 := BA4 ] } => #Top
   137        requires BA1 ==K BA3 andBool S1 ==Int S2 andBool BA2 ==K BA4
   138        [simplification]
   139  ```
   140  
   141  Summaries
   142  ---------
   143  
   144  Functions summaries are rewrite rules that capture (summarize) the effects of executing a function. Such rules allow Kontrol to, instead of executing the function itself, just apply the summary rule.
   145  
   146  ## `copy_memory_to_memory` summary
   147  
   148  The following rule summarises the behavior of the `copy_memory_to_memory` function. This function is automatically generated by the Solidity compiler. In its Yul form, it is as follows:
   149  
   150  ```solidity
   151  function copy_memory_to_memory(src, dst, length) {
   152    let i := 0
   153    for { } lt(i, length) { i := add(i, 32) }
   154    {
   155      mstore(add(dst, i), mload(add(src, i)))
   156    }
   157    if gt(i, length)
   158    {
   159      // clear end
   160      mstore(add(dst, length), 0)
   161    }
   162  }
   163  ```
   164  
   165  It is used to copy `length` bytes of memory from index `src` to index `dest`, doing so in steps of 32 bytes, and right-padding with zeros to a multiple of 32.
   166  
   167  Following the compiler constraints, we enforce a limit on the length of byte arrays and indices into byte arrays.
   168  
   169  ```k
   170      syntax Int ::= "maxBytesLength" [alias]
   171      rule maxBytesLength => 9223372036854775808
   172  ```
   173  
   174  The summary lemma is as follows, with commentary inlined:
   175  
   176  ```k
   177      rule [copy-memory-to-memory-summary]:
   178        <k> #execute ... </k>
   179        <useGas> false </useGas>
   180        <schedule> SHANGHAI </schedule>
   181        <jumpDests> JUMPDESTS </jumpDests>
   182        // The program and program counter are symbolic, focusing on the part we will be executing (CP)
   183        <program> PROGRAM </program>
   184        <pc> PCOUNT => PCOUNT +Int 53 </pc>
   185        // The word stack has the appropriate form, as per the compiled code
   186        <wordStack> LENGTH : _ : SRC : DEST : WS </wordStack>
   187        // The program copies LENGTH bytes of memory from SRC +Int 32 to DEST +Int OFFSET,
   188        // padded with 32 zeros in case LENGTH is not divisible by 32
   189        <localMem>
   190          LM => LM [ DEST +Int 32 := #range ( LM, SRC +Int 32, LENGTH ) +Bytes
   191                                     #buf ( ( ( notMaxUInt5 &Int ( LENGTH +Int maxUInt5 ) ) -Int LENGTH ) , 0 ) +Bytes
   192                                     #buf ( ( ( ( 32 -Int ( ( notMaxUInt5 &Int ( LENGTH +Int maxUInt5 ) ) -Int LENGTH ) ) ) modInt 32 ), 0 ) ]
   193        </localMem>
   194        requires
   195         // The current program we are executing differs from the original one only in the hardcoded jump addresses,
   196         // which are now relative to PCOUNT, and the hardcoded offset, which is now symbolic.
   197                 #range(PROGRAM, PCOUNT, 53) ==K b"`\x00[\x81\x81\x10\x15b\x00\x81`W` \x81\x85\x01\x81\x01Q\x86\x83\x01\x82\x01R\x01b\x00\x81BV[\x81\x81\x11\x15b\x00\x81sW`\x00` \x83\x87\x01\x01R[P"
   198                                                 [ 08 := #buf(3, PCOUNT +Int 32) ]
   199                                                 [ 28 := #buf(3, PCOUNT +Int  2) ]
   200                                                 [ 38 := #buf(3, PCOUNT +Int 51) ]
   201  
   202         // Various well-formedness constraints. In particular, the maxBytesLength-related ones are present to
   203         // remove various chops that would otherwise creep into the execution, and are reasonable since byte
   204         // arrays in actual programs would never reach that size.
   205         andBool 0 <=Int PCOUNT
   206         andBool 0 <=Int LENGTH andBool LENGTH <Int maxBytesLength
   207         andBool 0 <=Int SRC    andBool SRC    <Int maxBytesLength
   208         andBool 0 <=Int DEST   andBool DEST   <Int maxBytesLength
   209         andBool #sizeWordStack(WS) <=Int 1015
   210  
   211         andBool SRC +Int LENGTH <=Int DEST // No overlap between source and destination
   212         andBool DEST <=Int lengthBytes(LM) // Destination starts within current memory
   213         // All JUMPDESTs in the program are valid
   214         andBool (PCOUNT +Int 2) in JUMPDESTS andBool (PCOUNT +Int 32) in JUMPDESTS andBool (PCOUNT +Int 51) in JUMPDESTS
   215         andBool PCOUNT +Int 51 <Int 2 ^Int 16  // and fit into two bytes
   216        [priority(30), concrete(JUMPDESTS, PROGRAM, PCOUNT), preserves-definedness]
   217  
   218  endmodule
   219  ```
   220  
   221  This summary is required to enable reasoning about byte arrays or arbitrary (symbolic) length. Otherwise, we would have to deal with a loop as the Solidity compiler copies memory to memory in chunks of 32 bytes at a time, and as this loop would have a symbolic bound, the symbolic execution would either have to be bounded or would not terminate.
   222  
   223  Unfortunately, the Solidity compiler optimizes the compiled bytecode in unpredictable ways, meaning that changes in the test suite can affect the compilation of `copy_memory_to_memory`. In light of this, and in order to be able to use our summary, we opt against using the `Test` contract of `forge-std`.
   224  
   225  The looping issue has been recognized as such by the Solidity developers, and starting from version [0.8.24](https://soliditylang.org/blog/2024/01/26/solidity-0.8.24-release-announcement/) EVM comes with an `MCOPY` instruction ([EIP-5656](https://eips.ethereum.org/EIPS/eip-5656)), which copies a part of memory to another part of memory as an atomic action. If the development were to move to this (or higher) version of the compiler, there would be no need for this summary.