ERC20-ish Verification
requires "edsl.md"
requires "optimizations.md"
requires "lemmas/lemmas.k"
Solidity Code
File ERC20.sol
contains some code snippets we want to verify the functional correctness of.
Call kevm solc-to-k ERC20.sol ERC20 > erc20-bin-runtime.k
, to generate the helper K files.
Verification Module
Helper module for verification tasks.
- Add any lemmas needed for our proofs.
- Import a large body of existing lemmas from KEVM.
requires "erc20-bin-runtime.k"
module VERIFICATION
imports EDSL
imports LEMMAS
imports EVM-OPTIMIZATIONS
imports ERC20-VERIFICATION
syntax Step ::= ByteArray | Int
syntax KItem ::= runLemma ( Step ) | doneLemma ( Step )
// -------------------------------------------------------
rule <k> runLemma(S) => doneLemma(S) ... </k>
// decimals lemmas
// ---------------
rule 255 &Int X <Int 256 => true requires 0 <=Int X [simplification, smt-lemma]
rule 0 <=Int 255 &Int X => true requires 0 <=Int X [simplification, smt-lemma]
endmodule
K Specifications
Formal specifications (using KEVM) of the correctness properties for our Solidity code.
module ERC20-SPEC
imports VERIFICATION
Functional Claims
claim <k> runLemma(#bufStrict(32, #loc(ERC20._allowances[OWNER]))) => doneLemma(#buf(32, keccak(#buf(32, OWNER) ++ #buf(32, 1)))) ... </k>
requires #rangeAddress(OWNER)
Calling decimals() works
- Everything from
<mode>
to<callValue>
is boilerplate. - We are setting
<callData>
todecimals()
. - We ask the prover to show that in all cases, we will end in
EVMC_SUCCESS
(rollback) when this happens. - The
<output>
cell specifies that we correctly lookup theDECIMALS
value from the account storage.
claim [decimals]:
<mode> NORMAL </mode>
<schedule> ISTANBUL </schedule>
<callStack> .List </callStack>
<program> #binRuntime(ERC20) </program>
<jumpDests> #computeValidJumpDests(#binRuntime(ERC20)) </jumpDests>
<id> ACCTID => ?_ </id>
<localMem> .Memory => ?_ </localMem>
<memoryUsed> 0 => ?_ </memoryUsed>
<wordStack> .WordStack => ?_ </wordStack>
<pc> 0 => ?_ </pc>
<endPC> _ => ?_ </endPC>
<gas> #gas(_VGAS) => ?_ </gas>
<callValue> 0 => ?_ </callValue>
<callData> ERC20.decimals() </callData>
<k> #execute => #halt ... </k>
<output> .ByteArray => #buf(32, DECIMALS) </output>
<statusCode> _ => EVMC_SUCCESS </statusCode>
<account>
<acctID> ACCTID </acctID>
<storage> ACCT_STORAGE </storage>
...
</account>
requires DECIMALS_KEY ==Int #loc(ERC20._decimals)
andBool DECIMALS ==Int 255 &Int #lookup(ACCT_STORAGE, DECIMALS_KEY)
Calling totalSupply() works
- Everything from
<mode>
to<callValue>
is boilerplate. - We are setting
<callData>
tototalSupply()
. - We ask the prover to show that in all cases, we will end in
EVMC_SUCCESS
(rollback) when this happens. - The
<output>
cell specifies that we correctly lookup theTS
value from the account storage.
claim [totalSupply]:
<mode> NORMAL </mode>
<schedule> ISTANBUL </schedule>
<callStack> .List </callStack>
<program> #binRuntime(ERC20) </program>
<jumpDests> #computeValidJumpDests(#binRuntime(ERC20)) </jumpDests>
<id> ACCTID => ?_ </id>
<localMem> .Memory => ?_ </localMem>
<memoryUsed> 0 => ?_ </memoryUsed>
<wordStack> .WordStack => ?_ </wordStack>
<pc> 0 => ?_ </pc>
<endPC> _ => ?_ </endPC>
<gas> #gas(_VGAS) => ?_ </gas>
<callValue> 0 => ?_ </callValue>
<callData> ERC20.totalSupply() </callData>
<k> #execute => #halt ... </k>
<output> .ByteArray => #buf(32, TOTALSUPPLY) </output>
<statusCode> _ => EVMC_SUCCESS </statusCode>
<account>
<acctID> ACCTID </acctID>
<storage> ACCT_STORAGE </storage>
...
</account>
requires TOTALSUPPLY_KEY ==Int #loc(ERC20._totalSupply)
andBool TOTALSUPPLY ==Int #lookup(ACCT_STORAGE, TOTALSUPPLY_KEY)
Calling Approve works
- Everything from
<mode>
to<substate>
is boilerplate. - We are setting
<callData>
toapprove(SPENDER, AMOUNT)
. - We ask the prover to show that in all cases, we will end in
EVMC_SUCCESS
whenSENDER
orOWNER
is notaddress(0)
, and that we will end inEVMC_REVERT
(rollback) when either one of them is. - We take the OWNER from the
<caller>
cell, which is themsg.sender
. - The
<output>
should be#buf(32, bool2Word(True))
if the function does not revert. - The storage locations for Allowance should be updated accordingly.
claim [approve.success]:
<mode> NORMAL </mode>
<schedule> ISTANBUL </schedule>
<callStack> .List </callStack>
<program> #binRuntime(ERC20) </program>
<jumpDests> #computeValidJumpDests(#binRuntime(ERC20)) </jumpDests>
<static> false </static>
<id> ACCTID => ?_ </id>
<caller> OWNER => ?_ </caller>
<localMem> .Memory => ?_ </localMem>
<memoryUsed> 0 => ?_ </memoryUsed>
<wordStack> .WordStack => ?_ </wordStack>
<pc> 0 => ?_ </pc>
<endPC> _ => ?_ </endPC>
<gas> #gas(_VGAS) => ?_ </gas>
<callValue> 0 => ?_ </callValue>
<substate> _ => ?_ </substate>
<callData> ERC20.approve(SPENDER, AMOUNT) </callData>
<k> #execute => #halt ... </k>
<output> .ByteArray => #buf(32, 1) </output>
<statusCode> _ => EVMC_SUCCESS </statusCode>
<account>
<acctID> ACCTID </acctID>
<storage> ACCT_STORAGE => ACCT_STORAGE [ ALLOWANCE_KEY <- AMOUNT ] </storage>
...
</account>
requires ALLOWANCE_KEY ==Int #loc(ERC20._allowances[OWNER][SPENDER])
andBool #rangeAddress(OWNER)
andBool #rangeAddress(SPENDER)
andBool #rangeUInt(256, AMOUNT)
andBool OWNER =/=Int 0
andBool SPENDER =/=Int 0
claim [approve.revert]:
<mode> NORMAL </mode>
<schedule> ISTANBUL </schedule>
<callStack> .List </callStack>
<program> #binRuntime(ERC20) </program>
<jumpDests> #computeValidJumpDests(#binRuntime(ERC20)) </jumpDests>
<static> false </static>
<id> ACCTID => ?_ </id>
<caller> OWNER => ?_ </caller>
<localMem> .Memory => ?_ </localMem>
<memoryUsed> 0 => ?_ </memoryUsed>
<wordStack> .WordStack => ?_ </wordStack>
<pc> 0 => ?_ </pc>
<endPC> _ => ?_ </endPC>
<gas> #gas(_VGAS) => ?_ </gas>
<callValue> 0 => ?_ </callValue>
<substate> _ => ?_ </substate>
<callData> ERC20.approve(SPENDER, AMOUNT) </callData>
<k> #execute => #halt ... </k>
<output> _ => ?_ </output>
<statusCode> _ => EVMC_REVERT </statusCode>
<account>
<acctID> ACCTID </acctID>
<storage> _ACCT_STORAGE </storage>
...
</account>
requires #rangeAddress(OWNER)
andBool #rangeAddress(SPENDER)
andBool #rangeUInt(256, AMOUNT)
andBool (OWNER ==Int 0 orBool SPENDER ==Int 0)
endmodule