Equivalence Checking
Concord is Certora’s equivalence checker for smart contracts. It takes two contracts and one method that both contracts implement, and it proves that the two methods behave the same on every input. If they do not, Concord shows an input that makes them differ.
Typical uses:
Check that a refactored or gas-optimized method still behaves like the original.
Check that two compilers, or two compiler settings, produce equivalent bytecode for the same source.
Warning
Concord is experimental. Each run prints a warning that says so.
Installation
Concord is part of the certora-cli package. See Installation. You run
it with the certoraConcord command. If your installation does not have that
command, run the module through the Python interpreter of your certora-cli
installation:
python -m certora_cli.certoraConcord <files> --check_method "<signature>"
Quick start
Concord takes two inputs and the signature of the method to compare.
From Solidity or Vyper source, name the two contracts in the usual file:contract form:
certoraConcord Token.sol:TokenV1 Token.sol:TokenV2 --check_method "transfer(address,uint256)"
From bytecode, give two files that hold only the runtime bytecode as a hex string:
certoraConcord Counter.solc.hex Counter.solar.hex --check_method "increment(uint256)"
You can also put the same settings in a .conf file:
{
"files": [
"Counter.solc.hex",
"Counter.solar.hex"
],
"check_method": "increment(uint256)"
}
certoraConcord Default.conf
What Concord compares
Concord runs the method of contract A and the method of contract B on the same starting state, and then compares what each run did.
Starting state
Both runs start with:
the same calldata, which is arbitrary beyond the method selector,
the same storage and the same transient storage,
the same
address(this),msg.sender,msg.value,tx.originand block values (for exampleblock.numberandblock.timestamp),the same balance, nonce, code size and code hash for every account.
Immutable variables with the same name are assumed to have the same value.
Compared behavior
If any of these differ between the two runs, Concord reports a difference:
The outcome: one run returns and the other reverts.
The return data, or the revert data.
The emitted events, in order, with their topics and data.
The external calls, in order, with the callee address, the value sent and the calldata.
The final storage.
Concord compares events and external calls only on runs where neither method reverts.
Concord matches external calls by position. The first external call in A and the first external call in B get the same result, and so on. Concord then proves that the calls themselves are equal.
Behavior that is not compared
Gas use.
The kind of call (
CALL,STATICCALLorDELEGATECALL).Contract creation (
CREATEandCREATE2) andSELFDESTRUCT.Final transient storage.
Concord does not compare final ETH balances directly. They follow from the
equal starting balances and from the value of each compared external call. The
exceptions are the ETH that contract creation and SELFDESTRUCT move, because
Concord does not compare those.
Inputs
Solidity and Vyper contracts
Give exactly two contracts. Concord compiles them like the Prover does, so the usual compiler options apply, for example solc and compiler_map.
Runtime bytecode files
Give exactly two files. Each file holds the runtime (deployed) bytecode of one
contract as a hex string, with or without a 0x prefix. Concord recognizes
these files by their content, so any file extension works.
To get the runtime bytecode from solc:
solc --bin-runtime -o out/ Counter.sol
To get it from solar:
solar -Zcodegen --emit bin-runtime Counter.sol | jq -r '.contracts["Counter.sol:Counter"]["bin-runtime"]'
Concord names each contract after its file, without the last extension. It
replaces characters that are not allowed in a contract name with _. For
example, Counter.solar.hex becomes Counter_solar. If the two names are the
same, Concord adds the position of the file, for example Counter_0 and
Counter_1.
The first file is contract A, and the second file is contract B.
A bytecode file has no ABI. Concord finds the method by the selector of the
check_method signature. For the same reason, the counterexample does not
decode the method arguments.
You cannot mix bytecode files with source files in one run.
Options
check_method
The method to compare, as an ABI signature with parameter types and no spaces,
for example "transfer(address,uint256)". Do not add a contract name. This
option is mandatory, and Concord checks one method per run. To compare several
methods, run Concord once for each method.
Contract B must have a method with the same selector as the method in contract A. As a result, the two methods must have the same name and parameter types.
Prover options
Concord accepts most Prover options. Some options do not apply to equivalence checking, and Concord refuses them:
Rule and method selection:
verify,method,exclude_method,exclude_rule,parametric_contracts.Sanity, coverage and multiple results:
rule_sanity,project_sanity,coverage_info,foundry,independent_satisfy,multi_assert_check,multi_example.Linking and dispatch:
link,struct_link,address,contract_extensions,contract_recursion_limit,optimistic_contract_recursion,dynamic_bound,dynamic_dispatch,prototype.
Use prover_args to pass these settings to Concord:
-equivalenceIncludeDelegates true
: Inlines delegate calls. By default, Concord treats a delegate call like any
other external call.
-equivTraceFile <file>
: Writes the explanation of a counterexample to <file> instead of the
console.
-equivalenceUniversalCopyTracking true
: Tracks all long memory copies precisely. This can fix a false difference,
but it makes the formulas larger and the run slower.
Reading the results
The result has one rule, named after the two contracts and the method:
Equivalence of Counter_solc and Counter_solar on increment(uint256)
If the methods are equivalent, the rule is verified. If they are not, the rule
is violated with the message traces equal, or with the message
storage equal post execution when only the final storage differs.
Concord checks calls, events and the result in separate steps, and it refines
false counterexamples. Because of this, it can print several intermediate
Verified and Violated lines for the same rule while it runs. Only the final
results table counts.
For a violation, Concord explains the difference. The explanation shows the input (the sender, the call value and the method arguments), the events before the difference, and one of these messages:
The methods performed different actions, followed by the action in each method.<method> included an event *missing* in <method>.The executions left storage in conflicting states, followed by the value of the storage slot in each contract.
Limitations
Concord compares one method per run.
The two methods must have the same selector.
Concord compares storage slot by slot, so the two contracts must use the same storage layout.
Concord does not run a constructor. It compares the deployed code of the two contracts.
With bytecode inputs, counterexamples have no source mapping, and Concord cannot use the source to simplify the problem. Large methods can be slower to check than the same methods given as source.