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.