From c40d9c9b8af62574feda84204ea90960865d2e3e Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 11 Mar 2024 10:27:05 +0000 Subject: [PATCH 001/152] Autofinder bug in EVault --- certora/conf/EVault.conf | 29 +++++++++++++++++++ .../benchmarking/EVault_benchmarking.spec | 0 2 files changed, 29 insertions(+) create mode 100644 certora/conf/EVault.conf create mode 100644 certora/specs/benchmarking/EVault_benchmarking.spec diff --git a/certora/conf/EVault.conf b/certora/conf/EVault.conf new file mode 100644 index 00000000..dedd0102 --- /dev/null +++ b/certora/conf/EVault.conf @@ -0,0 +1,29 @@ +{ + "files": [ + "src/EVault/EVault.sol" + ], + "verify": "EVault:certora/specs/benchmarking/EVault_benchmarking.spec", + "solc": "solc8.23", + "solc_optimize": "10000", + "rule_sanity": "basic", + "assert_autofinder_success" : true, + "solc_via_ir": true, + "msg": "EVault benchmarking", + "optimistic_loop": true, + "optimistic_hashing": true, + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/specs/benchmarking/EVault_benchmarking.spec b/certora/specs/benchmarking/EVault_benchmarking.spec new file mode 100644 index 00000000..e69de29b From ff74e6054e76a02f164169fb912a3f4d80acae11 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 11 Mar 2024 12:42:53 +0000 Subject: [PATCH 002/152] Confs for EVault/modules. Start interestRateModels --- certora/conf/EVault/DToken.conf | 27 ++++++++ certora/conf/{ => EVault}/EVault.conf | 10 ++- .../conf/EVault/modules/BalanceForwarder.conf | 27 ++++++++ certora/conf/EVault/modules/Borrowing.conf | 27 ++++++++ certora/conf/EVault/modules/Governance.conf | 27 ++++++++ certora/conf/EVault/modules/Initialize.conf | 27 ++++++++ certora/conf/EVault/modules/Liquidation.conf | 27 ++++++++ .../conf/EVault/modules/ModuleDispatch.conf | 27 ++++++++ certora/conf/EVault/modules/RiskManager.conf | 27 ++++++++ certora/conf/EVault/modules/Token.conf | 27 ++++++++ certora/conf/EVault/modules/Vault.conf | 27 ++++++++ .../interestRateModels/BaseIRMLinearKink.conf | 27 ++++++++ certora/harness/ModuleDispatchHarness.sol | 10 +++ certora/specs/benchmarking/Benchmarking.spec | 67 +++++++++++++++++++ certora/specs/benchmarking/EVault/DToken.spec | 1 + certora/specs/benchmarking/EVault/EVault.spec | 1 + .../EVault/modules/BalanceForwarder.spec | 1 + .../benchmarking/EVault_benchmarking.spec | 0 18 files changed, 381 insertions(+), 6 deletions(-) create mode 100644 certora/conf/EVault/DToken.conf rename certora/conf/{ => EVault}/EVault.conf (67%) create mode 100644 certora/conf/EVault/modules/BalanceForwarder.conf create mode 100644 certora/conf/EVault/modules/Borrowing.conf create mode 100644 certora/conf/EVault/modules/Governance.conf create mode 100644 certora/conf/EVault/modules/Initialize.conf create mode 100644 certora/conf/EVault/modules/Liquidation.conf create mode 100644 certora/conf/EVault/modules/ModuleDispatch.conf create mode 100644 certora/conf/EVault/modules/RiskManager.conf create mode 100644 certora/conf/EVault/modules/Token.conf create mode 100644 certora/conf/EVault/modules/Vault.conf create mode 100644 certora/conf/interestRateModels/BaseIRMLinearKink.conf create mode 100644 certora/harness/ModuleDispatchHarness.sol create mode 100644 certora/specs/benchmarking/Benchmarking.spec create mode 100644 certora/specs/benchmarking/EVault/DToken.spec create mode 100644 certora/specs/benchmarking/EVault/EVault.spec create mode 100644 certora/specs/benchmarking/EVault/modules/BalanceForwarder.spec delete mode 100644 certora/specs/benchmarking/EVault_benchmarking.spec diff --git a/certora/conf/EVault/DToken.conf b/certora/conf/EVault/DToken.conf new file mode 100644 index 00000000..fe908e67 --- /dev/null +++ b/certora/conf/EVault/DToken.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/EVault/DToken.sol" + ], + "verify": "DToken:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + // "solc_via_ir": true, + // "solc_optimize": "10000", + "rule_sanity": "basic", + "assert_autofinder_success" : true, + "msg": "DToken benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/conf/EVault.conf b/certora/conf/EVault/EVault.conf similarity index 67% rename from certora/conf/EVault.conf rename to certora/conf/EVault/EVault.conf index dedd0102..4c69536d 100644 --- a/certora/conf/EVault.conf +++ b/certora/conf/EVault/EVault.conf @@ -2,15 +2,13 @@ "files": [ "src/EVault/EVault.sol" ], - "verify": "EVault:certora/specs/benchmarking/EVault_benchmarking.spec", + "verify": "EVault:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", - "solc_optimize": "10000", "rule_sanity": "basic", - "assert_autofinder_success" : true, - "solc_via_ir": true, + // fails currently "assert_autofinder_success" : true, + // "solc_via_ir": true, + // "solc_optimize": "10000", "msg": "EVault benchmarking", - "optimistic_loop": true, - "optimistic_hashing": true, "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" diff --git a/certora/conf/EVault/modules/BalanceForwarder.conf b/certora/conf/EVault/modules/BalanceForwarder.conf new file mode 100644 index 00000000..73a197db --- /dev/null +++ b/certora/conf/EVault/modules/BalanceForwarder.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/EVault/modules/BalanceForwarder.sol" + ], + "verify": "BalanceForwarder:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + // "solc_via_ir": true, + // "solc_optimize": "10000", + "rule_sanity": "basic", + // fails currently "assert_autofinder_success" : true, + "msg": "BalanceForwarder benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/conf/EVault/modules/Borrowing.conf b/certora/conf/EVault/modules/Borrowing.conf new file mode 100644 index 00000000..45152291 --- /dev/null +++ b/certora/conf/EVault/modules/Borrowing.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/EVault/modules/Borrowing.sol" + ], + "verify": "Borrowing:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + // "solc_via_ir": true, + // "solc_optimize": "10000", + "rule_sanity": "basic", + // fails currently "assert_autofinder_success" : true, + "msg": "Borrowing benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/conf/EVault/modules/Governance.conf b/certora/conf/EVault/modules/Governance.conf new file mode 100644 index 00000000..bc58cadd --- /dev/null +++ b/certora/conf/EVault/modules/Governance.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/EVault/modules/Initialize.sol" + ], + "verify": "Initialize:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + // "solc_via_ir": true, + // "solc_optimize": "10000", + "rule_sanity": "basic", + // fails currently "assert_autofinder_success" : true, + "msg": "Initialize benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/conf/EVault/modules/Initialize.conf b/certora/conf/EVault/modules/Initialize.conf new file mode 100644 index 00000000..bc58cadd --- /dev/null +++ b/certora/conf/EVault/modules/Initialize.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/EVault/modules/Initialize.sol" + ], + "verify": "Initialize:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + // "solc_via_ir": true, + // "solc_optimize": "10000", + "rule_sanity": "basic", + // fails currently "assert_autofinder_success" : true, + "msg": "Initialize benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf new file mode 100644 index 00000000..13d342a1 --- /dev/null +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/EVault/modules/Liquidation.sol" + ], + "verify": "Liquidation:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + // "solc_via_ir": true, + // "solc_optimize": "10000", + "rule_sanity": "basic", + // fails currently "assert_autofinder_success" : true, + "msg": "Liquidation benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/conf/EVault/modules/ModuleDispatch.conf b/certora/conf/EVault/modules/ModuleDispatch.conf new file mode 100644 index 00000000..6ef766be --- /dev/null +++ b/certora/conf/EVault/modules/ModuleDispatch.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "certora/harness/ModuleDispatchHarness.sol" + ], + "verify": "ModuleDispatchHarness:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + // "solc_via_ir": true, + // "solc_optimize": "10000", + "rule_sanity": "basic", + // fails currently "assert_autofinder_success" : true, + "msg": "Module Dispatch benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/conf/EVault/modules/RiskManager.conf b/certora/conf/EVault/modules/RiskManager.conf new file mode 100644 index 00000000..a8f6fb3d --- /dev/null +++ b/certora/conf/EVault/modules/RiskManager.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/EVault/modules/RiskManager.sol" + ], + "verify": "RiskManager:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + // "solc_via_ir": true, + // "solc_optimize": "10000", + "rule_sanity": "basic", + // fails currently "assert_autofinder_success" : true, + "msg": "RiskManager benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/conf/EVault/modules/Token.conf b/certora/conf/EVault/modules/Token.conf new file mode 100644 index 00000000..f73e5155 --- /dev/null +++ b/certora/conf/EVault/modules/Token.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/EVault/modules/Token.sol" + ], + "verify": "Token:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + // "solc_via_ir": true, + // "solc_optimize": "10000", + "rule_sanity": "basic", + // fails currently "assert_autofinder_success" : true, + "msg": "Token benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/conf/EVault/modules/Vault.conf b/certora/conf/EVault/modules/Vault.conf new file mode 100644 index 00000000..64ea3804 --- /dev/null +++ b/certora/conf/EVault/modules/Vault.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/EVault/modules/Vault.sol" + ], + "verify": "Vault:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + // "solc_via_ir": true, + // "solc_optimize": "10000", + "rule_sanity": "basic", + // fails currently "assert_autofinder_success" : true, + "msg": "Vault benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/conf/interestRateModels/BaseIRMLinearKink.conf b/certora/conf/interestRateModels/BaseIRMLinearKink.conf new file mode 100644 index 00000000..4c69536d --- /dev/null +++ b/certora/conf/interestRateModels/BaseIRMLinearKink.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/EVault/EVault.sol" + ], + "verify": "EVault:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + // fails currently "assert_autofinder_success" : true, + // "solc_via_ir": true, + // "solc_optimize": "10000", + "msg": "EVault benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/harness/ModuleDispatchHarness.sol b/certora/harness/ModuleDispatchHarness.sol new file mode 100644 index 00000000..f1000bf1 --- /dev/null +++ b/certora/harness/ModuleDispatchHarness.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + + +pragma solidity ^0.8.0; + +import "../../src/EVault/modules/ModuleDispatch.sol"; + +contract ModuleDispatchHarness is ModuleDispatch { + constructor(Integrations memory integrations) Base(integrations) {} +} diff --git a/certora/specs/benchmarking/Benchmarking.spec b/certora/specs/benchmarking/Benchmarking.spec new file mode 100644 index 00000000..9e3c2671 --- /dev/null +++ b/certora/specs/benchmarking/Benchmarking.spec @@ -0,0 +1,67 @@ +use builtin rule sanity; +use builtin rule hasDelegateCalls; +use builtin rule msgValueInLoopRule; +use builtin rule viewReentrancy; + + +rule noRevert(method f) { + env e; + calldataarg arg; + require e.msg.value == 0; + f@withrevert(e, arg); + assert !lastReverted; +} + + +rule alwaysRevert(method f) { + env e; + calldataarg arg; + f@withrevert(e, arg); + assert lastReverted; +} + +/* +This rule find which functions that can be called, may fail due to someone else calling a function right before. + +This is n expensive rule - might fail on the demo site on big contracts +*/ +rule simpleFrontRunning(method f, address privileged) filtered { f-> !f.isView } { + env e1; + calldataarg arg; + require e1.msg.sender == privileged; + storage initialStorage = lastStorage; + f(e1, arg); + bool firstSucceeded = !lastReverted; + env e2; + calldataarg arg2; + require e2.msg.sender != e1.msg.sender; + f(e2, arg2) at initialStorage; + f@withrevert(e1, arg); + bool succeeded = !lastReverted; + assert succeeded; +} + + +/* +This rule find which functions are privileged. +A function is privileged if there is only one address that can call it. + +The rules finds this by finding which functions can be called by two different users. +*/ +rule privilegedOperation(method f, address privileged) { + env e1; + calldataarg arg; + require e1.msg.sender == privileged; + + storage initialStorage = lastStorage; + f@withrevert(e1, arg); // privileged succeeds executing candidate privileged operation. + bool firstSucceeded = !lastReverted; + + env e2; + calldataarg arg2; + require e2.msg.sender != privileged; + f@withrevert(e2, arg2) at initialStorage; // unprivileged + bool secondSucceeded = !lastReverted; + + assert !(firstSucceeded && secondSucceeded); +} diff --git a/certora/specs/benchmarking/EVault/DToken.spec b/certora/specs/benchmarking/EVault/DToken.spec new file mode 100644 index 00000000..9aee557a --- /dev/null +++ b/certora/specs/benchmarking/EVault/DToken.spec @@ -0,0 +1 @@ +use builtin rule sanity; \ No newline at end of file diff --git a/certora/specs/benchmarking/EVault/EVault.spec b/certora/specs/benchmarking/EVault/EVault.spec new file mode 100644 index 00000000..9859f4c3 --- /dev/null +++ b/certora/specs/benchmarking/EVault/EVault.spec @@ -0,0 +1 @@ +use builtin rule sanity; diff --git a/certora/specs/benchmarking/EVault/modules/BalanceForwarder.spec b/certora/specs/benchmarking/EVault/modules/BalanceForwarder.spec new file mode 100644 index 00000000..9aee557a --- /dev/null +++ b/certora/specs/benchmarking/EVault/modules/BalanceForwarder.spec @@ -0,0 +1 @@ +use builtin rule sanity; \ No newline at end of file diff --git a/certora/specs/benchmarking/EVault_benchmarking.spec b/certora/specs/benchmarking/EVault_benchmarking.spec deleted file mode 100644 index e69de29b..00000000 From 6661360c7d25a830ef395a46edb251220287aec6 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 11 Mar 2024 12:52:05 +0000 Subject: [PATCH 003/152] initial IRM confs done --- .../interestRateModels/BaseIRMLinearKink.conf | 8 +++--- .../conf/interestRateModels/IRMClassLido.conf | 27 +++++++++++++++++++ .../interestRateModels/IRMClassMajor.conf | 27 +++++++++++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 certora/conf/interestRateModels/IRMClassLido.conf create mode 100644 certora/conf/interestRateModels/IRMClassMajor.conf diff --git a/certora/conf/interestRateModels/BaseIRMLinearKink.conf b/certora/conf/interestRateModels/BaseIRMLinearKink.conf index 4c69536d..1d8e7b92 100644 --- a/certora/conf/interestRateModels/BaseIRMLinearKink.conf +++ b/certora/conf/interestRateModels/BaseIRMLinearKink.conf @@ -1,14 +1,14 @@ { "files": [ - "src/EVault/EVault.sol" + "src/interestRateModels/BaseIRMLinearKink.sol" ], - "verify": "EVault:certora/specs/benchmarking/Benchmarking.spec", + "verify": "BaseIRMLinearKink:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", "rule_sanity": "basic", - // fails currently "assert_autofinder_success" : true, + "assert_autofinder_success" : true, // "solc_via_ir": true, // "solc_optimize": "10000", - "msg": "EVault benchmarking", + "msg": "BaseIRMLinearKink benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" diff --git a/certora/conf/interestRateModels/IRMClassLido.conf b/certora/conf/interestRateModels/IRMClassLido.conf new file mode 100644 index 00000000..142ed33a --- /dev/null +++ b/certora/conf/interestRateModels/IRMClassLido.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/interestRateModels/IRMClassLido.sol" + ], + "verify": "IRMClassLido:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + // fails currently "assert_autofinder_success" : true, + // "solc_via_ir": true, + // "solc_optimize": "10000", + "msg": "IRMClassLido benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/conf/interestRateModels/IRMClassMajor.conf b/certora/conf/interestRateModels/IRMClassMajor.conf new file mode 100644 index 00000000..b7a2c7d4 --- /dev/null +++ b/certora/conf/interestRateModels/IRMClassMajor.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/interestRateModels/IRMClassMajor.sol" + ], + "verify": "IRMClassMajor:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + // fails currently "assert_autofinder_success" : true, + // "solc_via_ir": true, + // "solc_optimize": "10000", + "msg": "IRMClassMajor benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file From db89a1e4266b8cf69c0024e02ef899b391539cf8 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 11 Mar 2024 12:57:11 +0000 Subject: [PATCH 004/152] Fix autofinder --- certora/conf/interestRateModels/IRMClassLido.conf | 2 +- certora/conf/interestRateModels/IRMClassMajor.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/certora/conf/interestRateModels/IRMClassLido.conf b/certora/conf/interestRateModels/IRMClassLido.conf index 142ed33a..a02d60bc 100644 --- a/certora/conf/interestRateModels/IRMClassLido.conf +++ b/certora/conf/interestRateModels/IRMClassLido.conf @@ -5,7 +5,7 @@ "verify": "IRMClassLido:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", "rule_sanity": "basic", - // fails currently "assert_autofinder_success" : true, + "assert_autofinder_success" : true, // "solc_via_ir": true, // "solc_optimize": "10000", "msg": "IRMClassLido benchmarking", diff --git a/certora/conf/interestRateModels/IRMClassMajor.conf b/certora/conf/interestRateModels/IRMClassMajor.conf index b7a2c7d4..deaf79f3 100644 --- a/certora/conf/interestRateModels/IRMClassMajor.conf +++ b/certora/conf/interestRateModels/IRMClassMajor.conf @@ -5,7 +5,7 @@ "verify": "IRMClassMajor:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", "rule_sanity": "basic", - // fails currently "assert_autofinder_success" : true, + "assert_autofinder_success" : true, // "solc_via_ir": true, // "solc_optimize": "10000", "msg": "IRMClassMajor benchmarking", From 5268490dc55210cc2880e3f7529f62a55dabb65a Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 11 Mar 2024 16:34:10 +0000 Subject: [PATCH 005/152] More prover setup --- certora/conf/EVault/DToken.conf | 2 +- certora/conf/EVault/EVault.conf | 2 +- certora/conf/EVault/modules/BalanceForwarder.conf | 2 +- certora/conf/EVault/modules/Borrowing.conf | 2 +- certora/conf/EVault/modules/Governance.conf | 8 ++++---- certora/conf/EVault/modules/Initialize.conf | 2 +- certora/conf/EVault/modules/Liquidation.conf | 2 +- certora/conf/EVault/modules/ModuleDispatch.conf | 2 +- certora/conf/EVault/modules/RiskManager.conf | 2 +- certora/conf/EVault/modules/Token.conf | 2 +- certora/conf/EVault/modules/Vault.conf | 2 +- certora/conf/interestRateModels/BaseIRMLinearKink.conf | 2 +- certora/conf/interestRateModels/IRMClassLido.conf | 2 +- certora/conf/interestRateModels/IRMClassMajor.conf | 2 +- 14 files changed, 17 insertions(+), 17 deletions(-) diff --git a/certora/conf/EVault/DToken.conf b/certora/conf/EVault/DToken.conf index fe908e67..f728bbdc 100644 --- a/certora/conf/EVault/DToken.conf +++ b/certora/conf/EVault/DToken.conf @@ -18,7 +18,7 @@ "-verifyTACDumps", "-testMode", "-checkRuleDigest", - "-callTraceHardFail" + "-callTraceHardFail on" ], "java_args": [ "-ea", diff --git a/certora/conf/EVault/EVault.conf b/certora/conf/EVault/EVault.conf index 4c69536d..4ab28732 100644 --- a/certora/conf/EVault/EVault.conf +++ b/certora/conf/EVault/EVault.conf @@ -18,7 +18,7 @@ "-verifyTACDumps", "-testMode", "-checkRuleDigest", - "-callTraceHardFail" + "-callTraceHardFail on" ], "java_args": [ "-ea", diff --git a/certora/conf/EVault/modules/BalanceForwarder.conf b/certora/conf/EVault/modules/BalanceForwarder.conf index 73a197db..586d2418 100644 --- a/certora/conf/EVault/modules/BalanceForwarder.conf +++ b/certora/conf/EVault/modules/BalanceForwarder.conf @@ -18,7 +18,7 @@ "-verifyTACDumps", "-testMode", "-checkRuleDigest", - "-callTraceHardFail" + "-callTraceHardFail on" ], "java_args": [ "-ea", diff --git a/certora/conf/EVault/modules/Borrowing.conf b/certora/conf/EVault/modules/Borrowing.conf index 45152291..805c59c9 100644 --- a/certora/conf/EVault/modules/Borrowing.conf +++ b/certora/conf/EVault/modules/Borrowing.conf @@ -18,7 +18,7 @@ "-verifyTACDumps", "-testMode", "-checkRuleDigest", - "-callTraceHardFail" + "-callTraceHardFail on" ], "java_args": [ "-ea", diff --git a/certora/conf/EVault/modules/Governance.conf b/certora/conf/EVault/modules/Governance.conf index bc58cadd..492144c1 100644 --- a/certora/conf/EVault/modules/Governance.conf +++ b/certora/conf/EVault/modules/Governance.conf @@ -1,14 +1,14 @@ { "files": [ - "src/EVault/modules/Initialize.sol" + "src/EVault/modules/Governance.sol" ], - "verify": "Initialize:certora/specs/benchmarking/Benchmarking.spec", + "verify": "Governance:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", // "solc_via_ir": true, // "solc_optimize": "10000", "rule_sanity": "basic", // fails currently "assert_autofinder_success" : true, - "msg": "Initialize benchmarking", + "msg": "Governance benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" @@ -18,7 +18,7 @@ "-verifyTACDumps", "-testMode", "-checkRuleDigest", - "-callTraceHardFail" + "-callTraceHardFail on" ], "java_args": [ "-ea", diff --git a/certora/conf/EVault/modules/Initialize.conf b/certora/conf/EVault/modules/Initialize.conf index bc58cadd..a32cf47f 100644 --- a/certora/conf/EVault/modules/Initialize.conf +++ b/certora/conf/EVault/modules/Initialize.conf @@ -18,7 +18,7 @@ "-verifyTACDumps", "-testMode", "-checkRuleDigest", - "-callTraceHardFail" + "-callTraceHardFail on" ], "java_args": [ "-ea", diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index 13d342a1..2f2de464 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -18,7 +18,7 @@ "-verifyTACDumps", "-testMode", "-checkRuleDigest", - "-callTraceHardFail" + "-callTraceHardFail on" ], "java_args": [ "-ea", diff --git a/certora/conf/EVault/modules/ModuleDispatch.conf b/certora/conf/EVault/modules/ModuleDispatch.conf index 6ef766be..8b0cb079 100644 --- a/certora/conf/EVault/modules/ModuleDispatch.conf +++ b/certora/conf/EVault/modules/ModuleDispatch.conf @@ -18,7 +18,7 @@ "-verifyTACDumps", "-testMode", "-checkRuleDigest", - "-callTraceHardFail" + "-callTraceHardFail on" ], "java_args": [ "-ea", diff --git a/certora/conf/EVault/modules/RiskManager.conf b/certora/conf/EVault/modules/RiskManager.conf index a8f6fb3d..77056790 100644 --- a/certora/conf/EVault/modules/RiskManager.conf +++ b/certora/conf/EVault/modules/RiskManager.conf @@ -18,7 +18,7 @@ "-verifyTACDumps", "-testMode", "-checkRuleDigest", - "-callTraceHardFail" + "-callTraceHardFail on" ], "java_args": [ "-ea", diff --git a/certora/conf/EVault/modules/Token.conf b/certora/conf/EVault/modules/Token.conf index f73e5155..d9e1224b 100644 --- a/certora/conf/EVault/modules/Token.conf +++ b/certora/conf/EVault/modules/Token.conf @@ -18,7 +18,7 @@ "-verifyTACDumps", "-testMode", "-checkRuleDigest", - "-callTraceHardFail" + "-callTraceHardFail on" ], "java_args": [ "-ea", diff --git a/certora/conf/EVault/modules/Vault.conf b/certora/conf/EVault/modules/Vault.conf index 64ea3804..469fd56c 100644 --- a/certora/conf/EVault/modules/Vault.conf +++ b/certora/conf/EVault/modules/Vault.conf @@ -18,7 +18,7 @@ "-verifyTACDumps", "-testMode", "-checkRuleDigest", - "-callTraceHardFail" + "-callTraceHardFail on" ], "java_args": [ "-ea", diff --git a/certora/conf/interestRateModels/BaseIRMLinearKink.conf b/certora/conf/interestRateModels/BaseIRMLinearKink.conf index 1d8e7b92..ea3a3f42 100644 --- a/certora/conf/interestRateModels/BaseIRMLinearKink.conf +++ b/certora/conf/interestRateModels/BaseIRMLinearKink.conf @@ -18,7 +18,7 @@ "-verifyTACDumps", "-testMode", "-checkRuleDigest", - "-callTraceHardFail" + "-callTraceHardFail on" ], "java_args": [ "-ea", diff --git a/certora/conf/interestRateModels/IRMClassLido.conf b/certora/conf/interestRateModels/IRMClassLido.conf index a02d60bc..d13377d3 100644 --- a/certora/conf/interestRateModels/IRMClassLido.conf +++ b/certora/conf/interestRateModels/IRMClassLido.conf @@ -18,7 +18,7 @@ "-verifyTACDumps", "-testMode", "-checkRuleDigest", - "-callTraceHardFail" + "-callTraceHardFail on" ], "java_args": [ "-ea", diff --git a/certora/conf/interestRateModels/IRMClassMajor.conf b/certora/conf/interestRateModels/IRMClassMajor.conf index deaf79f3..c43c1dc6 100644 --- a/certora/conf/interestRateModels/IRMClassMajor.conf +++ b/certora/conf/interestRateModels/IRMClassMajor.conf @@ -18,7 +18,7 @@ "-verifyTACDumps", "-testMode", "-checkRuleDigest", - "-callTraceHardFail" + "-callTraceHardFail on" ], "java_args": [ "-ea", From fd23976a99963a859a76c5d42cc1823500c80f88 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 11 Mar 2024 16:45:59 +0000 Subject: [PATCH 006/152] Mainly ERC20 Linking for Token --- certora/conf/EVault/modules/Token.conf | 2 +- certora/specs/benchmarking/EVault/DToken.spec | 1 - certora/specs/benchmarking/EVault/EVault.spec | 1 - certora/specs/benchmarking/EVault/modules/BalanceForwarder.spec | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 certora/specs/benchmarking/EVault/DToken.spec delete mode 100644 certora/specs/benchmarking/EVault/EVault.spec delete mode 100644 certora/specs/benchmarking/EVault/modules/BalanceForwarder.spec diff --git a/certora/conf/EVault/modules/Token.conf b/certora/conf/EVault/modules/Token.conf index d9e1224b..77498939 100644 --- a/certora/conf/EVault/modules/Token.conf +++ b/certora/conf/EVault/modules/Token.conf @@ -2,7 +2,7 @@ "files": [ "src/EVault/modules/Token.sol" ], - "verify": "Token:certora/specs/benchmarking/Benchmarking.spec", + "verify": "Token:certora/specs/benchmarking/EVault/modules/Token.spec", "solc": "solc8.23", // "solc_via_ir": true, // "solc_optimize": "10000", diff --git a/certora/specs/benchmarking/EVault/DToken.spec b/certora/specs/benchmarking/EVault/DToken.spec deleted file mode 100644 index 9aee557a..00000000 --- a/certora/specs/benchmarking/EVault/DToken.spec +++ /dev/null @@ -1 +0,0 @@ -use builtin rule sanity; \ No newline at end of file diff --git a/certora/specs/benchmarking/EVault/EVault.spec b/certora/specs/benchmarking/EVault/EVault.spec deleted file mode 100644 index 9859f4c3..00000000 --- a/certora/specs/benchmarking/EVault/EVault.spec +++ /dev/null @@ -1 +0,0 @@ -use builtin rule sanity; diff --git a/certora/specs/benchmarking/EVault/modules/BalanceForwarder.spec b/certora/specs/benchmarking/EVault/modules/BalanceForwarder.spec deleted file mode 100644 index 9aee557a..00000000 --- a/certora/specs/benchmarking/EVault/modules/BalanceForwarder.spec +++ /dev/null @@ -1 +0,0 @@ -use builtin rule sanity; \ No newline at end of file From dc8407d1f01fad6fa3dfd8cbb5d7f32a81521cd1 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 12 Mar 2024 15:27:04 +0000 Subject: [PATCH 007/152] Various configuration fixes --- certora/conf/EVault/DToken.conf | 8 +++++++- certora/conf/EVault/modules/BalanceForwarder.conf | 4 ++++ certora/conf/EVault/modules/Liquidation.conf | 2 ++ certora/conf/EVault/modules/Token.conf | 9 +++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/certora/conf/EVault/DToken.conf b/certora/conf/EVault/DToken.conf index f728bbdc..13d9ce46 100644 --- a/certora/conf/EVault/DToken.conf +++ b/certora/conf/EVault/DToken.conf @@ -1,18 +1,24 @@ { "files": [ + "src/Evault/EVault.sol", "src/EVault/DToken.sol" ], + "link": [ + "DToken:eVault=EVault", + "DToken:evc=EthereumVaultConnector" + ], "verify": "DToken:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", // "solc_via_ir": true, // "solc_optimize": "10000", "rule_sanity": "basic", - "assert_autofinder_success" : true, + // "assert_autofinder_success" : true, "msg": "DToken benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "parametric_contracts": ["DToken"], "prover_args": [ "-verifyCache", "-verifyTACDumps", diff --git a/certora/conf/EVault/modules/BalanceForwarder.conf b/certora/conf/EVault/modules/BalanceForwarder.conf index 586d2418..5267fb73 100644 --- a/certora/conf/EVault/modules/BalanceForwarder.conf +++ b/certora/conf/EVault/modules/BalanceForwarder.conf @@ -1,7 +1,11 @@ { "files": [ + "src/EVault/shared/types/MarketStorage.sol", "src/EVault/modules/BalanceForwarder.sol" ], + "link": [ + "BalanceForwarder:marketStorage=MarketStorage" + ], "verify": "BalanceForwarder:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", // "solc_via_ir": true, diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index 2f2de464..45540afe 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -13,6 +13,8 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "optimistic_loop": true, + "loop_iter": "2", "prover_args": [ "-verifyCache", "-verifyTACDumps", diff --git a/certora/conf/EVault/modules/Token.conf b/certora/conf/EVault/modules/Token.conf index 77498939..d936c420 100644 --- a/certora/conf/EVault/modules/Token.conf +++ b/certora/conf/EVault/modules/Token.conf @@ -1,7 +1,16 @@ { "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", "src/EVault/modules/Token.sol" ], + "link" : [ + // Note: this leads to some havocs of all contracts aside from EVC + "Token:evc=EthereumVaultConnector", + // Including this one causes a crash for now + "EthereumVaultConnector:executionContext=ExecutionContext" + ], + "parametric_contracts": ["Token"], "verify": "Token:certora/specs/benchmarking/EVault/modules/Token.spec", "solc": "solc8.23", // "solc_via_ir": true, From b0d6b70fc6226bb86bdfc9b53fdbddf162ba0df4 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 13 Mar 2024 13:47:51 +0000 Subject: [PATCH 008/152] Attempt DToken summarization. Does not seem to matter --- certora/conf/EVault/DToken.conf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/certora/conf/EVault/DToken.conf b/certora/conf/EVault/DToken.conf index 13d9ce46..417fb3d1 100644 --- a/certora/conf/EVault/DToken.conf +++ b/certora/conf/EVault/DToken.conf @@ -5,9 +5,8 @@ ], "link": [ "DToken:eVault=EVault", - "DToken:evc=EthereumVaultConnector" ], - "verify": "DToken:certora/specs/benchmarking/Benchmarking.spec", + "verify": "DToken:certora/specs/benchmarking/EVault/modules/DToken.spec", "solc": "solc8.23", // "solc_via_ir": true, // "solc_optimize": "10000", From 116e8a1fc6e84e38cf30de9a470a751067910cea Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 14 Mar 2024 10:38:24 +0000 Subject: [PATCH 009/152] Remove marketStorage linking in BalanceForwarder --- certora/conf/EVault/modules/BalanceForwarder.conf | 4 ---- 1 file changed, 4 deletions(-) diff --git a/certora/conf/EVault/modules/BalanceForwarder.conf b/certora/conf/EVault/modules/BalanceForwarder.conf index 5267fb73..586d2418 100644 --- a/certora/conf/EVault/modules/BalanceForwarder.conf +++ b/certora/conf/EVault/modules/BalanceForwarder.conf @@ -1,11 +1,7 @@ { "files": [ - "src/EVault/shared/types/MarketStorage.sol", "src/EVault/modules/BalanceForwarder.sol" ], - "link": [ - "BalanceForwarder:marketStorage=MarketStorage" - ], "verify": "BalanceForwarder:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", // "solc_via_ir": true, From f02c8cbd5a3a7027d22c00d674f21b2fb2aa00de Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 15 Mar 2024 09:51:50 +0000 Subject: [PATCH 010/152] Mainly EVaultHarness --- certora/conf/EVault/EVault.conf | 42 ++++++---- .../conf/EVault/modules/BalanceForwarder.conf | 26 +++--- certora/conf/EVault/modules/Borrowing.conf | 26 +++--- certora/conf/GenericFactory/BeaconProxy.conf | 27 +++++++ .../conf/GenericFactory/GenericFactory.conf | 27 +++++++ .../GenericFactory/MetaProxyDeployer.conf | 27 +++++++ certora/conf/ProtocolConfig.conf | 27 +++++++ certora/harness/EVaultHarness.sol | 59 ++++++++++++++ certora/specs/benchmarking/EVault/EVault.spec | 72 +++++++++++++++++ .../benchmarking/EVault/modules/DToken.spec | 71 ++++++++++++++++ .../benchmarking/EVault/modules/Token.spec | 81 +++++++++++++++++++ 11 files changed, 447 insertions(+), 38 deletions(-) create mode 100644 certora/conf/GenericFactory/BeaconProxy.conf create mode 100644 certora/conf/GenericFactory/GenericFactory.conf create mode 100644 certora/conf/GenericFactory/MetaProxyDeployer.conf create mode 100644 certora/conf/ProtocolConfig.conf create mode 100644 certora/harness/EVaultHarness.sol create mode 100644 certora/specs/benchmarking/EVault/EVault.spec create mode 100644 certora/specs/benchmarking/EVault/modules/DToken.spec create mode 100644 certora/specs/benchmarking/EVault/modules/Token.spec diff --git a/certora/conf/EVault/EVault.conf b/certora/conf/EVault/EVault.conf index 4ab28732..6665897d 100644 --- a/certora/conf/EVault/EVault.conf +++ b/certora/conf/EVault/EVault.conf @@ -1,27 +1,37 @@ { "files": [ - "src/EVault/EVault.sol" + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "certora/harness/EVaultHarness.sol", ], - "verify": "EVault:certora/specs/benchmarking/Benchmarking.spec", + "link" : [ + "EVaultHarness:evc=EthereumVaultConnector", + ], + "parametric_contracts": ["EVaultHarness"], + "verify": "EVaultHarness:certora/specs/benchmarking/EVault/EVault.spec", "solc": "solc8.23", "rule_sanity": "basic", - // fails currently "assert_autofinder_success" : true, - // "solc_via_ir": true, - // "solc_optimize": "10000", + // "assert_autofinder_success" : true, + "solc_via_ir": true, + "solc_optimize": "10000", "msg": "EVault benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] + // "function_finder_mode": "relaxed", + "optimistic_loop": true, + "optimistic_hashing": true, + "loop_iter": "2", + // "prover_args": [ + // "-verifyCache", + // "-verifyTACDumps", + // "-testMode", + // "-checkRuleDigest", + // "-callTraceHardFail on" + // ], + // "java_args": [ + // "-ea", + // "-Dlevel.setup.helpers=info" + // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/BalanceForwarder.conf b/certora/conf/EVault/modules/BalanceForwarder.conf index 586d2418..6786e268 100644 --- a/certora/conf/EVault/modules/BalanceForwarder.conf +++ b/certora/conf/EVault/modules/BalanceForwarder.conf @@ -1,7 +1,11 @@ { "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "src/EVault/modules/BalanceForwarder.sol" ], + "link": [ + "BalanceForwarder:evc=EthereumVaultConnector", + ], "verify": "BalanceForwarder:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", // "solc_via_ir": true, @@ -13,15 +17,15 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] + // "prover_args": [ + // "-verifyCache", + // "-verifyTACDumps", + // "-testMode", + // "-checkRuleDigest", + // "-callTraceHardFail on" + // ], + // "java_args": [ + // "-ea", + // "-Dlevel.setup.helpers=info" + // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Borrowing.conf b/certora/conf/EVault/modules/Borrowing.conf index 805c59c9..be5a04af 100644 --- a/certora/conf/EVault/modules/Borrowing.conf +++ b/certora/conf/EVault/modules/Borrowing.conf @@ -1,7 +1,11 @@ { "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "src/EVault/modules/Borrowing.sol" ], + "link": [ + "Borrowing:evc=EthereumVaultConnector", + ], "verify": "Borrowing:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", // "solc_via_ir": true, @@ -13,15 +17,15 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] + // "prover_args": [ + // "-verifyCache", + // "-verifyTACDumps", + // "-testMode", + // "-checkRuleDigest", + // "-callTraceHardFail on" + // ], + // "java_args": [ + // "-ea", + // "-Dlevel.setup.helpers=info" + // ] } \ No newline at end of file diff --git a/certora/conf/GenericFactory/BeaconProxy.conf b/certora/conf/GenericFactory/BeaconProxy.conf new file mode 100644 index 00000000..87123d87 --- /dev/null +++ b/certora/conf/GenericFactory/BeaconProxy.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/GenericFactory/BeaconProxy.sol" + ], + "verify": "BeaconProxy:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "assert_autofinder_success" : true, + // "solc_via_ir": true, + // "solc_optimize": "10000", + "msg": "BeaconProxy benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail on" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/conf/GenericFactory/GenericFactory.conf b/certora/conf/GenericFactory/GenericFactory.conf new file mode 100644 index 00000000..a106cd60 --- /dev/null +++ b/certora/conf/GenericFactory/GenericFactory.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/GenericFactory/GenericFactory.sol" + ], + "verify": "GenericFactory:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "assert_autofinder_success" : true, + // "solc_via_ir": true, + // "solc_optimize": "10000", + "msg": "GenericFactory benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail on" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/conf/GenericFactory/MetaProxyDeployer.conf b/certora/conf/GenericFactory/MetaProxyDeployer.conf new file mode 100644 index 00000000..709ca391 --- /dev/null +++ b/certora/conf/GenericFactory/MetaProxyDeployer.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/GenericFactory/MetaProxyDeployer.sol" + ], + "verify": "MetaProxyDeployer:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "assert_autofinder_success" : true, + // "solc_via_ir": true, + // "solc_optimize": "10000", + "msg": "MetaProxyDeployer benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail on" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/conf/ProtocolConfig.conf b/certora/conf/ProtocolConfig.conf new file mode 100644 index 00000000..4f75ec1b --- /dev/null +++ b/certora/conf/ProtocolConfig.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "src/ProtocolConfig/ProtocolConfig.sol" + ], + "verify": "ProtocolConfig:certora/specs/benchmarking/Benchmarking.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "assert_autofinder_success" : true, + // "solc_via_ir": true, + // "solc_optimize": "10000", + "msg": "ProtocolConfig benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-verifyCache", + "-verifyTACDumps", + "-testMode", + "-checkRuleDigest", + "-callTraceHardFail on" + ], + "java_args": [ + "-ea", + "-Dlevel.setup.helpers=info" + ] +} \ No newline at end of file diff --git a/certora/harness/EVaultHarness.sol b/certora/harness/EVaultHarness.sol new file mode 100644 index 00000000..0a7bca7d --- /dev/null +++ b/certora/harness/EVaultHarness.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import {Base} from "../../src/EVault/shared/Base.sol"; +// import {ModuleDispatch} from "../../src/EVault/modules/ModuleDispatch.sol"; + +import {TokenModule} from "../../src/EVault/modules/Token.sol"; +import {VaultModule} from "../../src/EVault/modules/Vault.sol"; +import {BorrowingModule} from "../../src/EVault/modules/Borrowing.sol"; +import {LiquidationModule} from "../../src/EVault/modules/Liquidation.sol"; +import {InitializeModule} from "../../src/EVault/modules/Initialize.sol"; +import {BalanceForwarderModule} from "../../src/EVault/modules/BalanceForwarder.sol"; +import {GovernanceModule} from "../../src/EVault/modules/Governance.sol"; +import {RiskManagerModule} from "../../src/EVault/modules/RiskManager.sol"; + +contract EVaultHarness is + // ModuleDispatch, + InitializeModule, + TokenModule, + VaultModule, + BorrowingModule, + LiquidationModule, + RiskManagerModule, + BalanceForwarderModule, + GovernanceModule +{ + // address immutable MODULE_INITIALIZE; + // address immutable MODULE_TOKEN; + // address immutable MODULE_VAULT; + // address immutable MODULE_BORROWING; + // address immutable MODULE_LIQUIDATION; + // address immutable MODULE_RISKMANAGER; + // address immutable MODULE_BALANCE_FORWARDER; + // address immutable MODULE_GOVERNANCE; + + constructor( + Integrations memory integrations, + address MODULE_INITIALIZE_, + address MODULE_TOKEN_, + address MODULE_VAULT_, + address MODULE_BORROWING_, + address MODULE_LIQUIDATION_, + address MODULE_RISKMANAGER_, + address MODULE_BALANCE_FORWARDER_, + address MODULE_GOVERNANCE_ + ) Base(integrations) { + // MODULE_INITIALIZE = MODULE_INITIALIZE_; + // MODULE_TOKEN = MODULE_TOKEN_; + // MODULE_VAULT = MODULE_VAULT_; + // MODULE_BORROWING = MODULE_BORROWING_; + // MODULE_LIQUIDATION = MODULE_LIQUIDATION_; + // MODULE_RISKMANAGER = MODULE_RISKMANAGER_; + // MODULE_BALANCE_FORWARDER = MODULE_BALANCE_FORWARDER_; + // MODULE_GOVERNANCE = MODULE_GOVERNANCE_; + } + + // Unlike EVault.sol, does not override methods with the useView pattern +} \ No newline at end of file diff --git a/certora/specs/benchmarking/EVault/EVault.spec b/certora/specs/benchmarking/EVault/EVault.spec new file mode 100644 index 00000000..722cd241 --- /dev/null +++ b/certora/specs/benchmarking/EVault/EVault.spec @@ -0,0 +1,72 @@ +use builtin rule sanity; +use builtin rule hasDelegateCalls; +use builtin rule msgValueInLoopRule; +use builtin rule viewReentrancy; + +methods { + function _.requireVaultStatusCheck() external => NONDET; + function _.requireAccountStatusCheck(address account) external => NONDET; + function _.requireAccountAndVaultStatusCheck(address account) external => NONDET; +} + +rule noRevert(method f) { + env e; + calldataarg arg; + require e.msg.value == 0; + f@withrevert(e, arg); + assert !lastReverted; +} + + +rule alwaysRevert(method f) { + env e; + calldataarg arg; + f@withrevert(e, arg); + assert lastReverted; +} + +/* +This rule find which functions that can be called, may fail due to someone else calling a function right before. + +This is n expensive rule - might fail on the demo site on big contracts +*/ +rule simpleFrontRunning(method f, address privileged) filtered { f-> !f.isView } { + env e1; + calldataarg arg; + require e1.msg.sender == privileged; + storage initialStorage = lastStorage; + f(e1, arg); + bool firstSucceeded = !lastReverted; + env e2; + calldataarg arg2; + require e2.msg.sender != e1.msg.sender; + f(e2, arg2) at initialStorage; + f@withrevert(e1, arg); + bool succeeded = !lastReverted; + assert succeeded; +} + + +/* +This rule find which functions are privileged. +A function is privileged if there is only one address that can call it. + +The rules finds this by finding which functions can be called by two different users. +*/ +rule privilegedOperation(method f, address privileged) { + env e1; + calldataarg arg; + require e1.msg.sender == privileged; + + storage initialStorage = lastStorage; + f@withrevert(e1, arg); // privileged succeeds executing candidate privileged operation. + bool firstSucceeded = !lastReverted; + + env e2; + calldataarg arg2; + require e2.msg.sender != privileged; + f@withrevert(e2, arg2) at initialStorage; // unprivileged + bool secondSucceeded = !lastReverted; + + assert !(firstSucceeded && secondSucceeded); +} diff --git a/certora/specs/benchmarking/EVault/modules/DToken.spec b/certora/specs/benchmarking/EVault/modules/DToken.spec new file mode 100644 index 00000000..c4c3c27a --- /dev/null +++ b/certora/specs/benchmarking/EVault/modules/DToken.spec @@ -0,0 +1,71 @@ +methods { +} + +// Below this line is same as Benchmarking.spec +use builtin rule sanity; +use builtin rule hasDelegateCalls; +use builtin rule msgValueInLoopRule; +use builtin rule viewReentrancy; + + +rule noRevert(method f) { + env e; + calldataarg arg; + require e.msg.value == 0; + f@withrevert(e, arg); + assert !lastReverted; +} + + +rule alwaysRevert(method f) { + env e; + calldataarg arg; + f@withrevert(e, arg); + assert lastReverted; +} + +/* +This rule find which functions that can be called, may fail due to someone else calling a function right before. + +This is n expensive rule - might fail on the demo site on big contracts +*/ +rule simpleFrontRunning(method f, address privileged) filtered { f-> !f.isView } { + env e1; + calldataarg arg; + require e1.msg.sender == privileged; + storage initialStorage = lastStorage; + f(e1, arg); + bool firstSucceeded = !lastReverted; + env e2; + calldataarg arg2; + require e2.msg.sender != e1.msg.sender; + f(e2, arg2) at initialStorage; + f@withrevert(e1, arg); + bool succeeded = !lastReverted; + assert succeeded; +} + + +/* +This rule find which functions are privileged. +A function is privileged if there is only one address that can call it. + +The rules finds this by finding which functions can be called by two different users. +*/ +rule privilegedOperation(method f, address privileged) { + env e1; + calldataarg arg; + require e1.msg.sender == privileged; + + storage initialStorage = lastStorage; + f@withrevert(e1, arg); // privileged succeeds executing candidate privileged operation. + bool firstSucceeded = !lastReverted; + + env e2; + calldataarg arg2; + require e2.msg.sender != privileged; + f@withrevert(e2, arg2) at initialStorage; // unprivileged + bool secondSucceeded = !lastReverted; + + assert !(firstSucceeded && secondSucceeded); +} diff --git a/certora/specs/benchmarking/EVault/modules/Token.spec b/certora/specs/benchmarking/EVault/modules/Token.spec new file mode 100644 index 00000000..965af76d --- /dev/null +++ b/certora/specs/benchmarking/EVault/modules/Token.spec @@ -0,0 +1,81 @@ +// erc20.spec +methods { + function _.name() external => DISPATCHER(true); + function _.symbol() external => DISPATCHER(true); + function _.decimals() external => DISPATCHER(true); + function _.totalSupply() external => DISPATCHER(true); + function _.balanceOf(address) external => DISPATCHER(true); + function _.allowance(address,address) external => DISPATCHER(true); + function _.approve(address,uint256) external => DISPATCHER(true); + function _.transfer(address,uint256) external => DISPATCHER(true); + function _.transferFrom(address,address,uint256) external => DISPATCHER(true); +} + +// Below this line is same as Benchmarking.spec +use builtin rule sanity; +use builtin rule hasDelegateCalls; +use builtin rule msgValueInLoopRule; +use builtin rule viewReentrancy; + + +rule noRevert(method f) { + env e; + calldataarg arg; + require e.msg.value == 0; + f@withrevert(e, arg); + assert !lastReverted; +} + + +rule alwaysRevert(method f) { + env e; + calldataarg arg; + f@withrevert(e, arg); + assert lastReverted; +} + +/* +This rule find which functions that can be called, may fail due to someone else calling a function right before. + +This is n expensive rule - might fail on the demo site on big contracts +*/ +rule simpleFrontRunning(method f, address privileged) filtered { f-> !f.isView } { + env e1; + calldataarg arg; + require e1.msg.sender == privileged; + storage initialStorage = lastStorage; + f(e1, arg); + bool firstSucceeded = !lastReverted; + env e2; + calldataarg arg2; + require e2.msg.sender != e1.msg.sender; + f(e2, arg2) at initialStorage; + f@withrevert(e1, arg); + bool succeeded = !lastReverted; + assert succeeded; +} + + +/* +This rule find which functions are privileged. +A function is privileged if there is only one address that can call it. + +The rules finds this by finding which functions can be called by two different users. +*/ +rule privilegedOperation(method f, address privileged) { + env e1; + calldataarg arg; + require e1.msg.sender == privileged; + + storage initialStorage = lastStorage; + f@withrevert(e1, arg); // privileged succeeds executing candidate privileged operation. + bool firstSucceeded = !lastReverted; + + env e2; + calldataarg arg2; + require e2.msg.sender != privileged; + f@withrevert(e2, arg2) at initialStorage; // unprivileged + bool secondSucceeded = !lastReverted; + + assert !(firstSucceeded && secondSucceeded); +} From 2e4a898279468255c256afc2b7a1009e5045cc38 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 18 Mar 2024 09:22:17 +0000 Subject: [PATCH 011/152] Linking for individual modules --- .../conf/EVault/modules/BalanceForwarder.conf | 5 +-- certora/conf/EVault/modules/Borrowing.conf | 5 +-- certora/conf/EVault/modules/Governance.conf | 31 +++++++++++-------- certora/conf/EVault/modules/Initialize.conf | 26 ++++++++-------- certora/conf/EVault/modules/Liquidation.conf | 27 +++++++++------- certora/conf/EVault/modules/RiskManager.conf | 31 +++++++++++-------- certora/conf/EVault/modules/Token.conf | 29 ++++++++--------- certora/conf/EVault/modules/Vault.conf | 31 +++++++++++-------- 8 files changed, 102 insertions(+), 83 deletions(-) diff --git a/certora/conf/EVault/modules/BalanceForwarder.conf b/certora/conf/EVault/modules/BalanceForwarder.conf index 6786e268..05e68c51 100644 --- a/certora/conf/EVault/modules/BalanceForwarder.conf +++ b/certora/conf/EVault/modules/BalanceForwarder.conf @@ -1,6 +1,7 @@ { "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", "src/EVault/modules/BalanceForwarder.sol" ], "link": [ @@ -8,8 +9,8 @@ ], "verify": "BalanceForwarder:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", - // "solc_via_ir": true, - // "solc_optimize": "10000", + "solc_via_ir": true, + "solc_optimize": "10000", "rule_sanity": "basic", // fails currently "assert_autofinder_success" : true, "msg": "BalanceForwarder benchmarking", diff --git a/certora/conf/EVault/modules/Borrowing.conf b/certora/conf/EVault/modules/Borrowing.conf index be5a04af..bf336200 100644 --- a/certora/conf/EVault/modules/Borrowing.conf +++ b/certora/conf/EVault/modules/Borrowing.conf @@ -1,6 +1,7 @@ { "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", "src/EVault/modules/Borrowing.sol" ], "link": [ @@ -8,8 +9,8 @@ ], "verify": "Borrowing:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", - // "solc_via_ir": true, - // "solc_optimize": "10000", + "solc_via_ir": true, + "solc_optimize": "10000", "rule_sanity": "basic", // fails currently "assert_autofinder_success" : true, "msg": "Borrowing benchmarking", diff --git a/certora/conf/EVault/modules/Governance.conf b/certora/conf/EVault/modules/Governance.conf index 492144c1..cac2ea85 100644 --- a/certora/conf/EVault/modules/Governance.conf +++ b/certora/conf/EVault/modules/Governance.conf @@ -1,11 +1,16 @@ { "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", "src/EVault/modules/Governance.sol" ], + "link": [ + "Governance:evc=EthereumVaultConnector", + ], "verify": "Governance:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", - // "solc_via_ir": true, - // "solc_optimize": "10000", + "solc_via_ir": true, + "solc_optimize": "10000", "rule_sanity": "basic", // fails currently "assert_autofinder_success" : true, "msg": "Governance benchmarking", @@ -13,15 +18,15 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] + // "prover_args": [ + // "-verifyCache", + // "-verifyTACDumps", + // "-testMode", + // "-checkRuleDigest", + // "-callTraceHardFail on" + // ], + // "java_args": [ + // "-ea", + // "-Dlevel.setup.helpers=info" + // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Initialize.conf b/certora/conf/EVault/modules/Initialize.conf index a32cf47f..5fcaad9b 100644 --- a/certora/conf/EVault/modules/Initialize.conf +++ b/certora/conf/EVault/modules/Initialize.conf @@ -4,8 +4,8 @@ ], "verify": "Initialize:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", - // "solc_via_ir": true, - // "solc_optimize": "10000", + "solc_via_ir": true, + "solc_optimize": "10000", "rule_sanity": "basic", // fails currently "assert_autofinder_success" : true, "msg": "Initialize benchmarking", @@ -13,15 +13,15 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] + // "prover_args": [ + // "-verifyCache", + // "-verifyTACDumps", + // "-testMode", + // "-checkRuleDigest", + // "-callTraceHardFail on" + // ], + // "java_args": [ + // "-ea", + // "-Dlevel.setup.helpers=info" + // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index 45540afe..8cabeaae 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -1,7 +1,12 @@ { "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", "src/EVault/modules/Liquidation.sol" ], + "link": [ + "Liquidation:evc=EthereumVaultConnector", + ], "verify": "Liquidation:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", // "solc_via_ir": true, @@ -15,15 +20,15 @@ ], "optimistic_loop": true, "loop_iter": "2", - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] + // "prover_args": [ + // "-verifyCache", + // "-verifyTACDumps", + // "-testMode", + // "-checkRuleDigest", + // "-callTraceHardFail on" + // ], + // "java_args": [ + // "-ea", + // "-Dlevel.setup.helpers=info" + // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/RiskManager.conf b/certora/conf/EVault/modules/RiskManager.conf index 77056790..e4a95125 100644 --- a/certora/conf/EVault/modules/RiskManager.conf +++ b/certora/conf/EVault/modules/RiskManager.conf @@ -1,11 +1,16 @@ { "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", "src/EVault/modules/RiskManager.sol" ], + "link": [ + "RiskManager:evc=EthereumVaultConnector", + ], "verify": "RiskManager:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", - // "solc_via_ir": true, - // "solc_optimize": "10000", + "solc_via_ir": true, + "solc_optimize": "10000", "rule_sanity": "basic", // fails currently "assert_autofinder_success" : true, "msg": "RiskManager benchmarking", @@ -13,15 +18,15 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] + // "prover_args": [ + // "-verifyCache", + // "-verifyTACDumps", + // "-testMode", + // "-checkRuleDigest", + // "-callTraceHardFail on" + // ], + // "java_args": [ + // "-ea", + // "-Dlevel.setup.helpers=info" + // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Token.conf b/certora/conf/EVault/modules/Token.conf index d936c420..89131f4f 100644 --- a/certora/conf/EVault/modules/Token.conf +++ b/certora/conf/EVault/modules/Token.conf @@ -5,16 +5,13 @@ "src/EVault/modules/Token.sol" ], "link" : [ - // Note: this leads to some havocs of all contracts aside from EVC "Token:evc=EthereumVaultConnector", - // Including this one causes a crash for now - "EthereumVaultConnector:executionContext=ExecutionContext" ], "parametric_contracts": ["Token"], "verify": "Token:certora/specs/benchmarking/EVault/modules/Token.spec", "solc": "solc8.23", - // "solc_via_ir": true, - // "solc_optimize": "10000", + "solc_via_ir": true, + "solc_optimize": "10000", "rule_sanity": "basic", // fails currently "assert_autofinder_success" : true, "msg": "Token benchmarking", @@ -22,15 +19,15 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] + // "prover_args": [ + // "-verifyCache", + // "-verifyTACDumps", + // "-testMode", + // "-checkRuleDigest", + // "-callTraceHardFail on" + // ], + // "java_args": [ + // "-ea", + // "-Dlevel.setup.helpers=info" + // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Vault.conf b/certora/conf/EVault/modules/Vault.conf index 469fd56c..37ae9159 100644 --- a/certora/conf/EVault/modules/Vault.conf +++ b/certora/conf/EVault/modules/Vault.conf @@ -1,11 +1,16 @@ { "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", "src/EVault/modules/Vault.sol" ], + "link" : [ + "Vault:evc=EthereumVaultConnector", + ], "verify": "Vault:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", - // "solc_via_ir": true, - // "solc_optimize": "10000", + "solc_via_ir": true, + "solc_optimize": "10000", "rule_sanity": "basic", // fails currently "assert_autofinder_success" : true, "msg": "Vault benchmarking", @@ -13,15 +18,15 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] + // "prover_args": [ + // "-verifyCache", + // "-verifyTACDumps", + // "-testMode", + // "-checkRuleDigest", + // "-callTraceHardFail on" + // ], + // "java_args": [ + // "-ea", + // "-Dlevel.setup.helpers=info" + // ] } \ No newline at end of file From 230fcd02d32f93dbb4b02b06ca5218e8cfdc3691 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 18 Mar 2024 09:25:18 +0000 Subject: [PATCH 012/152] parametric_contracts --- .../conf/EVault/modules/BalanceForwarder.conf | 1 + certora/conf/EVault/modules/Borrowing.conf | 1 + certora/conf/EVault/modules/Governance.conf | 1 + certora/conf/EVault/modules/Initialize.conf | 1 + certora/conf/EVault/modules/Liquidation.conf | 1 + .../conf/EVault/modules/ModuleDispatch.conf | 27 ++++++++++--------- certora/conf/EVault/modules/RiskManager.conf | 1 + certora/conf/EVault/modules/Vault.conf | 1 + 8 files changed, 21 insertions(+), 13 deletions(-) diff --git a/certora/conf/EVault/modules/BalanceForwarder.conf b/certora/conf/EVault/modules/BalanceForwarder.conf index 05e68c51..44e493ef 100644 --- a/certora/conf/EVault/modules/BalanceForwarder.conf +++ b/certora/conf/EVault/modules/BalanceForwarder.conf @@ -18,6 +18,7 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "parametric_contracts": ["BalanceForwarder"], // "prover_args": [ // "-verifyCache", // "-verifyTACDumps", diff --git a/certora/conf/EVault/modules/Borrowing.conf b/certora/conf/EVault/modules/Borrowing.conf index bf336200..dcfca3c4 100644 --- a/certora/conf/EVault/modules/Borrowing.conf +++ b/certora/conf/EVault/modules/Borrowing.conf @@ -18,6 +18,7 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "parametric_contracts": ["Borrowing"], // "prover_args": [ // "-verifyCache", // "-verifyTACDumps", diff --git a/certora/conf/EVault/modules/Governance.conf b/certora/conf/EVault/modules/Governance.conf index cac2ea85..1f48b882 100644 --- a/certora/conf/EVault/modules/Governance.conf +++ b/certora/conf/EVault/modules/Governance.conf @@ -18,6 +18,7 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "parametric_contracts": ["Governance"], // "prover_args": [ // "-verifyCache", // "-verifyTACDumps", diff --git a/certora/conf/EVault/modules/Initialize.conf b/certora/conf/EVault/modules/Initialize.conf index 5fcaad9b..e7015ef2 100644 --- a/certora/conf/EVault/modules/Initialize.conf +++ b/certora/conf/EVault/modules/Initialize.conf @@ -13,6 +13,7 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "parametric_contracts": ["Initialize"], // "prover_args": [ // "-verifyCache", // "-verifyTACDumps", diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index 8cabeaae..3c21da5e 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -18,6 +18,7 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "parametric_contracts": ["Liquidation"], "optimistic_loop": true, "loop_iter": "2", // "prover_args": [ diff --git a/certora/conf/EVault/modules/ModuleDispatch.conf b/certora/conf/EVault/modules/ModuleDispatch.conf index 8b0cb079..0016efdc 100644 --- a/certora/conf/EVault/modules/ModuleDispatch.conf +++ b/certora/conf/EVault/modules/ModuleDispatch.conf @@ -4,8 +4,8 @@ ], "verify": "ModuleDispatchHarness:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", - // "solc_via_ir": true, - // "solc_optimize": "10000", + "solc_via_ir": true, + "solc_optimize": "10000", "rule_sanity": "basic", // fails currently "assert_autofinder_success" : true, "msg": "Module Dispatch benchmarking", @@ -13,15 +13,16 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] + "parametric_contracts": ["ModuleDispatch"], + // "prover_args": [ + // "-verifyCache", + // "-verifyTACDumps", + // "-testMode", + // "-checkRuleDigest", + // "-callTraceHardFail on" + // ], + // "java_args": [ + // "-ea", + // "-Dlevel.setup.helpers=info" + // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/RiskManager.conf b/certora/conf/EVault/modules/RiskManager.conf index e4a95125..5da8ba30 100644 --- a/certora/conf/EVault/modules/RiskManager.conf +++ b/certora/conf/EVault/modules/RiskManager.conf @@ -18,6 +18,7 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "parametric_contracts": ["RiskManager"], // "prover_args": [ // "-verifyCache", // "-verifyTACDumps", diff --git a/certora/conf/EVault/modules/Vault.conf b/certora/conf/EVault/modules/Vault.conf index 37ae9159..606bad7a 100644 --- a/certora/conf/EVault/modules/Vault.conf +++ b/certora/conf/EVault/modules/Vault.conf @@ -18,6 +18,7 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "parametric_contracts": ["Vault"], // "prover_args": [ // "-verifyCache", // "-verifyTACDumps", From af3f5a3b85dfa19097eb7d3305f656b2fac7ca47 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 19 Mar 2024 11:11:00 +0000 Subject: [PATCH 013/152] dtoken use evaultHarness --- certora/conf/EVault/DToken.conf | 30 +++++++++++++++--------------- certora/harness/EVaultHarness.sol | 30 ++---------------------------- 2 files changed, 17 insertions(+), 43 deletions(-) diff --git a/certora/conf/EVault/DToken.conf b/certora/conf/EVault/DToken.conf index 417fb3d1..2bd4b248 100644 --- a/certora/conf/EVault/DToken.conf +++ b/certora/conf/EVault/DToken.conf @@ -1,15 +1,15 @@ { "files": [ - "src/Evault/EVault.sol", + "certora/harness/EVaultHarness.sol", "src/EVault/DToken.sol" ], "link": [ - "DToken:eVault=EVault", + "DToken:eVault=EVaultHarness", ], "verify": "DToken:certora/specs/benchmarking/EVault/modules/DToken.spec", "solc": "solc8.23", - // "solc_via_ir": true, - // "solc_optimize": "10000", + "solc_via_ir": true, + "solc_optimize": "10000", "rule_sanity": "basic", // "assert_autofinder_success" : true, "msg": "DToken benchmarking", @@ -18,15 +18,15 @@ "forge-std=lib/forge-std/src" ], "parametric_contracts": ["DToken"], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] + // "prover_args": [ + // "-verifyCache", + // "-verifyTACDumps", + // "-testMode", + // "-checkRuleDigest", + // "-callTraceHardFail on" + // ], + // "java_args": [ + // "-ea", + // "-Dlevel.setup.helpers=info" + // ] } \ No newline at end of file diff --git a/certora/harness/EVaultHarness.sol b/certora/harness/EVaultHarness.sol index 0a7bca7d..fb6dbb6b 100644 --- a/certora/harness/EVaultHarness.sol +++ b/certora/harness/EVaultHarness.sol @@ -15,7 +15,6 @@ import {GovernanceModule} from "../../src/EVault/modules/Governance.sol"; import {RiskManagerModule} from "../../src/EVault/modules/RiskManager.sol"; contract EVaultHarness is - // ModuleDispatch, InitializeModule, TokenModule, VaultModule, @@ -25,35 +24,10 @@ contract EVaultHarness is BalanceForwarderModule, GovernanceModule { - // address immutable MODULE_INITIALIZE; - // address immutable MODULE_TOKEN; - // address immutable MODULE_VAULT; - // address immutable MODULE_BORROWING; - // address immutable MODULE_LIQUIDATION; - // address immutable MODULE_RISKMANAGER; - // address immutable MODULE_BALANCE_FORWARDER; - // address immutable MODULE_GOVERNANCE; constructor( - Integrations memory integrations, - address MODULE_INITIALIZE_, - address MODULE_TOKEN_, - address MODULE_VAULT_, - address MODULE_BORROWING_, - address MODULE_LIQUIDATION_, - address MODULE_RISKMANAGER_, - address MODULE_BALANCE_FORWARDER_, - address MODULE_GOVERNANCE_ - ) Base(integrations) { - // MODULE_INITIALIZE = MODULE_INITIALIZE_; - // MODULE_TOKEN = MODULE_TOKEN_; - // MODULE_VAULT = MODULE_VAULT_; - // MODULE_BORROWING = MODULE_BORROWING_; - // MODULE_LIQUIDATION = MODULE_LIQUIDATION_; - // MODULE_RISKMANAGER = MODULE_RISKMANAGER_; - // MODULE_BALANCE_FORWARDER = MODULE_BALANCE_FORWARDER_; - // MODULE_GOVERNANCE = MODULE_GOVERNANCE_; - } + Integrations memory integrations + ) Base(integrations) {} // Unlike EVault.sol, does not override methods with the useView pattern } \ No newline at end of file From 93829640d43b5acbfd03a0100aa552f429de2e62 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 19 Mar 2024 11:37:41 +0000 Subject: [PATCH 014/152] Fix protocolConfig linking --- certora/conf/EVault/modules/Governance.conf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/certora/conf/EVault/modules/Governance.conf b/certora/conf/EVault/modules/Governance.conf index 1f48b882..5b9ff9cb 100644 --- a/certora/conf/EVault/modules/Governance.conf +++ b/certora/conf/EVault/modules/Governance.conf @@ -2,10 +2,12 @@ "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "src/ProtocolConfig/ProtocolConfig.sol", "src/EVault/modules/Governance.sol" ], "link": [ - "Governance:evc=EthereumVaultConnector", + "Governance:protocolConfig=ProtocolConfig", + "Governance:evc=EthereumVaultConnector" ], "verify": "Governance:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", From d7c8987f7903e5a2d871ea91ac5457cce82a984b Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 21 Mar 2024 12:07:43 +0000 Subject: [PATCH 015/152] Setup phase done for now. --- certora/harness/EVaultHarness.sol | 1 - certora/specs/benchmarking/Benchmarking.spec | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/certora/harness/EVaultHarness.sol b/certora/harness/EVaultHarness.sol index fb6dbb6b..0a19b99c 100644 --- a/certora/harness/EVaultHarness.sol +++ b/certora/harness/EVaultHarness.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.0; import {Base} from "../../src/EVault/shared/Base.sol"; -// import {ModuleDispatch} from "../../src/EVault/modules/ModuleDispatch.sol"; import {TokenModule} from "../../src/EVault/modules/Token.sol"; import {VaultModule} from "../../src/EVault/modules/Vault.sol"; diff --git a/certora/specs/benchmarking/Benchmarking.spec b/certora/specs/benchmarking/Benchmarking.spec index 9e3c2671..dc0798e5 100644 --- a/certora/specs/benchmarking/Benchmarking.spec +++ b/certora/specs/benchmarking/Benchmarking.spec @@ -1,3 +1,18 @@ +methods { + // Havocs here should be OK, but want to remove the linking issues from the tool + function _.calculateDTokenAddress() internal => NONDET; + // IERC20 + function _.name() external => DISPATCHER(true); + function _.symbol() external => DISPATCHER(true); + function _.decimals() external => DISPATCHER(true); + function _.totalSupply() external => DISPATCHER(true); + function _.balanceOf(address) external => DISPATCHER(true); + function _.allowance(address,address) external => DISPATCHER(true); + function _.approve(address,uint256) external => DISPATCHER(true); + function _.transfer(address,uint256) external => DISPATCHER(true); + function _.transferFrom(address,address,uint256) external => DISPATCHER(true); +} + use builtin rule sanity; use builtin rule hasDelegateCalls; use builtin rule msgValueInLoopRule; From c256fa5238486a7f6d39b54aad6625a6aec147ea Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 22 Mar 2024 09:42:03 +0000 Subject: [PATCH 016/152] update base --- certora/harness/EVaultHarness.sol | 1 + lib/forge-std | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/certora/harness/EVaultHarness.sol b/certora/harness/EVaultHarness.sol index 0a19b99c..2d23d1ab 100644 --- a/certora/harness/EVaultHarness.sol +++ b/certora/harness/EVaultHarness.sol @@ -14,6 +14,7 @@ import {GovernanceModule} from "../../src/EVault/modules/Governance.sol"; import {RiskManagerModule} from "../../src/EVault/modules/RiskManager.sol"; contract EVaultHarness is + Base, InitializeModule, TokenModule, VaultModule, diff --git a/lib/forge-std b/lib/forge-std index b6a506db..1d9650e9 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit b6a506db2262cad5ff982a87789ee6d1558ec861 +Subproject commit 1d9650e951204a0ddce9ff89c32f1997984cef4d From 5dad274c08502cd188a41524c8d5de23c182e460 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 22 Mar 2024 09:45:02 +0000 Subject: [PATCH 017/152] Clean up commented options --- certora/conf/EVault/DToken.conf | 12 ------------ certora/conf/EVault/EVault.conf | 13 ------------- certora/conf/EVault/modules/BalanceForwarder.conf | 12 ------------ certora/conf/EVault/modules/Borrowing.conf | 14 +------------- certora/conf/EVault/modules/Governance.conf | 13 ------------- certora/conf/EVault/modules/Initialize.conf | 12 ------------ certora/conf/EVault/modules/Liquidation.conf | 12 ------------ certora/conf/EVault/modules/ModuleDispatch.conf | 12 ------------ certora/conf/EVault/modules/RiskManager.conf | 12 ------------ certora/conf/EVault/modules/Token.conf | 12 ------------ certora/conf/EVault/modules/Vault.conf | 12 ------------ 11 files changed, 1 insertion(+), 135 deletions(-) diff --git a/certora/conf/EVault/DToken.conf b/certora/conf/EVault/DToken.conf index 2bd4b248..1a1c614f 100644 --- a/certora/conf/EVault/DToken.conf +++ b/certora/conf/EVault/DToken.conf @@ -11,22 +11,10 @@ "solc_via_ir": true, "solc_optimize": "10000", "rule_sanity": "basic", - // "assert_autofinder_success" : true, "msg": "DToken benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], "parametric_contracts": ["DToken"], - // "prover_args": [ - // "-verifyCache", - // "-verifyTACDumps", - // "-testMode", - // "-checkRuleDigest", - // "-callTraceHardFail on" - // ], - // "java_args": [ - // "-ea", - // "-Dlevel.setup.helpers=info" - // ] } \ No newline at end of file diff --git a/certora/conf/EVault/EVault.conf b/certora/conf/EVault/EVault.conf index 6665897d..c28d2c6d 100644 --- a/certora/conf/EVault/EVault.conf +++ b/certora/conf/EVault/EVault.conf @@ -11,7 +11,6 @@ "verify": "EVaultHarness:certora/specs/benchmarking/EVault/EVault.spec", "solc": "solc8.23", "rule_sanity": "basic", - // "assert_autofinder_success" : true, "solc_via_ir": true, "solc_optimize": "10000", "msg": "EVault benchmarking", @@ -19,19 +18,7 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - // "function_finder_mode": "relaxed", "optimistic_loop": true, "optimistic_hashing": true, "loop_iter": "2", - // "prover_args": [ - // "-verifyCache", - // "-verifyTACDumps", - // "-testMode", - // "-checkRuleDigest", - // "-callTraceHardFail on" - // ], - // "java_args": [ - // "-ea", - // "-Dlevel.setup.helpers=info" - // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/BalanceForwarder.conf b/certora/conf/EVault/modules/BalanceForwarder.conf index 44e493ef..757d2fe8 100644 --- a/certora/conf/EVault/modules/BalanceForwarder.conf +++ b/certora/conf/EVault/modules/BalanceForwarder.conf @@ -12,22 +12,10 @@ "solc_via_ir": true, "solc_optimize": "10000", "rule_sanity": "basic", - // fails currently "assert_autofinder_success" : true, "msg": "BalanceForwarder benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], "parametric_contracts": ["BalanceForwarder"], - // "prover_args": [ - // "-verifyCache", - // "-verifyTACDumps", - // "-testMode", - // "-checkRuleDigest", - // "-callTraceHardFail on" - // ], - // "java_args": [ - // "-ea", - // "-Dlevel.setup.helpers=info" - // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Borrowing.conf b/certora/conf/EVault/modules/Borrowing.conf index dcfca3c4..43b9cf11 100644 --- a/certora/conf/EVault/modules/Borrowing.conf +++ b/certora/conf/EVault/modules/Borrowing.conf @@ -12,22 +12,10 @@ "solc_via_ir": true, "solc_optimize": "10000", "rule_sanity": "basic", - // fails currently "assert_autofinder_success" : true, "msg": "Borrowing benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "parametric_contracts": ["Borrowing"], - // "prover_args": [ - // "-verifyCache", - // "-verifyTACDumps", - // "-testMode", - // "-checkRuleDigest", - // "-callTraceHardFail on" - // ], - // "java_args": [ - // "-ea", - // "-Dlevel.setup.helpers=info" - // ] + "parametric_contracts": ["Borrowing"] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Governance.conf b/certora/conf/EVault/modules/Governance.conf index 5b9ff9cb..47488184 100644 --- a/certora/conf/EVault/modules/Governance.conf +++ b/certora/conf/EVault/modules/Governance.conf @@ -14,22 +14,9 @@ "solc_via_ir": true, "solc_optimize": "10000", "rule_sanity": "basic", - // fails currently "assert_autofinder_success" : true, "msg": "Governance benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "parametric_contracts": ["Governance"], - // "prover_args": [ - // "-verifyCache", - // "-verifyTACDumps", - // "-testMode", - // "-checkRuleDigest", - // "-callTraceHardFail on" - // ], - // "java_args": [ - // "-ea", - // "-Dlevel.setup.helpers=info" - // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Initialize.conf b/certora/conf/EVault/modules/Initialize.conf index e7015ef2..205c3458 100644 --- a/certora/conf/EVault/modules/Initialize.conf +++ b/certora/conf/EVault/modules/Initialize.conf @@ -7,22 +7,10 @@ "solc_via_ir": true, "solc_optimize": "10000", "rule_sanity": "basic", - // fails currently "assert_autofinder_success" : true, "msg": "Initialize benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], "parametric_contracts": ["Initialize"], - // "prover_args": [ - // "-verifyCache", - // "-verifyTACDumps", - // "-testMode", - // "-checkRuleDigest", - // "-callTraceHardFail on" - // ], - // "java_args": [ - // "-ea", - // "-Dlevel.setup.helpers=info" - // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index 3c21da5e..1b3da1e2 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -12,7 +12,6 @@ // "solc_via_ir": true, // "solc_optimize": "10000", "rule_sanity": "basic", - // fails currently "assert_autofinder_success" : true, "msg": "Liquidation benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", @@ -21,15 +20,4 @@ "parametric_contracts": ["Liquidation"], "optimistic_loop": true, "loop_iter": "2", - // "prover_args": [ - // "-verifyCache", - // "-verifyTACDumps", - // "-testMode", - // "-checkRuleDigest", - // "-callTraceHardFail on" - // ], - // "java_args": [ - // "-ea", - // "-Dlevel.setup.helpers=info" - // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/ModuleDispatch.conf b/certora/conf/EVault/modules/ModuleDispatch.conf index 0016efdc..8f5bb8f6 100644 --- a/certora/conf/EVault/modules/ModuleDispatch.conf +++ b/certora/conf/EVault/modules/ModuleDispatch.conf @@ -7,22 +7,10 @@ "solc_via_ir": true, "solc_optimize": "10000", "rule_sanity": "basic", - // fails currently "assert_autofinder_success" : true, "msg": "Module Dispatch benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], "parametric_contracts": ["ModuleDispatch"], - // "prover_args": [ - // "-verifyCache", - // "-verifyTACDumps", - // "-testMode", - // "-checkRuleDigest", - // "-callTraceHardFail on" - // ], - // "java_args": [ - // "-ea", - // "-Dlevel.setup.helpers=info" - // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/RiskManager.conf b/certora/conf/EVault/modules/RiskManager.conf index 5da8ba30..c6cb3380 100644 --- a/certora/conf/EVault/modules/RiskManager.conf +++ b/certora/conf/EVault/modules/RiskManager.conf @@ -12,22 +12,10 @@ "solc_via_ir": true, "solc_optimize": "10000", "rule_sanity": "basic", - // fails currently "assert_autofinder_success" : true, "msg": "RiskManager benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], "parametric_contracts": ["RiskManager"], - // "prover_args": [ - // "-verifyCache", - // "-verifyTACDumps", - // "-testMode", - // "-checkRuleDigest", - // "-callTraceHardFail on" - // ], - // "java_args": [ - // "-ea", - // "-Dlevel.setup.helpers=info" - // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Token.conf b/certora/conf/EVault/modules/Token.conf index 89131f4f..b189806b 100644 --- a/certora/conf/EVault/modules/Token.conf +++ b/certora/conf/EVault/modules/Token.conf @@ -13,21 +13,9 @@ "solc_via_ir": true, "solc_optimize": "10000", "rule_sanity": "basic", - // fails currently "assert_autofinder_success" : true, "msg": "Token benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - // "prover_args": [ - // "-verifyCache", - // "-verifyTACDumps", - // "-testMode", - // "-checkRuleDigest", - // "-callTraceHardFail on" - // ], - // "java_args": [ - // "-ea", - // "-Dlevel.setup.helpers=info" - // ] } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Vault.conf b/certora/conf/EVault/modules/Vault.conf index 606bad7a..c863b279 100644 --- a/certora/conf/EVault/modules/Vault.conf +++ b/certora/conf/EVault/modules/Vault.conf @@ -12,22 +12,10 @@ "solc_via_ir": true, "solc_optimize": "10000", "rule_sanity": "basic", - // fails currently "assert_autofinder_success" : true, "msg": "Vault benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], "parametric_contracts": ["Vault"], - // "prover_args": [ - // "-verifyCache", - // "-verifyTACDumps", - // "-testMode", - // "-checkRuleDigest", - // "-callTraceHardFail on" - // ], - // "java_args": [ - // "-ea", - // "-Dlevel.setup.helpers=info" - // ] } \ No newline at end of file From 2c376598f0444d1fe0771226e453abb098145893 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 22 Mar 2024 10:56:49 +0000 Subject: [PATCH 018/152] Partial progress on bitmasks Need to figure out right setup for this --- certora/conf/EVault/DToken.conf | 2 +- certora/conf/EVault/modules/Token.conf | 2 +- certora/conf/EVault/shared/Constants.conf | 15 +++++++++++++++ certora/harness/ConstHarness.sol | 8 ++++++++ .../specs/{benchmarking => }/EVault/EVault.spec | 0 .../{benchmarking => }/EVault/modules/DToken.spec | 0 .../{benchmarking => }/EVault/modules/Token.spec | 0 certora/specs/EVault/shared/Constants.spec | 7 +++++++ 8 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 certora/conf/EVault/shared/Constants.conf create mode 100644 certora/harness/ConstHarness.sol rename certora/specs/{benchmarking => }/EVault/EVault.spec (100%) rename certora/specs/{benchmarking => }/EVault/modules/DToken.spec (100%) rename certora/specs/{benchmarking => }/EVault/modules/Token.spec (100%) create mode 100644 certora/specs/EVault/shared/Constants.spec diff --git a/certora/conf/EVault/DToken.conf b/certora/conf/EVault/DToken.conf index 1a1c614f..e34a4737 100644 --- a/certora/conf/EVault/DToken.conf +++ b/certora/conf/EVault/DToken.conf @@ -6,7 +6,7 @@ "link": [ "DToken:eVault=EVaultHarness", ], - "verify": "DToken:certora/specs/benchmarking/EVault/modules/DToken.spec", + "verify": "DToken:certora/specs/EVault/modules/DToken.spec", "solc": "solc8.23", "solc_via_ir": true, "solc_optimize": "10000", diff --git a/certora/conf/EVault/modules/Token.conf b/certora/conf/EVault/modules/Token.conf index b189806b..08621bad 100644 --- a/certora/conf/EVault/modules/Token.conf +++ b/certora/conf/EVault/modules/Token.conf @@ -8,7 +8,7 @@ "Token:evc=EthereumVaultConnector", ], "parametric_contracts": ["Token"], - "verify": "Token:certora/specs/benchmarking/EVault/modules/Token.spec", + "verify": "Token:certora/specs/EVault/modules/Token.spec", "solc": "solc8.23", "solc_via_ir": true, "solc_optimize": "10000", diff --git a/certora/conf/EVault/shared/Constants.conf b/certora/conf/EVault/shared/Constants.conf new file mode 100644 index 00000000..898d2679 --- /dev/null +++ b/certora/conf/EVault/shared/Constants.conf @@ -0,0 +1,15 @@ +{ + "files": [ + "certora/harness/ConstHarness.sol", + ], + "verify": "ConstHarness:certora/specs/EVault/shared/Constants.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Constants", + "packages": [ + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-useBitVectorTheory" + ] +} \ No newline at end of file diff --git a/certora/harness/ConstHarness.sol b/certora/harness/ConstHarness.sol new file mode 100644 index 00000000..d6095777 --- /dev/null +++ b/certora/harness/ConstHarness.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; +import "../../src/EVault/shared/Constants.sol"; + +// An empty contract to verify constant properties independently +contract ConstHarness { +} \ No newline at end of file diff --git a/certora/specs/benchmarking/EVault/EVault.spec b/certora/specs/EVault/EVault.spec similarity index 100% rename from certora/specs/benchmarking/EVault/EVault.spec rename to certora/specs/EVault/EVault.spec diff --git a/certora/specs/benchmarking/EVault/modules/DToken.spec b/certora/specs/EVault/modules/DToken.spec similarity index 100% rename from certora/specs/benchmarking/EVault/modules/DToken.spec rename to certora/specs/EVault/modules/DToken.spec diff --git a/certora/specs/benchmarking/EVault/modules/Token.spec b/certora/specs/EVault/modules/Token.spec similarity index 100% rename from certora/specs/benchmarking/EVault/modules/Token.spec rename to certora/specs/EVault/modules/Token.spec diff --git a/certora/specs/EVault/shared/Constants.spec b/certora/specs/EVault/shared/Constants.spec new file mode 100644 index 00000000..f50c1ba4 --- /dev/null +++ b/certora/specs/EVault/shared/Constants.spec @@ -0,0 +1,7 @@ +methods { + function OP_DEPOSIT() external returns (uint32) envfree; + function OP_MINT() external returns (uint32) envfree; +} +rule bitmasks_disjoint { + assert (OP_DEPOSIT() & OP_MINT()) == 0; +} \ No newline at end of file From 44e41f6377958315af2dd1cd35a25eed35c57c10 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 25 Mar 2024 08:41:06 +0000 Subject: [PATCH 019/152] MarketCache error --- certora/conf/EVault/modules/Liquidation.conf | 14 +++--- certora/harness/LiquidationHarness.sol | 25 ++++++++++ certora/specs/EVault/modules/Liquidation.spec | 47 +++++++++++++++++++ 3 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 certora/harness/LiquidationHarness.sol create mode 100644 certora/specs/EVault/modules/Liquidation.spec diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index 1b3da1e2..93402d11 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -2,22 +2,24 @@ "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "src/EVault/modules/Liquidation.sol" + // "src/EVault/shared/types/Types.sol", + "src/EVault/modules/Liquidation.sol", + "certora/harness/LiquidationHarness.sol" ], "link": [ - "Liquidation:evc=EthereumVaultConnector", + "LiquidationHarness:evc=EthereumVaultConnector", ], - "verify": "Liquidation:certora/specs/benchmarking/Benchmarking.spec", + "verify": "LiquidationHarness:certora/specs/EVault/modules/Liquidation.spec", "solc": "solc8.23", - // "solc_via_ir": true, - // "solc_optimize": "10000", + "solc_via_ir": true, + "solc_optimize": "10000", "rule_sanity": "basic", "msg": "Liquidation benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "parametric_contracts": ["Liquidation"], + "parametric_contracts": ["LiquidationHarness"], "optimistic_loop": true, "loop_iter": "2", } \ No newline at end of file diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/LiquidationHarness.sol new file mode 100644 index 00000000..4771d29f --- /dev/null +++ b/certora/harness/LiquidationHarness.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; +import "../../src/EVault/shared/types/Types.sol"; +import "../../src/EVault/modules/Liquidation.sol"; + + +contract LiquidationHarness is Liquidation { + constructor(Integrations memory integrations) Liquidation(integrations) {} + + // function calculateLiquidationExternal( + // MarketCache memory marketCache, + // address liquidator, + // address violator, + // address collateral, + // uint256 desiredRepay + // ) public view returns (LiquidationCache memory liqCache) { + // return calculateLiquidation( + // marketCache, + // liquidator, + // violator, + // collateral, + // desiredRepay); + // } +} \ No newline at end of file diff --git a/certora/specs/EVault/modules/Liquidation.spec b/certora/specs/EVault/modules/Liquidation.spec new file mode 100644 index 00000000..47682225 --- /dev/null +++ b/certora/specs/EVault/modules/Liquidation.spec @@ -0,0 +1,47 @@ +/* +CER-162 / Verify EVK-31 +If violator unhealthy, checkLiquidation returns the maximum amount of the debt +asset the liquidator is allowed to liquidate (maxRepay) in exchange for the +returned maximum amount of collateral shares from violator (maxYield). + +If violator healthy, checkLiquidation returns maxRepay and maxYield as 0. + +Unless violator healthy, considering the liquidator bonus is positive and grows +linearly as the health of the violator deteriorates, the value of maxYield is +greater than the value of maxRepay. + +If needed, checkLiquidation must limit the maxRepay as per available amount of +collateral to be seized from the violator. + +If needed, checkLiquidation must limit the maxRepay and the maxYield as per +desired amount to be repaid (desiredRepay) parameter. + +checkLiquidation must revert if: + - violator is the same account as liquidator + - collateral is not accepted + - collateral is not enabled collateral for the violator + - liability vault is not enabled as the only controller of the violator + - violator account status check is deferred + - price oracle is not configured + - price oracle is not configured +*/ + +rule checkLiquidation_healthy() { + env e; + address liquidator; + address violator; + address collateral; + MarketCache.MarketCache marketCache; + uint256 maxRepay; + uint256 maxYield; + uint256 liquidityCollateralValue; + uint256 liquidityLiabilityValue; + address[] collaterals; + // (MarketCache memory marketCache, address liquidator) = initOperation(OP_LIQUIDATE, CHECKACCOUNT_CALLER); + // (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); + // (liquidityCollateralValue, liquidityLiabilityValue) = + // calculateLiquidity(e, violator, collaterals); + require liquidityCollateralValue >= liquidityLiabilityValue; + assert maxRepay == 0; + assert maxYield == 0; +} \ No newline at end of file From afd12b255e1150ad1e36216f4d28fb6089e3d1e8 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 25 Mar 2024 16:09:33 +0000 Subject: [PATCH 020/152] liquidation mustRevert cases --- certora/conf/EVault/modules/Liquidation.conf | 1 + certora/harness/ConstHarness.sol | 3 + certora/harness/LiquidationHarness.sol | 45 +++++++++---- certora/specs/EVault/modules/Liquidation.spec | 64 ++++++++++++++++--- 4 files changed, 90 insertions(+), 23 deletions(-) diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index 93402d11..50becad0 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -19,6 +19,7 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "coverage_info" : "advanced", "parametric_contracts": ["LiquidationHarness"], "optimistic_loop": true, "loop_iter": "2", diff --git a/certora/harness/ConstHarness.sol b/certora/harness/ConstHarness.sol index d6095777..14415f3a 100644 --- a/certora/harness/ConstHarness.sol +++ b/certora/harness/ConstHarness.sol @@ -5,4 +5,7 @@ import "../../src/EVault/shared/Constants.sol"; // An empty contract to verify constant properties independently contract ConstHarness { + // Constants undeclared. Circular dependency if dropped. + uint32 public constant OP_DEPOSIT_ = OP_DEPOSIT; + uint32 public constant OP_MINT_ = OP_MINT; } \ No newline at end of file diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/LiquidationHarness.sol index 4771d29f..fe1915da 100644 --- a/certora/harness/LiquidationHarness.sol +++ b/certora/harness/LiquidationHarness.sol @@ -8,18 +8,35 @@ import "../../src/EVault/modules/Liquidation.sol"; contract LiquidationHarness is Liquidation { constructor(Integrations memory integrations) Liquidation(integrations) {} - // function calculateLiquidationExternal( - // MarketCache memory marketCache, - // address liquidator, - // address violator, - // address collateral, - // uint256 desiredRepay - // ) public view returns (LiquidationCache memory liqCache) { - // return calculateLiquidation( - // marketCache, - // liquidator, - // violator, - // collateral, - // desiredRepay); - // } + function calculateLiquidityExternal( + address account + ) public view returns (uint256 collateralValue, uint256 liabilityValue) { + MarketCache memory marketCache; // uninitialized + return calculateLiquidity(marketCache, account, getCollaterals(account), LTVType.LIQUIDATION); + } + + function initOperationExternal(uint32 operation, address accountToCheck) + public + returns (MarketCache memory marketCache, address account) + { + return initOperation(operation, accountToCheck); + } + + function getNumCollaterals(address account) public view returns (uint256) { + return getCollaterals(account).length; + } + + function isRecognizedCollateralExt(address collateral) external view virtual returns (bool) { + return isRecognizedCollateral(collateral); + } + + function isCollateralEnabledExt(address account, address market) external view returns (bool) { + return isCollateralEnabled(account, market); + } + + function isAccountStatusCheckDeferredExt(address account) external view returns (bool) { + return isAccountStatusCheckDeferred(account); + } + + } \ No newline at end of file diff --git a/certora/specs/EVault/modules/Liquidation.spec b/certora/specs/EVault/modules/Liquidation.spec index 47682225..28f0653d 100644 --- a/certora/specs/EVault/modules/Liquidation.spec +++ b/certora/specs/EVault/modules/Liquidation.spec @@ -26,22 +26,68 @@ checkLiquidation must revert if: - price oracle is not configured */ +methods { + // This is defined in IPriceOracle which is in another codebase + function _.getQuote(uint256 amount, address base, address quote) external => NONDET; + function Cache.loadMarket() internal returns (Liquidation.MarketCache memory) => UninitMarket(); + function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; + + function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; + + function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; +} + +function UninitMarket() returns Liquidation.MarketCache { + Liquidation.MarketCache mk; + return mk; +} + rule checkLiquidation_healthy() { env e; address liquidator; address violator; address collateral; - MarketCache.MarketCache marketCache; uint256 maxRepay; uint256 maxYield; uint256 liquidityCollateralValue; uint256 liquidityLiabilityValue; - address[] collaterals; - // (MarketCache memory marketCache, address liquidator) = initOperation(OP_LIQUIDATE, CHECKACCOUNT_CALLER); + + (liquidityCollateralValue, liquidityLiabilityValue) = + calculateLiquidityExternal(e, violator); + // (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); - // (liquidityCollateralValue, liquidityLiabilityValue) = - // calculateLiquidity(e, violator, collaterals); - require liquidityCollateralValue >= liquidityLiabilityValue; - assert maxRepay == 0; - assert maxYield == 0; -} \ No newline at end of file + + // bool checkReverted = lastReverted; + + // Assume healthy + // require liquidityCollateralValue >= liquidityLiabilityValue; + // assert checkReverted; + // satisfy !checkReverted; + + // require liquidityCollateralValue >= liquidityLiabilityValue; + // assert maxRepay == 0; + // assert maxYield == 0; + assert false; +} + +rule checkLiquidation_mustRevert { + env e; + address liquidator; + address violator; + address collateral; + uint256 maxRepay; + uint256 maxYield; + (maxRepay, maxYield) = checkLiquidation@withrevert(e, liquidator, violator, collateral); + bool reverted = lastReverted; + + bool selfLiquidate = liquidator == violator; + bool badCollateral = !isRecognizedCollateralExt(collateral); + bool notEnabledCollateral = !isCollateralEnabledExt(violator, collateral); + bool violatorStatusCheckDeferred = isAccountStatusCheckDeferredExt(violator); + + assert selfLiquidate || + badCollateral || + notEnabledCollateral || + violatorStatusCheckDeferred => reverted; + +} From d13d135c10268197f5a298922ab84c6b94a69b92 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 26 Mar 2024 11:18:11 +0000 Subject: [PATCH 021/152] checkLiquidation_healthy finally working --- certora/conf/EVault/modules/Liquidation.conf | 2 +- certora/harness/LiquidationHarness.sol | 19 +++++++++-- certora/specs/EVault/modules/Liquidation.spec | 34 +++++++------------ 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index 50becad0..56f72813 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -19,7 +19,7 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "coverage_info" : "advanced", + // "coverage_info" : "advanced", "parametric_contracts": ["LiquidationHarness"], "optimistic_loop": true, "loop_iter": "2", diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/LiquidationHarness.sol index fe1915da..fd5306d1 100644 --- a/certora/harness/LiquidationHarness.sol +++ b/certora/harness/LiquidationHarness.sol @@ -11,8 +11,19 @@ contract LiquidationHarness is Liquidation { function calculateLiquidityExternal( address account ) public view returns (uint256 collateralValue, uint256 liabilityValue) { - MarketCache memory marketCache; // uninitialized - return calculateLiquidity(marketCache, account, getCollaterals(account), LTVType.LIQUIDATION); + return calculateLiquidity(loadMarket(), account, getCollaterals(account), LTVType.LIQUIDATION); + } + + function getLiquidityValue(address account, MarketCache memory marketCache, address[] memory collaterals) public view returns (uint256 collateralValue) { + (collateralValue, ) = calculateLiquidity(marketCache, account, collaterals, LTVType.LIQUIDATION); + } + + function getLiabilityValue(address account, MarketCache memory marketCache, address[] memory collaterals) public view returns (uint256 liabilityValue) { + (,liabilityValue) = calculateLiquidity(marketCache, account, collaterals, LTVType.LIQUIDATION); + } + + function loadMarketExt() public returns (MarketCache memory marketCache) { + return loadMarket(); } function initOperationExternal(uint32 operation, address accountToCheck) @@ -22,6 +33,10 @@ contract LiquidationHarness is Liquidation { return initOperation(operation, accountToCheck); } + function getCollateralsExt(address account) public view returns (address[] memory) { + return getCollaterals(account); + } + function getNumCollaterals(address account) public view returns (uint256) { return getCollaterals(account).length; } diff --git a/certora/specs/EVault/modules/Liquidation.spec b/certora/specs/EVault/modules/Liquidation.spec index 28f0653d..c65c9646 100644 --- a/certora/specs/EVault/modules/Liquidation.spec +++ b/certora/specs/EVault/modules/Liquidation.spec @@ -29,7 +29,7 @@ checkLiquidation must revert if: methods { // This is defined in IPriceOracle which is in another codebase function _.getQuote(uint256 amount, address base, address quote) external => NONDET; - function Cache.loadMarket() internal returns (Liquidation.MarketCache memory) => UninitMarket(); + function _.getQuotes(uint256 amount, address base, address quote) external => NONDET; function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; @@ -37,11 +37,6 @@ methods { function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; } -function UninitMarket() returns Liquidation.MarketCache { - Liquidation.MarketCache mk; - return mk; -} - rule checkLiquidation_healthy() { env e; address liquidator; @@ -49,27 +44,23 @@ rule checkLiquidation_healthy() { address collateral; uint256 maxRepay; uint256 maxYield; - uint256 liquidityCollateralValue; - uint256 liquidityLiabilityValue; - (liquidityCollateralValue, liquidityLiabilityValue) = - calculateLiquidityExternal(e, violator); + Liquidation.MarketCache marketCache; + require marketCache.oracle!= 0; - // (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); + address[] collaterals = getCollateralsExt(e, violator); - // bool checkReverted = lastReverted; + uint256 liquidityCollateralValue = getLiquidityValue(e, violator, marketCache, collaterals); + uint256 liquidityLiabilityValue = getLiabilityValue(e, violator, marketCache, collaterals); - // Assume healthy - // require liquidityCollateralValue >= liquidityLiabilityValue; - // assert checkReverted; - // satisfy !checkReverted; - - // require liquidityCollateralValue >= liquidityLiabilityValue; - // assert maxRepay == 0; - // assert maxYield == 0; - assert false; + (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); + + require liquidityCollateralValue >= liquidityLiabilityValue; + assert maxRepay == 0; + assert maxYield == 0; } +/* rule checkLiquidation_mustRevert { env e; address liquidator; @@ -91,3 +82,4 @@ rule checkLiquidation_mustRevert { violatorStatusCheckDeferred => reverted; } +*/ \ No newline at end of file From 1985a5aa6a60817809951b7757cb1a645f64912f Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 26 Mar 2024 11:55:14 +0000 Subject: [PATCH 022/152] Partial progress on max repay greater --- certora/conf/EVault/modules/Liquidation.conf | 3 ++ certora/harness/LiquidationHarness.sol | 8 ++--- certora/specs/EVault/modules/Liquidation.spec | 31 ++++++++++++++++--- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index 56f72813..d953ff78 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -19,6 +19,9 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "rule": [ + "checkLiquidation_maxYieldGreater" + ], // "coverage_info" : "advanced", "parametric_contracts": ["LiquidationHarness"], "optimistic_loop": true, diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/LiquidationHarness.sol index fd5306d1..0add175e 100644 --- a/certora/harness/LiquidationHarness.sol +++ b/certora/harness/LiquidationHarness.sol @@ -37,10 +37,6 @@ contract LiquidationHarness is Liquidation { return getCollaterals(account); } - function getNumCollaterals(address account) public view returns (uint256) { - return getCollaterals(account).length; - } - function isRecognizedCollateralExt(address collateral) external view virtual returns (bool) { return isRecognizedCollateral(collateral); } @@ -53,5 +49,9 @@ contract LiquidationHarness is Liquidation { return isAccountStatusCheckDeferred(account); } + function marketCacheOracleConfigured() external returns (bool) { + return address(loadMarket().oracle) != address(0); + } + } \ No newline at end of file diff --git a/certora/specs/EVault/modules/Liquidation.spec b/certora/specs/EVault/modules/Liquidation.spec index c65c9646..ca0df792 100644 --- a/certora/specs/EVault/modules/Liquidation.spec +++ b/certora/specs/EVault/modules/Liquidation.spec @@ -58,9 +58,33 @@ rule checkLiquidation_healthy() { require liquidityCollateralValue >= liquidityLiabilityValue; assert maxRepay == 0; assert maxYield == 0; -} +} + +rule checkLiquidation_maxYieldGreater { + env e; + address liquidator; + address violator; + address collateral; + uint256 maxRepay; + uint256 maxYield; + + Liquidation.MarketCache marketCache; + require marketCache.oracle!= 0; + + address[] collaterals = getCollateralsExt(e, violator); + + uint256 liquidityCollateralValue = getLiquidityValue(e, violator, marketCache, collaterals); + uint256 liquidityLiabilityValue = getLiabilityValue(e, violator, marketCache, collaterals); + + (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); + + require maxYield > 0; + require maxRepay > 0; + + require liquidityCollateralValue < liquidityLiabilityValue; + assert maxYield > maxRepay; +} -/* rule checkLiquidation_mustRevert { env e; address liquidator; @@ -81,5 +105,4 @@ rule checkLiquidation_mustRevert { notEnabledCollateral || violatorStatusCheckDeferred => reverted; -} -*/ \ No newline at end of file +} \ No newline at end of file From cfa61b0a6fe6fb532b902447d1cef583855d1a91 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 26 Mar 2024 12:38:16 +0000 Subject: [PATCH 023/152] fix after master update --- certora/conf/EVault/modules/Liquidation.conf | 6 +++--- certora/harness/LiquidationHarness.sol | 20 +++++++++---------- certora/specs/EVault/modules/Liquidation.spec | 16 +++++++-------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index d953ff78..20b60154 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -19,9 +19,9 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "rule": [ - "checkLiquidation_maxYieldGreater" - ], + // "rule": [ + // "checkLiquidation_maxYieldGreater" + // ], // "coverage_info" : "advanced", "parametric_contracts": ["LiquidationHarness"], "optimistic_loop": true, diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/LiquidationHarness.sol index 0add175e..a02d7400 100644 --- a/certora/harness/LiquidationHarness.sol +++ b/certora/harness/LiquidationHarness.sol @@ -11,24 +11,24 @@ contract LiquidationHarness is Liquidation { function calculateLiquidityExternal( address account ) public view returns (uint256 collateralValue, uint256 liabilityValue) { - return calculateLiquidity(loadMarket(), account, getCollaterals(account), LTVType.LIQUIDATION); + return calculateLiquidity(loadVault(), account, getCollaterals(account), LTVType.LIQUIDATION); } - function getLiquidityValue(address account, MarketCache memory marketCache, address[] memory collaterals) public view returns (uint256 collateralValue) { - (collateralValue, ) = calculateLiquidity(marketCache, account, collaterals, LTVType.LIQUIDATION); + function getLiquidityValue(address account, VaultCache memory vaultCache, address[] memory collaterals) public view returns (uint256 collateralValue) { + (collateralValue, ) = calculateLiquidity(vaultCache, account, collaterals, LTVType.LIQUIDATION); } - function getLiabilityValue(address account, MarketCache memory marketCache, address[] memory collaterals) public view returns (uint256 liabilityValue) { - (,liabilityValue) = calculateLiquidity(marketCache, account, collaterals, LTVType.LIQUIDATION); + function getLiabilityValue(address account, VaultCache memory vaultCache, address[] memory collaterals) public view returns (uint256 liabilityValue) { + (,liabilityValue) = calculateLiquidity(vaultCache, account, collaterals, LTVType.LIQUIDATION); } - function loadMarketExt() public returns (MarketCache memory marketCache) { - return loadMarket(); + function loadVaultExt() public returns (VaultCache memory vaultCache) { + return loadVault(); } function initOperationExternal(uint32 operation, address accountToCheck) public - returns (MarketCache memory marketCache, address account) + returns (VaultCache memory vaultCache, address account) { return initOperation(operation, accountToCheck); } @@ -49,8 +49,8 @@ contract LiquidationHarness is Liquidation { return isAccountStatusCheckDeferred(account); } - function marketCacheOracleConfigured() external returns (bool) { - return address(loadMarket().oracle) != address(0); + function vaultCacheOracleConfigured() external returns (bool) { + return address(loadVault().oracle) != address(0); } diff --git a/certora/specs/EVault/modules/Liquidation.spec b/certora/specs/EVault/modules/Liquidation.spec index ca0df792..4edad8ae 100644 --- a/certora/specs/EVault/modules/Liquidation.spec +++ b/certora/specs/EVault/modules/Liquidation.spec @@ -45,13 +45,13 @@ rule checkLiquidation_healthy() { uint256 maxRepay; uint256 maxYield; - Liquidation.MarketCache marketCache; - require marketCache.oracle!= 0; + Liquidation.VaultCache vaultCache; + require vaultCache.oracle!= 0; address[] collaterals = getCollateralsExt(e, violator); - uint256 liquidityCollateralValue = getLiquidityValue(e, violator, marketCache, collaterals); - uint256 liquidityLiabilityValue = getLiabilityValue(e, violator, marketCache, collaterals); + uint256 liquidityCollateralValue = getLiquidityValue(e, violator, vaultCache, collaterals); + uint256 liquidityLiabilityValue = getLiabilityValue(e, violator, vaultCache, collaterals); (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); @@ -68,13 +68,13 @@ rule checkLiquidation_maxYieldGreater { uint256 maxRepay; uint256 maxYield; - Liquidation.MarketCache marketCache; - require marketCache.oracle!= 0; + Liquidation.VaultCache vaultCache; + require vaultCache.oracle!= 0; address[] collaterals = getCollateralsExt(e, violator); - uint256 liquidityCollateralValue = getLiquidityValue(e, violator, marketCache, collaterals); - uint256 liquidityLiabilityValue = getLiabilityValue(e, violator, marketCache, collaterals); + uint256 liquidityCollateralValue = getLiquidityValue(e, violator, vaultCache, collaterals); + uint256 liquidityLiabilityValue = getLiabilityValue(e, violator, vaultCache, collaterals); (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); From 2c6c90c5d6e8b282cc482755fdf95cca2114016b Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 27 Mar 2024 13:43:02 +0000 Subject: [PATCH 024/152] Prove reverting for calculateLiquidation Temporarily give up on trying to prove the same for liquidate... --- certora/harness/LiquidationHarness.sol | 17 +++ certora/specs/EVault/modules/Liquidation.spec | 108 ------------------ 2 files changed, 17 insertions(+), 108 deletions(-) delete mode 100644 certora/specs/EVault/modules/Liquidation.spec diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/LiquidationHarness.sol index a02d7400..81bd88d5 100644 --- a/certora/harness/LiquidationHarness.sol +++ b/certora/harness/LiquidationHarness.sol @@ -6,6 +6,9 @@ import "../../src/EVault/modules/Liquidation.sol"; contract LiquidationHarness is Liquidation { + // VaultCache vaultCache_; + // LiquidationCache liqCache_; + constructor(Integrations memory integrations) Liquidation(integrations) {} function calculateLiquidityExternal( @@ -53,5 +56,19 @@ contract LiquidationHarness is Liquidation { return address(loadVault().oracle) != address(0); } + function getLiquidator() external returns (address liquidator) { + (, liquidator) = initOperation(OP_LIQUIDATE, CHECKACCOUNT_CALLER); + } + + function calculateLiquidationExt( + VaultCache memory vaultCache, + address liquidator, + address violator, + address collateral, + uint256 desiredRepay + ) external view returns (LiquidationCache memory liqCache) { + return calculateLiquidation(vaultCache, liquidator, violator, collateral, desiredRepay); + } + } \ No newline at end of file diff --git a/certora/specs/EVault/modules/Liquidation.spec b/certora/specs/EVault/modules/Liquidation.spec deleted file mode 100644 index 4edad8ae..00000000 --- a/certora/specs/EVault/modules/Liquidation.spec +++ /dev/null @@ -1,108 +0,0 @@ -/* -CER-162 / Verify EVK-31 -If violator unhealthy, checkLiquidation returns the maximum amount of the debt -asset the liquidator is allowed to liquidate (maxRepay) in exchange for the -returned maximum amount of collateral shares from violator (maxYield). - -If violator healthy, checkLiquidation returns maxRepay and maxYield as 0. - -Unless violator healthy, considering the liquidator bonus is positive and grows -linearly as the health of the violator deteriorates, the value of maxYield is -greater than the value of maxRepay. - -If needed, checkLiquidation must limit the maxRepay as per available amount of -collateral to be seized from the violator. - -If needed, checkLiquidation must limit the maxRepay and the maxYield as per -desired amount to be repaid (desiredRepay) parameter. - -checkLiquidation must revert if: - - violator is the same account as liquidator - - collateral is not accepted - - collateral is not enabled collateral for the violator - - liability vault is not enabled as the only controller of the violator - - violator account status check is deferred - - price oracle is not configured - - price oracle is not configured -*/ - -methods { - // This is defined in IPriceOracle which is in another codebase - function _.getQuote(uint256 amount, address base, address quote) external => NONDET; - function _.getQuotes(uint256 amount, address base, address quote) external => NONDET; - function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; - - function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; - - function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; -} - -rule checkLiquidation_healthy() { - env e; - address liquidator; - address violator; - address collateral; - uint256 maxRepay; - uint256 maxYield; - - Liquidation.VaultCache vaultCache; - require vaultCache.oracle!= 0; - - address[] collaterals = getCollateralsExt(e, violator); - - uint256 liquidityCollateralValue = getLiquidityValue(e, violator, vaultCache, collaterals); - uint256 liquidityLiabilityValue = getLiabilityValue(e, violator, vaultCache, collaterals); - - (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); - - require liquidityCollateralValue >= liquidityLiabilityValue; - assert maxRepay == 0; - assert maxYield == 0; -} - -rule checkLiquidation_maxYieldGreater { - env e; - address liquidator; - address violator; - address collateral; - uint256 maxRepay; - uint256 maxYield; - - Liquidation.VaultCache vaultCache; - require vaultCache.oracle!= 0; - - address[] collaterals = getCollateralsExt(e, violator); - - uint256 liquidityCollateralValue = getLiquidityValue(e, violator, vaultCache, collaterals); - uint256 liquidityLiabilityValue = getLiabilityValue(e, violator, vaultCache, collaterals); - - (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); - - require maxYield > 0; - require maxRepay > 0; - - require liquidityCollateralValue < liquidityLiabilityValue; - assert maxYield > maxRepay; -} - -rule checkLiquidation_mustRevert { - env e; - address liquidator; - address violator; - address collateral; - uint256 maxRepay; - uint256 maxYield; - (maxRepay, maxYield) = checkLiquidation@withrevert(e, liquidator, violator, collateral); - bool reverted = lastReverted; - - bool selfLiquidate = liquidator == violator; - bool badCollateral = !isRecognizedCollateralExt(collateral); - bool notEnabledCollateral = !isCollateralEnabledExt(violator, collateral); - bool violatorStatusCheckDeferred = isAccountStatusCheckDeferredExt(violator); - - assert selfLiquidate || - badCollateral || - notEnabledCollateral || - violatorStatusCheckDeferred => reverted; - -} \ No newline at end of file From b2bf90b64fc30014eda301a0680fe1aa1cbc5cd5 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 2 Apr 2024 11:33:44 +0100 Subject: [PATCH 025/152] Fixes for function finding --- certora/conf/CER-131-Vault.conf | 24 ++++ .../CER-162-CheckLiquidation.conf | 32 +++++ .../CER-163-Liquidate.conf | 31 +++++ certora/conf/EVault/modules/Liquidation.conf | 9 +- certora/harness/LiquidationHarness.sol | 5 + certora/specs/CER-131-Vault.spec | 114 ++++++++++++++++++ .../CER-162-CheckLiquidation.spec | 109 +++++++++++++++++ .../CER-163-Liquidate.spec | 91 ++++++++++++++ 8 files changed, 412 insertions(+), 3 deletions(-) create mode 100644 certora/conf/CER-131-Vault.conf create mode 100644 certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf create mode 100644 certora/conf/CER-161-Liquidation/CER-163-Liquidate.conf create mode 100644 certora/specs/CER-131-Vault.spec create mode 100644 certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec create mode 100644 certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec diff --git a/certora/conf/CER-131-Vault.conf b/certora/conf/CER-131-Vault.conf new file mode 100644 index 00000000..723d5a9e --- /dev/null +++ b/certora/conf/CER-131-Vault.conf @@ -0,0 +1,24 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "src/EVault/modules/Vault.sol" + ], + "link" : [ + "Vault:evc=EthereumVaultConnector", + ], + "verify": "Vault:certora/specs/CER-131-Vault.spec", + "solc": "solc8.23", + "msg": "Vault benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "parametric_contracts": ["Vault"], + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, + "prover_version" : "master" // need for dev fix +} \ No newline at end of file diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf new file mode 100644 index 00000000..e368fb1d --- /dev/null +++ b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf @@ -0,0 +1,32 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", + // "src/EVault/shared/types/Types.sol", + "src/EVault/modules/Liquidation.sol", + "certora/harness/LiquidationHarness.sol" + ], + "link": [ + "LiquidationHarness:evc=EthereumVaultConnector", + ], + "verify": "LiquidationHarness:certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec", + "solc": "solc8.23", + "msg": "Liquidation benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + // "rule": [ + // "checkLiquidation_maxYieldGreater" + // ], + // "coverage_info" : "advanced", + "parametric_contracts": ["LiquidationHarness"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, + "prover_version" : "master" // need for dev fix +} \ No newline at end of file diff --git a/certora/conf/CER-161-Liquidation/CER-163-Liquidate.conf b/certora/conf/CER-161-Liquidation/CER-163-Liquidate.conf new file mode 100644 index 00000000..e16d1dcb --- /dev/null +++ b/certora/conf/CER-161-Liquidation/CER-163-Liquidate.conf @@ -0,0 +1,31 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "src/EVault/modules/Liquidation.sol", + "certora/harness/LiquidationHarness.sol" + ], + "link": [ + "LiquidationHarness:evc=EthereumVaultConnector", + ], + "verify": "LiquidationHarness:certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec", + "solc": "solc8.23", + "msg": "Liquidation benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + // "rule": [ + // "calculateLiquidation_setViolator" + // ], + // "coverage_info" : "advanced", + "parametric_contracts": ["LiquidationHarness"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, + "prover_version" : "master" // need for dev fix +} \ No newline at end of file diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index 20b60154..99a6bab0 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -11,9 +11,6 @@ ], "verify": "LiquidationHarness:certora/specs/EVault/modules/Liquidation.spec", "solc": "solc8.23", - "solc_via_ir": true, - "solc_optimize": "10000", - "rule_sanity": "basic", "msg": "Liquidation benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", @@ -26,4 +23,10 @@ "parametric_contracts": ["LiquidationHarness"], "optimistic_loop": true, "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : "no", + "prover_version" : "master" // need for dev fix } \ No newline at end of file diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/LiquidationHarness.sol index 81bd88d5..7baa7631 100644 --- a/certora/harness/LiquidationHarness.sol +++ b/certora/harness/LiquidationHarness.sol @@ -60,6 +60,11 @@ contract LiquidationHarness is Liquidation { (, liquidator) = initOperation(OP_LIQUIDATE, CHECKACCOUNT_CALLER); } + function vaultIsOnlyController(address account) external view returns (bool) { + address[] memory controllers = IEVC(evc).getControllers(account); + return controllers.length == 1 && controllers[0] == address(this); + } + function calculateLiquidationExt( VaultCache memory vaultCache, address liquidator, diff --git a/certora/specs/CER-131-Vault.spec b/certora/specs/CER-131-Vault.spec new file mode 100644 index 00000000..8e876605 --- /dev/null +++ b/certora/specs/CER-131-Vault.spec @@ -0,0 +1,114 @@ +/* +CER-132 / EVK-45 convertToAssets +returns the amount of assets (rounding down) +that the vault would exchange for the amount of shares provided. The function +must make calculations based on the total shares amount, the amount of assets +held by the vault and the amount of liabilities issued. It must be ensured that +the vault implements the function in a manipulation-resistant manner. +*/ + +/* +CER-133 / EVK-46 convertToShares +returns the amount of shares (rounding down) +that the vault would exchange for the amount of assets provided. The function +must make calculations based on the total shares amount, the amount of assets +held by the vault and the amount of liabilities issued. It must be ensured that +the vault implements the function in a manipulation-resistant manner. +*/ + +/* +CER-134 / EVK-47 deposit +If operation enabled, deposit mints vault shares (rounding +down) to receiver by depositing exactly assets of underlying tokens pulled from +the authenticated account. + +If balance forwarding enabled for the receiver address, the balance tracker hook +must be called with the new shares balance of receiver. This operation is +always called through the EVC. This operation schedules the vault status check. + +This operation affects: +- shares balance of the receiver account +- total shares balance +- total balance of the underlying assets held by the vault +*/ + +/* +CER-135 / EVK-48 mint +If operation enabled, mint mints exactly shares vault shares to receiver by +depositing corresponding amount of underlying tokens (rounding up) pulled from +the authenticated account. If balance forwarding enabled for the receiver +address, the balance tracker hook must be called with the new shares balance of +receiver. This operation is always called through the EVC. This operation +schedules the vault status check. +This operation affects: +- shares balance of the receiver account +- total shares balance +- total balance of the underlying assets held by the vault +*/ + +/* +CER-136 / EVK-49 withdraw +If operation enabled, withdraw burns vault shares (rounding up) from owner and +sends exactly assets of underlying tokens to receiver. If the owner account does +not belong to the authenticated account, the amount of shares burned is a +subject to the ERC20 allowance check. +If balance forwarding enabled for the owner address, the balance tracker hook +must be called with the new shares balance of owner. +If asset receiver validation enabled, this operation must protect user from +sending assets to a virtual account. +This operation is always called through the EVC. +This operation schedules the account status check on the owner address. +This operation schedules the vault status check. +This operation affects: + - shares balance of the owner account + - total shares balance + - total balance of the underlying assets held by the vault +*/ + +/* +CER-137 / EVK-50 redeem +If operation enabled, redeem burns exactly shares vault shares from owner and +sends corresponding amount of underlying tokens (rounding down) to receiver. If +the owner account does not belong to the authenticated account, the amount of +shares burned is a subject to the ERC20 allowance check. +If balance forwarding enabled for the owner address, the balance tracker hook +must be called with the new shares balance of owner. +If asset receiver validation enabled, this operation must protect user from +sending assets to a virtual account. +This operation is always called through the EVC. +This operation schedules the account status check on the owner address. +This operation schedules the vault status check. + +This operation affects: + - shares balance of the owner account + - total shares balance + - total balance of the underlying assets held by the vault +*/ + +/* +CER-138 / EVK-51 skim +If operation enabled, skim mints vault shares (rounding down) to receiver by +assuming that the excess of the underlying tokens, that may occur due to +internal balance tracking, belongs to the receiver. +If balance forwarding enabled for the receiver address, the balance tracker hook +must be called with the new shares balance of receiver. +This operation is always called through the EVC. +This operation schedules the vault status check. +This operation affects: + - shares balance of the receiver account + - total shares balance + - total balance of the underlying assets held by the vault +*/ +methods { + function _.requireVaultStatusCheck() external => NONDET; + function _.requireAccountAndVaultStatusCheck(address account) external => NONDET; + function _.calculateDTokenAddress() internal => NONDET; + function EVCClient.EVCRequireStatusChecks(address account) internal => NONDET; +} + +rule sanity (method f) { + env e; + calldataarg args; + f(e, args); + satisfy true; +} \ No newline at end of file diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec new file mode 100644 index 00000000..18f4e170 --- /dev/null +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -0,0 +1,109 @@ +/* +CER-162 / Verify EVK-31 +If violator unhealthy, checkLiquidation returns the maximum amount of the debt +asset the liquidator is allowed to liquidate (maxRepay) in exchange for the +returned maximum amount of collateral shares from violator (maxYield). + +If violator healthy, checkLiquidation returns maxRepay and maxYield as 0. + +Unless violator healthy, considering the liquidator bonus is positive and grows +linearly as the health of the violator deteriorates, the value of maxYield is +greater than the value of maxRepay. + +If needed, checkLiquidation must limit the maxRepay as per available amount of +collateral to be seized from the violator. + +If needed, checkLiquidation must limit the maxRepay and the maxYield as per +desired amount to be repaid (desiredRepay) parameter. + +checkLiquidation must revert if: + - violator is the same account as liquidator + - collateral is not accepted + - collateral is not enabled collateral for the violator + - liability vault is not enabled as the only controller of the violator + - violator account status check is deferred + - price oracle is not configured + - price oracle is not configured +*/ + +methods { + // This is defined in IPriceOracle which is in another codebase + function _.getQuote(uint256 amount, address base, address quote) external => NONDET; + function _.getQuotes(uint256 amount, address base, address quote) external => NONDET; + function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; + + function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; + + function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; +} + +rule checkLiquidation_healthy() { + env e; + address liquidator; + address violator; + address collateral; + uint256 maxRepay; + uint256 maxYield; + + Liquidation.VaultCache vaultCache; + require vaultCache.oracle!= 0; + + address[] collaterals = getCollateralsExt(e, violator); + + uint256 liquidityCollateralValue = getLiquidityValue(e, violator, vaultCache, collaterals); + uint256 liquidityLiabilityValue = getLiabilityValue(e, violator, vaultCache, collaterals); + + (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); + + require liquidityCollateralValue >= liquidityLiabilityValue; + assert maxRepay == 0; + assert maxYield == 0; +} + +rule checkLiquidation_maxYieldGreater { + env e; + address liquidator; + address violator; + address collateral; + uint256 maxRepay; + uint256 maxYield; + + // Liquidation.VaultCache vaultCache; + // require vaultCache.oracle!= 0; + + // address[] collaterals = getCollateralsExt(e, violator); + + // uint256 liquidityCollateralValue = getLiquidityValue(e, violator, vaultCache, collaterals); + // uint256 liquidityLiabilityValue = getLiabilityValue(e, violator, vaultCache, collaterals); + + (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); + + require maxYield > 0; + require maxRepay > 0; + + satisfy true; + // require liquidityCollateralValue < liquidityLiabilityValue; + // assert maxYield > maxRepay; +} + +rule checkLiquidation_mustRevert { + env e; + address liquidator; + address violator; + address collateral; + uint256 maxRepay; + uint256 maxYield; + (maxRepay, maxYield) = checkLiquidation@withrevert(e, liquidator, violator, collateral); + bool reverted = lastReverted; + + bool selfLiquidate = liquidator == violator; + bool badCollateral = !isRecognizedCollateralExt(collateral); + bool notEnabledCollateral = !isCollateralEnabledExt(violator, collateral); + bool violatorStatusCheckDeferred = isAccountStatusCheckDeferredExt(violator); + + assert selfLiquidate || + badCollateral || + notEnabledCollateral || + violatorStatusCheckDeferred => reverted; + +} \ No newline at end of file diff --git a/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec b/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec new file mode 100644 index 00000000..91a47148 --- /dev/null +++ b/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec @@ -0,0 +1,91 @@ +/* +CER-163 / Verify EVK-7 +If operation enabled AND violator unhealthy, liquidate: + +liquidates the debt of the violator and transfers it to the liquidator, up to +the amount returned by checkLiquidation as per desired amount to be repaid +specified (repayAssets) + +seizes the collateral shares of the violator and transfers them to the +liquidator, up to the amount returned by checkLiquidation as per desired amount +to be repaid specified (repayAssets) + +If collateral is worthless, it can be seized without taking on any debt by the +liquidator. + +If operation enabled AND violator healthy, liquidate must be a no-op. + +If debt socialization enabled AND the violator has outstanding debt after the +liquidation AND the violator has no more accepted collaterals, the debt must be +socialized amongst all the lenders in the vault. + +liquidate must revert if: + - liquidate operation disabled + - violator is the same account as liquidator + - collateral is not accepted + - collateral is not enabled collateral for the violator + - liability vault is not enabled as the only controller of the violator + - liability vault is not enabled as the only controller of the liquidator + - violator account status check is deferred + - price oracle is not configured + - amount of collateral to be seized is less than the desired amount of + yieldspecified (minYieldBalance) + +This operation is always called through the EVC. +This operation schedules the account status check on the liquidator address. +This operation schedules the vault status check. + +Refer to the EVC documentation to learn how the collateral seizing mechanism +works: +*/ + +methods { + function _.requireVaultStatusCheck() external => NONDET; + function _.requireAccountAndVaultStatusCheck(address account) external => NONDET; + function _.calculateDTokenAddress() internal => NONDET; + function EVCClient.EVCRequireStatusChecks(address account) internal => NONDET; + function _.validateAndCallHook(Liquidation.Flags hookedOps, uint32 operation, address caller) internal => NONDET; + function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; + function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; + function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; + function vaultIsOnlyController(address account) external returns (bool) envfree; +} + +rule calculateLiquidation_setViolator { + env e; + Liquidation.VaultCache vaultCache; + address liquidator; + address violator; + address collateral; + uint256 desiredRepay; + LiquidationModule.LiquidationCache liqCache = calculateLiquidationExt(e, + vaultCache, + liquidator, + violator, + collateral, + desiredRepay); + assert liqCache.violator == violator; + assert liqCache.liquidator == liquidator; + assert violator != liquidator; +} + +rule liquidate_mustRevert { + env e; + address violator; + address collateral; + uint256 repayAssets; + uint256 minYieldBalance; + + address liquidator = getLiquidator(e); + bool selfLiquidation = violator == liquidator; + bool recognizedCollateral = isREcognizedCollateralExt(collateral); + bool enabledCollateral = isCollateralEnabledExt(violator, collateral); + bool violatorStatusCheckDeferred = isAccountStatusCheckDeferredExt(violator); + + liquidate(e, violator, collateral, repayAssets, minYieldBalance); + assert !selfLiquidation; + assert recognizedCollateral; + assert enabledCollateral; + assert !violatorStatusCheckDeferred; + +} \ No newline at end of file From f5bf19f539d86016351bedd41c286b701ebc0128 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 5 Jun 2024 14:26:51 +0100 Subject: [PATCH 026/152] Can get past loadVault sanity with summary --- .../CER-162-CheckLiquidation.conf | 9 +- certora/harness/LiquidationHarness.sol | 18 ++- .../CER-162-CheckLiquidation.spec | 111 ++++++++++++++++-- .../CER-163-Liquidate.spec | 10 +- lib/ethereum-vault-connector | 2 +- src/EVault/modules/Liquidation.sol | 10 ++ 6 files changed, 144 insertions(+), 16 deletions(-) diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf index e368fb1d..ac801538 100644 --- a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf +++ b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf @@ -2,6 +2,7 @@ "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "src/EVault/shared/lib/SafeERC20Lib.sol", // "src/EVault/shared/types/Types.sol", "src/EVault/modules/Liquidation.sol", "certora/harness/LiquidationHarness.sol" @@ -16,9 +17,11 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - // "rule": [ - // "checkLiquidation_maxYieldGreater" - // ], + "rule": [ + "loadVaultSanity" + // "checkLiquidation_maxYieldGreater" + // "debugCheckLiquidation" + ], // "coverage_info" : "advanced", "parametric_contracts": ["LiquidationHarness"], "optimistic_loop": true, diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/LiquidationHarness.sol index 7baa7631..b71d6a0c 100644 --- a/certora/harness/LiquidationHarness.sol +++ b/certora/harness/LiquidationHarness.sol @@ -3,7 +3,9 @@ pragma solidity ^0.8.0; import "../../src/EVault/shared/types/Types.sol"; import "../../src/EVault/modules/Liquidation.sol"; - +import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; +import "../../src/interfaces/IPriceOracle.sol"; +import {IERC20} from "../../src/EVault/IEVault.sol"; contract LiquidationHarness is Liquidation { // VaultCache vaultCache_; @@ -14,7 +16,11 @@ contract LiquidationHarness is Liquidation { function calculateLiquidityExternal( address account ) public view returns (uint256 collateralValue, uint256 liabilityValue) { - return calculateLiquidity(loadVault(), account, getCollaterals(account), LTVType.LIQUIDATION); + // This is intentionally an uninitialized vaultCache. + // calculateLiquidity is summarized in a way that vaultCache does not + // matter and calling loadVault() causes issues. + VaultCache memory vaultCache; + return calculateLiquidity(vaultCache, account, getCollaterals(account), LTVType.LIQUIDATION); } function getLiquidityValue(address account, VaultCache memory vaultCache, address[] memory collaterals) public view returns (uint256 collateralValue) { @@ -56,6 +62,10 @@ contract LiquidationHarness is Liquidation { return address(loadVault().oracle) != address(0); } + function validateOracleExt(VaultCache memory vaultCache) external pure { + validateOracle(vaultCache); + } + function getLiquidator() external returns (address liquidator) { (, liquidator) = initOperation(OP_LIQUIDATE, CHECKACCOUNT_CALLER); } @@ -75,5 +85,9 @@ contract LiquidationHarness is Liquidation { return calculateLiquidation(vaultCache, liquidator, violator, collateral, desiredRepay); } + function getCurrentOwedExt(VaultCache memory vaultCache, address violator) external view returns (Assets) { + return getCurrentOwed(vaultCache, violator).toAssetsUp(); + } + } \ No newline at end of file diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index 18f4e170..4e422d2d 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -26,6 +26,8 @@ checkLiquidation must revert if: - price oracle is not configured */ +using SafeERC20Lib as safeERC20; + methods { // This is defined in IPriceOracle which is in another codebase function _.getQuote(uint256 amount, address base, address quote) external => NONDET; @@ -35,6 +37,57 @@ methods { function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; + // function Cache.initVaultCache(Liquidation.VaultCache memory vaultCache) internal returns (bool) => NONDET; + // function LiquidityUtils.calculateLiquidity( + // Liquidation.VaultCache memory vaultCache, + // address account, + // address[] memory collaterals, + // Liquidation.LTVType ltvType + // ) internal returns (uint256, uint256) => calcLiquidity(account, collaterals, ltvType); + + // function ProxyUtils.metadata() internal returns (address, address, address)=> NONDET; + + // Workaround for lack of ability to summarize metadata + function Cache.loadVault() internal returns (Liquidation.VaultCache memory) => CVLLoadVault(); + + + // IERC20 + function _.name() external => DISPATCHER(true); + function _.symbol() external => DISPATCHER(true); + function _.decimals() external => DISPATCHER(true); + function _.totalSupply() external => DISPATCHER(true); + function _.balanceOf(address) external => DISPATCHER(true); + function _.allowance(address,address) external => DISPATCHER(true); + function _.approve(address,uint256) external => DISPATCHER(true); + function _.transfer(address,uint256) external => DISPATCHER(true); + function _.transferFrom(address,address,uint256) external => DISPATCHER(true); +} + +ghost address oracleAddress; +ghost address unitOfAccount; +function CVLProxyMetadata() returns (address, address, address) { + return (safeERC20, oracleAddress, unitOfAccount); +} + +function CVLLoadVault() returns Liquidation.VaultCache { + Liquidation.VaultCache vaultCache; + require vaultCache.oracle != 0; + return vaultCache; +} + +persistent ghost uint256 ghost_liability; +persistent ghost uint256 ghost_collateral; + +function calcLiabilityValue(address account, address[] collaterals, Liquidation.LTVType ltvType) returns uint256 { + return ghost_liability; +} + +function calcCollateralValue(address account, address[] collaterals, Liquidation.LTVType ltvType) returns uint256 { + return ghost_collateral; +} + +function calcLiquidity(address account, address[] collaterals, Liquidation.LTVType ltvType) returns (uint256, uint256) { + return (ghost_collateral, ghost_liability); } rule checkLiquidation_healthy() { @@ -46,7 +99,7 @@ rule checkLiquidation_healthy() { uint256 maxYield; Liquidation.VaultCache vaultCache; - require vaultCache.oracle!= 0; + require vaultCache.oracle != 0; address[] collaterals = getCollateralsExt(e, violator); @@ -68,24 +121,64 @@ rule checkLiquidation_maxYieldGreater { uint256 maxRepay; uint256 maxYield; - // Liquidation.VaultCache vaultCache; - // require vaultCache.oracle!= 0; - - // address[] collaterals = getCollateralsExt(e, violator); - - // uint256 liquidityCollateralValue = getLiquidityValue(e, violator, vaultCache, collaterals); - // uint256 liquidityLiabilityValue = getLiabilityValue(e, violator, vaultCache, collaterals); + require ghost_collateral > 0; + require ghost_liability > 0; + require ghost_collateral < ghost_liability; (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); + // If this works try to work this assumption "backwards" require maxYield > 0; require maxRepay > 0; + + assert maxYield >= maxRepay; + + + // assert false; - satisfy true; // require liquidityCollateralValue < liquidityLiabilityValue; // assert maxYield > maxRepay; } +rule debug_calculate_liquidity { + env e; + address account; + uint256 calculatedCollateral; + uint256 calculatedLiability; + (calculatedCollateral, calculatedLiability) = calculateLiquidityExternal(e, account); + assert calculatedCollateral == ghost_collateral; + assert calculatedLiability == ghost_liability; +} + +rule debugCheckLiquidation { + env e; + address violator; + Liquidation.VaultCache vaultCache = loadVaultExt(e); + + Liquidation.Assets owed = getCurrentOwedExt(e, vaultCache, violator); + // satisfy !isZero(owed); + satisfy owed > 0; +} + +rule alwaysRevert { + env e; + address liquidator; + address violator; + address collateral; + + checkLiquidation@withrevert(e, liquidator, violator, collateral); + satisfy !lastReverted; + +} + +rule loadVaultSanity { + env e; + // require oracleAddress != 0; + Liquidation.VaultCache vaultCache = loadVaultExt(e); + // validateOracleExt(e, vaultCache); + assert vaultCache.oracle != 0; +} + rule checkLiquidation_mustRevert { env e; address liquidator; diff --git a/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec b/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec index 91a47148..3d6e2016 100644 --- a/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec +++ b/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec @@ -49,6 +49,7 @@ methods { function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; function vaultIsOnlyController(address account) external returns (bool) envfree; + function ProxyUtils.metadata() internal returns (address, address, address)=> NONDET; } rule calculateLiquidation_setViolator { @@ -78,14 +79,21 @@ rule liquidate_mustRevert { address liquidator = getLiquidator(e); bool selfLiquidation = violator == liquidator; - bool recognizedCollateral = isREcognizedCollateralExt(collateral); + bool recognizedCollateral = isRecognizedCollateralExt(collateral); bool enabledCollateral = isCollateralEnabledExt(violator, collateral); bool violatorStatusCheckDeferred = isAccountStatusCheckDeferredExt(violator); + bool vaultControlsLiquidator = vaultIsOnlyController(liquidator); + bool vaultControlsViolator = vaultIsOnlyController(violator); + bool oracleConfigured = vaultCacheOracleConfigured(e); liquidate(e, violator, collateral, repayAssets, minYieldBalance); + // TODO liquidate operation not disabled assert !selfLiquidation; assert recognizedCollateral; assert enabledCollateral; + // assert vaultControlsLiquidator; // VIOLATED + assert vaultControlsViolator; assert !violatorStatusCheckDeferred; + // assert oracleConfigured; } \ No newline at end of file diff --git a/lib/ethereum-vault-connector b/lib/ethereum-vault-connector index 0229f62f..d24ce8c9 160000 --- a/lib/ethereum-vault-connector +++ b/lib/ethereum-vault-connector @@ -1 +1 @@ -Subproject commit 0229f62f92856201e1f33bee9e59daf68938ba34 +Subproject commit d24ce8c92fffbd0c95948c1c843e3e23b27318ca diff --git a/src/EVault/modules/Liquidation.sol b/src/EVault/modules/Liquidation.sol index e938a4e0..571c1260 100644 --- a/src/EVault/modules/Liquidation.sol +++ b/src/EVault/modules/Liquidation.sol @@ -109,6 +109,7 @@ abstract contract LiquidationModule is ILiquidation, BalanceUtils, LiquidityUtil liqCache.repay = desiredRepay.toAssets(); } } + } function calculateMaxLiquidation(LiquidationCache memory liqCache, VaultCache memory vaultCache) @@ -154,6 +155,15 @@ abstract contract LiquidationModule is ILiquidation, BalanceUtils, LiquidityUtil return liqCache; } +<<<<<<< HEAD +======= + uint256 liabilityValue = liqCache.owed.toUint(); + if (address(vaultCache.asset) != vaultCache.unitOfAccount) { + liabilityValue = + vaultCache.oracle.getQuote(liabilityValue, address(vaultCache.asset), vaultCache.unitOfAccount); + } + +>>>>>>> 9763b68 (Can get past loadVault sanity with summary) uint256 maxRepayValue = liabilityValue; uint256 maxYieldValue = maxRepayValue * 1e18 / discountFactor; From 7f3062c9f58314b376944ecb652bb66f4dba97b3 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 5 Apr 2024 11:30:38 +0100 Subject: [PATCH 027/152] Summarize PriceOracle, cleanup --- .../CER-162-CheckLiquidation.conf | 10 ++-- certora/harness/LiquidationHarness.sol | 6 +- .../CER-162-CheckLiquidation.spec | 60 +++++++------------ 3 files changed, 28 insertions(+), 48 deletions(-) diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf index ac801538..236c5a58 100644 --- a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf +++ b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf @@ -17,11 +17,11 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "rule": [ - "loadVaultSanity" - // "checkLiquidation_maxYieldGreater" - // "debugCheckLiquidation" - ], + // "rule": [ + // "loadVaultSanity" + // // "checkLiquidation_maxYieldGreater" + // // "debugCheckLiquidation" + // ], // "coverage_info" : "advanced", "parametric_contracts": ["LiquidationHarness"], "optimistic_loop": true, diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/LiquidationHarness.sol index b71d6a0c..233c01b8 100644 --- a/certora/harness/LiquidationHarness.sol +++ b/certora/harness/LiquidationHarness.sol @@ -16,11 +16,7 @@ contract LiquidationHarness is Liquidation { function calculateLiquidityExternal( address account ) public view returns (uint256 collateralValue, uint256 liabilityValue) { - // This is intentionally an uninitialized vaultCache. - // calculateLiquidity is summarized in a way that vaultCache does not - // matter and calling loadVault() causes issues. - VaultCache memory vaultCache; - return calculateLiquidity(vaultCache, account, getCollaterals(account), LTVType.LIQUIDATION); + return calculateLiquidity(loadVault(), account, getCollaterals(account), LTVType.LIQUIDATION); } function getLiquidityValue(address account, VaultCache memory vaultCache, address[] memory collaterals) public view returns (uint256 collateralValue) { diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index 4e422d2d..68d36cb8 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -29,9 +29,9 @@ checkLiquidation must revert if: using SafeERC20Lib as safeERC20; methods { - // This is defined in IPriceOracle which is in another codebase - function _.getQuote(uint256 amount, address base, address quote) external => NONDET; - function _.getQuotes(uint256 amount, address base, address quote) external => NONDET; + // IPriceOracle + function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); + function _.getQuotes(uint256 amount, address base, address quote) external => CVLGetQuotes(amount, base, quote) expect (uint256, uint256); function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; @@ -50,7 +50,6 @@ methods { // Workaround for lack of ability to summarize metadata function Cache.loadVault() internal returns (Liquidation.VaultCache memory) => CVLLoadVault(); - // IERC20 function _.name() external => DISPATCHER(true); function _.symbol() external => DISPATCHER(true); @@ -63,6 +62,17 @@ methods { function _.transferFrom(address,address,uint256) external => DISPATCHER(true); } +function CVLGetQuote(uint256 amount, address base, address quote) returns uint256 { + uint256 out; + return out; +} + +function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint256, uint256) { + uint256 bidOut; + uint256 askOut; + return (bidOut, askOut); +} + ghost address oracleAddress; ghost address unitOfAccount; function CVLProxyMetadata() returns (address, address, address) { @@ -75,21 +85,6 @@ function CVLLoadVault() returns Liquidation.VaultCache { return vaultCache; } -persistent ghost uint256 ghost_liability; -persistent ghost uint256 ghost_collateral; - -function calcLiabilityValue(address account, address[] collaterals, Liquidation.LTVType ltvType) returns uint256 { - return ghost_liability; -} - -function calcCollateralValue(address account, address[] collaterals, Liquidation.LTVType ltvType) returns uint256 { - return ghost_collateral; -} - -function calcLiquidity(address account, address[] collaterals, Liquidation.LTVType ltvType) returns (uint256, uint256) { - return (ghost_collateral, ghost_liability); -} - rule checkLiquidation_healthy() { env e; address liquidator; @@ -121,9 +116,14 @@ rule checkLiquidation_maxYieldGreater { uint256 maxRepay; uint256 maxYield; - require ghost_collateral > 0; - require ghost_liability > 0; - require ghost_collateral < ghost_liability; + uint256 collateralValue; + uint256 liabilityValue; + (collateralValue, liabilityValue) = + calculateLiquidityExternal(e, violator); + + require collateralValue > 0; + require liabilityValue > 0; + require collateralValue < liabilityValue; (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); @@ -133,21 +133,6 @@ rule checkLiquidation_maxYieldGreater { assert maxYield >= maxRepay; - - // assert false; - - // require liquidityCollateralValue < liquidityLiabilityValue; - // assert maxYield > maxRepay; -} - -rule debug_calculate_liquidity { - env e; - address account; - uint256 calculatedCollateral; - uint256 calculatedLiability; - (calculatedCollateral, calculatedLiability) = calculateLiquidityExternal(e, account); - assert calculatedCollateral == ghost_collateral; - assert calculatedLiability == ghost_liability; } rule debugCheckLiquidation { @@ -168,7 +153,6 @@ rule alwaysRevert { checkLiquidation@withrevert(e, liquidator, violator, collateral); satisfy !lastReverted; - } rule loadVaultSanity { From 00768fc40fbda77f837bd165c75a53f287ff669a Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 5 Apr 2024 12:46:48 +0100 Subject: [PATCH 028/152] Fix balanceOf linking --- .../CER-162-CheckLiquidation.conf | 12 ++++++------ certora/harness/LiquidationHarness.sol | 1 + .../CER-162-CheckLiquidation.spec | 4 ---- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf index 236c5a58..81f66218 100644 --- a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf +++ b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf @@ -3,6 +3,7 @@ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", "src/EVault/shared/lib/SafeERC20Lib.sol", + "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", // "src/EVault/shared/types/Types.sol", "src/EVault/modules/Liquidation.sol", "certora/harness/LiquidationHarness.sol" @@ -17,12 +18,11 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - // "rule": [ - // "loadVaultSanity" - // // "checkLiquidation_maxYieldGreater" - // // "debugCheckLiquidation" - // ], - // "coverage_info" : "advanced", + "rule": [ + "checkLiquidation_maxYieldGreater" + // "debugCheckLiquidation" + ], + "coverage_info" : "advanced", "parametric_contracts": ["LiquidationHarness"], "optimistic_loop": true, "loop_iter": "2", diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/LiquidationHarness.sol index 233c01b8..b5a08bc0 100644 --- a/certora/harness/LiquidationHarness.sol +++ b/certora/harness/LiquidationHarness.sol @@ -6,6 +6,7 @@ import "../../src/EVault/modules/Liquidation.sol"; import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; import "../../src/interfaces/IPriceOracle.sol"; import {IERC20} from "../../src/EVault/IEVault.sol"; +import {ERC20} from "../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; contract LiquidationHarness is Liquidation { // VaultCache vaultCache_; diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index 68d36cb8..ec483e49 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -127,10 +127,6 @@ rule checkLiquidation_maxYieldGreater { (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); - // If this works try to work this assumption "backwards" - require maxYield > 0; - require maxRepay > 0; - assert maxYield >= maxRepay; } From 89e20723a6c94e9eac991dac98ce88a3ff55afbf Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 5 Apr 2024 16:12:09 +0100 Subject: [PATCH 029/152] Update spec in liquidate. Refine revert conditions --- .../CER-162-CheckLiquidation.conf | 2 +- .../CER-163-Liquidate.conf | 1 + certora/harness/LiquidationHarness.sol | 4 ++ .../CER-163-Liquidate.spec | 44 +++++++++++++++++-- 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf index 81f66218..1ae3fcae 100644 --- a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf +++ b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf @@ -2,8 +2,8 @@ "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "src/EVault/shared/lib/SafeERC20Lib.sol", "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "src/EVault/shared/lib/SafeERC20Lib.sol", // "src/EVault/shared/types/Types.sol", "src/EVault/modules/Liquidation.sol", "certora/harness/LiquidationHarness.sol" diff --git a/certora/conf/CER-161-Liquidation/CER-163-Liquidate.conf b/certora/conf/CER-161-Liquidation/CER-163-Liquidate.conf index e16d1dcb..6df2511d 100644 --- a/certora/conf/CER-161-Liquidation/CER-163-Liquidate.conf +++ b/certora/conf/CER-161-Liquidation/CER-163-Liquidate.conf @@ -2,6 +2,7 @@ "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", "src/EVault/modules/Liquidation.sol", "certora/harness/LiquidationHarness.sol" ], diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/LiquidationHarness.sol index b5a08bc0..83a83827 100644 --- a/certora/harness/LiquidationHarness.sol +++ b/certora/harness/LiquidationHarness.sol @@ -72,6 +72,10 @@ contract LiquidationHarness is Liquidation { return controllers.length == 1 && controllers[0] == address(this); } + function vaultIsController(address account) external view returns (bool) { + return IEVC(evc).isControllerEnabled(account, address(this)); + } + function calculateLiquidationExt( VaultCache memory vaultCache, address liquidator, diff --git a/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec b/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec index 3d6e2016..89532843 100644 --- a/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec +++ b/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec @@ -40,6 +40,15 @@ works: */ methods { + + // IPriceOracle + function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); + function _.getQuotes(uint256 amount, address base, address quote) external => CVLGetQuotes(amount, base, quote) expect (uint256, uint256); + function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; + + // Workaround for lack of ability to summarize metadata + function Cache.loadVault() internal returns (Liquidation.VaultCache memory) => CVLLoadVault(); + function _.requireVaultStatusCheck() external => NONDET; function _.requireAccountAndVaultStatusCheck(address account) external => NONDET; function _.calculateDTokenAddress() internal => NONDET; @@ -49,7 +58,36 @@ methods { function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; function vaultIsOnlyController(address account) external returns (bool) envfree; + function vaultIsController(address account) external returns (bool) envfree; function ProxyUtils.metadata() internal returns (address, address, address)=> NONDET; + + // IERC20 + function _.name() external => DISPATCHER(true); + function _.symbol() external => DISPATCHER(true); + function _.decimals() external => DISPATCHER(true); + function _.totalSupply() external => DISPATCHER(true); + function _.balanceOf(address) external => DISPATCHER(true); + function _.allowance(address,address) external => DISPATCHER(true); + function _.approve(address,uint256) external => DISPATCHER(true); + function _.transfer(address,uint256) external => DISPATCHER(true); + function _.transferFrom(address,address,uint256) external => DISPATCHER(true); +} + +function CVLGetQuote(uint256 amount, address base, address quote) returns uint256 { + uint256 out; + return out; +} + +function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint256, uint256) { + uint256 bidOut; + uint256 askOut; + return (bidOut, askOut); +} + +function CVLLoadVault() returns Liquidation.VaultCache { + Liquidation.VaultCache vaultCache; + require vaultCache.oracle != 0; + return vaultCache; } rule calculateLiquidation_setViolator { @@ -82,7 +120,7 @@ rule liquidate_mustRevert { bool recognizedCollateral = isRecognizedCollateralExt(collateral); bool enabledCollateral = isCollateralEnabledExt(violator, collateral); bool violatorStatusCheckDeferred = isAccountStatusCheckDeferredExt(violator); - bool vaultControlsLiquidator = vaultIsOnlyController(liquidator); + bool vaultControlsLiquidator = vaultIsController(liquidator); bool vaultControlsViolator = vaultIsOnlyController(violator); bool oracleConfigured = vaultCacheOracleConfigured(e); @@ -91,9 +129,9 @@ rule liquidate_mustRevert { assert !selfLiquidation; assert recognizedCollateral; assert enabledCollateral; - // assert vaultControlsLiquidator; // VIOLATED + assert vaultControlsLiquidator; assert vaultControlsViolator; assert !violatorStatusCheckDeferred; - // assert oracleConfigured; + assert oracleConfigured; } \ No newline at end of file From 04758c1a970240544a27ef4d98ae7d79674be9ff Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 8 Apr 2024 10:58:28 +0100 Subject: [PATCH 030/152] mainly cleanup --- .../CER-162-CheckLiquidation.conf | 10 +-- .../CER-162-CheckLiquidation.spec | 75 ++++++------------- .../CER-163-Liquidate.spec | 16 ++-- 3 files changed, 36 insertions(+), 65 deletions(-) diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf index 1ae3fcae..9ef0c23b 100644 --- a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf +++ b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf @@ -18,11 +18,11 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "rule": [ - "checkLiquidation_maxYieldGreater" - // "debugCheckLiquidation" - ], - "coverage_info" : "advanced", + // "rule": [ + // "checkLiquidation_maxYieldGreater" + // // "debugCheckLiquidation" + // ], + // "coverage_info" : "advanced", "parametric_contracts": ["LiquidationHarness"], "optimistic_loop": true, "loop_iter": "2", diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index ec483e49..28544696 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -29,27 +29,21 @@ checkLiquidation must revert if: using SafeERC20Lib as safeERC20; methods { - // IPriceOracle - function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); - function _.getQuotes(uint256 amount, address base, address quote) external => CVLGetQuotes(amount, base, quote) expect (uint256, uint256); + // envfree function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; - function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; - + function vaultIsOnlyController(address account) external returns (bool) envfree; function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; - // function Cache.initVaultCache(Liquidation.VaultCache memory vaultCache) internal returns (bool) => NONDET; - // function LiquidityUtils.calculateLiquidity( - // Liquidation.VaultCache memory vaultCache, - // address account, - // address[] memory collaterals, - // Liquidation.LTVType ltvType - // ) internal returns (uint256, uint256) => calcLiquidity(account, collaterals, ltvType); - + // function ProxyUtils.metadata() internal returns (address, address, address)=> NONDET; - // Workaround for lack of ability to summarize metadata function Cache.loadVault() internal returns (Liquidation.VaultCache memory) => CVLLoadVault(); + // IPriceOracle + function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); + function _.getQuotes(uint256 amount, address base, address quote) external => CVLGetQuotes(amount, base, quote) expect (uint256, uint256); + + // IERC20 function _.name() external => DISPATCHER(true); function _.symbol() external => DISPATCHER(true); @@ -126,37 +120,8 @@ rule checkLiquidation_maxYieldGreater { require collateralValue < liabilityValue; (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); - - assert maxYield >= maxRepay; - -} - -rule debugCheckLiquidation { - env e; - address violator; - Liquidation.VaultCache vaultCache = loadVaultExt(e); - - Liquidation.Assets owed = getCurrentOwedExt(e, vaultCache, violator); - // satisfy !isZero(owed); - satisfy owed > 0; -} - -rule alwaysRevert { - env e; - address liquidator; - address violator; - address collateral; - - checkLiquidation@withrevert(e, liquidator, violator, collateral); - satisfy !lastReverted; -} - -rule loadVaultSanity { - env e; - // require oracleAddress != 0; - Liquidation.VaultCache vaultCache = loadVaultExt(e); - // validateOracleExt(e, vaultCache); - assert vaultCache.oracle != 0; + assert maxRepay == maxYield => maxRepay == 0; + assert maxRepay > 0 => maxRepay < maxYield; } rule checkLiquidation_mustRevert { @@ -166,17 +131,21 @@ rule checkLiquidation_mustRevert { address collateral; uint256 maxRepay; uint256 maxYield; - (maxRepay, maxYield) = checkLiquidation@withrevert(e, liquidator, violator, collateral); - bool reverted = lastReverted; - + bool selfLiquidate = liquidator == violator; bool badCollateral = !isRecognizedCollateralExt(collateral); - bool notEnabledCollateral = !isCollateralEnabledExt(violator, collateral); + bool enabledCollateral = isCollateralEnabledExt(violator, collateral); + bool vaultControlsViolator = vaultIsOnlyController(violator); bool violatorStatusCheckDeferred = isAccountStatusCheckDeferredExt(violator); + bool oracleConfigured = vaultCacheOracleConfigured(e); + + (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); - assert selfLiquidate || - badCollateral || - notEnabledCollateral || - violatorStatusCheckDeferred => reverted; + assert !selfLiquidate; + assert !badCollateral; + assert enabledCollateral; + assert vaultControlsViolator; + assert !violatorStatusCheckDeferred; + assert oracleConfigured; } \ No newline at end of file diff --git a/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec b/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec index 89532843..f213f7b0 100644 --- a/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec +++ b/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec @@ -40,13 +40,20 @@ works: */ methods { + // envfree + function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; + function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; + function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; + function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; + function vaultIsOnlyController(address account) external returns (bool) envfree; + function vaultIsController(address account) external returns (bool) envfree; // IPriceOracle function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); function _.getQuotes(uint256 amount, address base, address quote) external => CVLGetQuotes(amount, base, quote) expect (uint256, uint256); - function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; // Workaround for lack of ability to summarize metadata + // function ProxyUtils.metadata() internal returns (address, address, address)=> NONDET; function Cache.loadVault() internal returns (Liquidation.VaultCache memory) => CVLLoadVault(); function _.requireVaultStatusCheck() external => NONDET; @@ -54,12 +61,6 @@ methods { function _.calculateDTokenAddress() internal => NONDET; function EVCClient.EVCRequireStatusChecks(address account) internal => NONDET; function _.validateAndCallHook(Liquidation.Flags hookedOps, uint32 operation, address caller) internal => NONDET; - function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; - function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; - function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; - function vaultIsOnlyController(address account) external returns (bool) envfree; - function vaultIsController(address account) external returns (bool) envfree; - function ProxyUtils.metadata() internal returns (address, address, address)=> NONDET; // IERC20 function _.name() external => DISPATCHER(true); @@ -126,6 +127,7 @@ rule liquidate_mustRevert { liquidate(e, violator, collateral, repayAssets, minYieldBalance); // TODO liquidate operation not disabled + // TODO amount of collateral to be seized is less than the desired amount of assert !selfLiquidation; assert recognizedCollateral; assert enabledCollateral; From 5ffe69714e26d814b118c4fb066d7c960a07890c Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 5 Jun 2024 14:31:03 +0100 Subject: [PATCH 031/152] Balance forwarder working (Note, during rebase deleted some debugging changes in Liquidation.sol) --- certora/conf/CER-131-Vault.conf | 18 ++- .../CER-162-CheckLiquidation.conf | 2 - certora/specs/CER-131-Vault.spec | 134 +++++++++++++++++- src/EVault/modules/Liquidation.sol | 9 -- 4 files changed, 139 insertions(+), 24 deletions(-) diff --git a/certora/conf/CER-131-Vault.conf b/certora/conf/CER-131-Vault.conf index 723d5a9e..6e6a998e 100644 --- a/certora/conf/CER-131-Vault.conf +++ b/certora/conf/CER-131-Vault.conf @@ -2,23 +2,29 @@ "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "src/EVault/modules/Vault.sol" + "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "src/EVault/modules/Vault.sol", + "certora/harness/VaultHarness.sol" ], - "link" : [ - "Vault:evc=EthereumVaultConnector", + "link": [ + "VaultHarness:evc=EthereumVaultConnector", ], - "verify": "Vault:certora/specs/CER-131-Vault.spec", + "verify": "VaultHarness:certora/specs/CER-131-Vault.spec", "solc": "solc8.23", - "msg": "Vault benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "rule": [ + "balance_forwarding_called_deposit" + ], + "coverage_info" : "advanced", "parametric_contracts": ["Vault"], + "optimistic_loop": true, + "loop_iter": "2", "solc_via_ir": true, "solc_optimize": "10000", "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, - "prover_version" : "master" // need for dev fix } \ No newline at end of file diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf index 9ef0c23b..c031d3c2 100644 --- a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf +++ b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf @@ -3,8 +3,6 @@ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", - "src/EVault/shared/lib/SafeERC20Lib.sol", - // "src/EVault/shared/types/Types.sol", "src/EVault/modules/Liquidation.sol", "certora/harness/LiquidationHarness.sol" ], diff --git a/certora/specs/CER-131-Vault.spec b/certora/specs/CER-131-Vault.spec index 8e876605..e18ab13a 100644 --- a/certora/specs/CER-131-Vault.spec +++ b/certora/specs/CER-131-Vault.spec @@ -100,15 +100,135 @@ This operation affects: - total balance of the underlying assets held by the vault */ methods { - function _.requireVaultStatusCheck() external => NONDET; - function _.requireAccountAndVaultStatusCheck(address account) external => NONDET; - function _.calculateDTokenAddress() internal => NONDET; - function EVCClient.EVCRequireStatusChecks(address account) internal => NONDET; + // Track if a check was scheduled + function EVCClient.EVCRequireStatusChecks(address account) internal => CVLRequireStatusCheck(account); + + // Track if balance forwarder hook is called + function BalanceUtils.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal returns (bool) => + CVLCalledBalanceForwarder(account, newAccountBalance); + + // TypesLib -- in practice these cause vacuity errors without summaries + function _.toAssets(uint256 amount) internal => + CVLToAssets(amount) expect (uint112); + function _.toShares(uint256 amount) internal => + CVLToShares(amount) expect (uint112); + function _.toOwed(uint256 amount) internal => + CVLToOwed(amount) expect (uint144); + + // Workaround for lack of ability to summarize metadata + function Cache.loadVault() internal returns (Vault.VaultCache memory) => CVLLoadVault(); + function Cache.updateVault() internal returns (Vault.VaultCache memory) => CVLLoadVault(); + + // IERC20 + function _.name() external => DISPATCHER(true); + function _.symbol() external => DISPATCHER(true); + function _.decimals() external => DISPATCHER(true); + function _.totalSupply() external => DISPATCHER(true); + function _.balanceOf(address) external => DISPATCHER(true); + function _.allowance(address,address) external => DISPATCHER(true); + function _.approve(address,uint256) external => DISPATCHER(true); + function _.transfer(address,uint256) external => DISPATCHER(true); + function _.transferFrom(address,address,uint256) external => DISPATCHER(true); +} + +function CVLToAssets(uint256 amount) returns uint112 { + return require_uint112(amount); +} + +function CVLToShares(uint256 amount) returns uint112 { + return require_uint112(amount); +} + +function CVLToOwed(uint256 amount) returns uint144 { + return require_uint144(amount); } -rule sanity (method f) { +persistent ghost bool calledStatusCheck; +function CVLRequireStatusCheck(address account) { + calledStatusCheck = true; +} + +function CVLLoadVault() returns Vault.VaultCache { + Vault.VaultCache vaultCache; + require vaultCache.oracle != 0; + return vaultCache; +} + +definition isHookOperation(method f) returns bool = + f.selector == sig:Vault.deposit(uint256, address).selector || + f.selector == sig:Vault.mint(uint256, address).selector || + f.selector == sig:Vault.withdraw(uint256, address, address).selector || + f.selector == sig:Vault.redeem(uint256, address, address).selector || + f.selector == sig:Vault.skim(uint256, address).selector; + +rule status_checks_scheduled (method f) filtered { f -> + isHookOperation(f) +}{ env e; calldataarg args; + require calledStatusCheck == false; f(e, args); - satisfy true; -} \ No newline at end of file + assert calledStatusCheck; +} + +persistent ghost bool calledForwarder; +function CVLCalledBalanceForwarder(address account, uint256 newAccountBalance) returns bool { + calledForwarder = true; + return true; +} + +// Need to summarize UserStorage bit mask stuff +// including BalanceFowrarder and balance + +rule balance_forwarding_called_deposit { + env e; + uint256 amount; + address receiver; + + uint256 balance; + bool forwarderEnabled; + + require !calledForwarder; + + // deposit returns early if amount is 0. + require amount > 0; + require amount != 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + + // if balance forwarding is enabled and OP is not disabled + balance, forwarderEnabled = getBalanceAndForwarderExt(e, receiver); + + require forwarderEnabled; + require !isDepositDisabled(e); + deposit(e, amount, receiver); + + // // balance forwarding hook is called + assert calledForwarder; +} + +rule balance_forwarding_called_mint{ + env e; + uint256 amount; + address receiver; + + uint256 balance; + bool forwarderEnabled; + + require !calledForwarder; + + // mint returns early if amount is 0. + require amount > 0; + + // if balance forwarding is enabled and OP is not disabled + balance, forwarderEnabled = getBalanceAndForwarderExt(e, receiver); + require forwarderEnabled; + require !isMintDisabled(e); + mint(e, amount, receiver); + + // balance forwarding hook is called + assert calledForwarder; + +} + +// NOTE: disabled ops do not cause a revert. They cause the call +// to act like a NOP in callHook (in initOperation). So we could prove +// that the actual call does not happen by writing a "hook" on invokeTarget \ No newline at end of file diff --git a/src/EVault/modules/Liquidation.sol b/src/EVault/modules/Liquidation.sol index 571c1260..fe53e893 100644 --- a/src/EVault/modules/Liquidation.sol +++ b/src/EVault/modules/Liquidation.sol @@ -155,15 +155,6 @@ abstract contract LiquidationModule is ILiquidation, BalanceUtils, LiquidityUtil return liqCache; } -<<<<<<< HEAD -======= - uint256 liabilityValue = liqCache.owed.toUint(); - if (address(vaultCache.asset) != vaultCache.unitOfAccount) { - liabilityValue = - vaultCache.oracle.getQuote(liabilityValue, address(vaultCache.asset), vaultCache.unitOfAccount); - } - ->>>>>>> 9763b68 (Can get past loadVault sanity with summary) uint256 maxRepayValue = liabilityValue; uint256 maxYieldValue = maxRepayValue * 1e18 / discountFactor; From 05f0b5f1e06f13ed44897907f05802e53dfcacc2 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 9 Apr 2024 11:46:22 +0100 Subject: [PATCH 032/152] BalanceForwarding rule --- certora/conf/CER-131-Vault.conf | 6 +- .../CER-162-CheckLiquidation.conf | 3 +- certora/specs/CER-131-Vault.spec | 88 ++++++++++++++++--- .../CER-162-CheckLiquidation.spec | 15 ++-- 4 files changed, 87 insertions(+), 25 deletions(-) diff --git a/certora/conf/CER-131-Vault.conf b/certora/conf/CER-131-Vault.conf index 6e6a998e..72f9e5eb 100644 --- a/certora/conf/CER-131-Vault.conf +++ b/certora/conf/CER-131-Vault.conf @@ -15,9 +15,9 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "rule": [ - "balance_forwarding_called_deposit" - ], + // "rule": [ + // "balance_forwarding_called_deposit" + // ], "coverage_info" : "advanced", "parametric_contracts": ["Vault"], "optimistic_loop": true, diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf index c031d3c2..f2703c79 100644 --- a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf +++ b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf @@ -28,6 +28,5 @@ "solc_optimize": "10000", "rule_sanity": "basic", "function_finder_mode" : "relaxed", - "finder_friendly_optimizer" : false, - "prover_version" : "master" // need for dev fix + "finder_friendly_optimizer" : true, } \ No newline at end of file diff --git a/certora/specs/CER-131-Vault.spec b/certora/specs/CER-131-Vault.spec index e18ab13a..2db8e785 100644 --- a/certora/specs/CER-131-Vault.spec +++ b/certora/specs/CER-131-Vault.spec @@ -177,56 +177,116 @@ function CVLCalledBalanceForwarder(address account, uint256 newAccountBalance) r return true; } -// Need to summarize UserStorage bit mask stuff -// including BalanceFowrarder and balance +// NOTE: these rules are not parametric because they need +// to constrain to the case that the result is nonzero. rule balance_forwarding_called_deposit { env e; uint256 amount; address receiver; + uint256 result; uint256 balance; bool forwarderEnabled; + require !calledForwarder; - // deposit returns early if amount is 0. - require amount > 0; - require amount != 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; - // if balance forwarding is enabled and OP is not disabled balance, forwarderEnabled = getBalanceAndForwarderExt(e, receiver); require forwarderEnabled; require !isDepositDisabled(e); - deposit(e, amount, receiver); + result = deposit(e, amount, receiver); // // balance forwarding hook is called - assert calledForwarder; + assert result !=0 => calledForwarder; } -rule balance_forwarding_called_mint{ +rule balance_forwarding_called_mint { env e; uint256 amount; address receiver; + uint256 result; uint256 balance; bool forwarderEnabled; require !calledForwarder; - // mint returns early if amount is 0. - require amount > 0; - // if balance forwarding is enabled and OP is not disabled balance, forwarderEnabled = getBalanceAndForwarderExt(e, receiver); require forwarderEnabled; require !isMintDisabled(e); - mint(e, amount, receiver); + result = mint(e, amount, receiver); + + // balance forwarding hook is called + assert result != 0 => calledForwarder; +} + +rule balance_forwarding_called_withdraw { + env e; + uint256 amount; + address receiver; + address owner; + uint256 result; + + uint256 balance; + bool forwarderEnabled; + + require !calledForwarder; + + // if balance forwarding is enabled and OP is not disabled + balance, forwarderEnabled = getBalanceAndForwarderExt(e, owner); + require forwarderEnabled; + require !isWithdrawDisabled(e); + result = withdraw(e, amount, receiver, owner); // balance forwarding hook is called - assert calledForwarder; + assert result !=0 => calledForwarder; +} +rule balance_forwarding_called_redeem { + env e; + uint256 amount; + address receiver; + address owner; + uint256 result; + + uint256 balance; + bool forwarderEnabled; + + require !calledForwarder; + + // if balance forwarding is enabled and OP is not disabled + balance, forwarderEnabled = getBalanceAndForwarderExt(e, owner); + require forwarderEnabled; + require !isRedeemDisabled(e); + result = redeem(e, amount, receiver, owner); + + // balance forwarding hook is called + assert result != 0 => calledForwarder; +} + +rule balance_forwarding_called_skim { + env e; + uint256 amount; + address receiver; + uint256 result; + + uint256 balance; + bool forwarderEnabled; + + require !calledForwarder; + + // if balance forwarding is enabled and OP is not disabled + balance, forwarderEnabled = getBalanceAndForwarderExt(e, receiver); + require forwarderEnabled; + require !isSkimDisabled(e); + result = skim(e, amount, receiver); + + // balance forwarding hook is called + assert result != 0 => calledForwarder; } // NOTE: disabled ops do not cause a revert. They cause the call diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index 28544696..b37da70e 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -26,7 +26,8 @@ checkLiquidation must revert if: - price oracle is not configured */ -using SafeERC20Lib as safeERC20; +// using SafeERC20Lib as safeERC20; +using ERC20 as erc20; methods { // envfree @@ -35,9 +36,9 @@ methods { function vaultIsOnlyController(address account) external returns (bool) envfree; function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; - // function ProxyUtils.metadata() internal returns (address, address, address)=> NONDET; + function ProxyUtils.metadata() internal returns (address, address, address)=> NONDET; // Workaround for lack of ability to summarize metadata - function Cache.loadVault() internal returns (Liquidation.VaultCache memory) => CVLLoadVault(); + // function Cache.loadVault() internal returns (Liquidation.VaultCache memory) => CVLLoadVault(); // IPriceOracle function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); @@ -70,7 +71,7 @@ function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint ghost address oracleAddress; ghost address unitOfAccount; function CVLProxyMetadata() returns (address, address, address) { - return (safeERC20, oracleAddress, unitOfAccount); + return (erc20, oracleAddress, unitOfAccount); } function CVLLoadVault() returns Liquidation.VaultCache { @@ -89,6 +90,7 @@ rule checkLiquidation_healthy() { Liquidation.VaultCache vaultCache; require vaultCache.oracle != 0; + require oracleAddress != 0; address[] collaterals = getCollateralsExt(e, violator); @@ -115,13 +117,13 @@ rule checkLiquidation_maxYieldGreater { (collateralValue, liabilityValue) = calculateLiquidityExternal(e, violator); + require oracleAddress != 0; require collateralValue > 0; require liabilityValue > 0; require collateralValue < liabilityValue; (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); - assert maxRepay == maxYield => maxRepay == 0; - assert maxRepay > 0 => maxRepay < maxYield; + assert maxRepay > 0 => maxRepay <= maxYield; } rule checkLiquidation_mustRevert { @@ -132,6 +134,7 @@ rule checkLiquidation_mustRevert { uint256 maxRepay; uint256 maxYield; + require oracleAddress != 0; bool selfLiquidate = liquidator == violator; bool badCollateral = !isRecognizedCollateralExt(collateral); bool enabledCollateral = isCollateralEnabledExt(violator, collateral); From 2061011c336b7c1e3293bebea027c06a8fb261ab Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 9 Apr 2024 11:57:25 +0100 Subject: [PATCH 033/152] Add missing harness --- certora/harness/VaultHarness.sol | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 certora/harness/VaultHarness.sol diff --git a/certora/harness/VaultHarness.sol b/certora/harness/VaultHarness.sol new file mode 100644 index 00000000..67010c49 --- /dev/null +++ b/certora/harness/VaultHarness.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; +import "../../src/EVault/modules/Vault.sol"; + +contract VaultHarness is Vault { + constructor(Integrations memory integrations) Vault(integrations) {} + function isOperationDisabledExt(uint32 operation) public returns (bool) { + // This is based on the check in callHook. + VaultCache memory vaultCache = updateVault(); + return vaultCache.hookedOps.isNotSet(operation); + } + + function isDepositDisabled() public returns (bool) { + return isOperationDisabledExt(OP_DEPOSIT); + } + + function isMintDisabled() public returns (bool) { + return isOperationDisabledExt(OP_MINT); + } + + function isWithdrawDisabled() public returns (bool) { + return isOperationDisabledExt(OP_WITHDRAW); + } + + function isRedeemDisabled() public returns (bool) { + return isOperationDisabledExt(OP_REDEEM); + } + + function isSkimDisabled() public returns (bool) { + return isOperationDisabledExt(OP_SKIM); + } + + + function getBalanceAndForwarderExt(address account) public returns (Shares, bool) { + return vaultStorage.users[account].getBalanceAndBalanceForwarder(); + } + + +} \ No newline at end of file From ff7c97802dd8c9739551976874615960c5c62695 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 9 Apr 2024 13:32:40 +0100 Subject: [PATCH 034/152] Add ProxyUtils.metadata summary --- certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index b37da70e..b02770e1 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -36,7 +36,7 @@ methods { function vaultIsOnlyController(address account) external returns (bool) envfree; function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; - function ProxyUtils.metadata() internal returns (address, address, address)=> NONDET; + function ProxyUtils.metadata() internal returns (address, address, address)=> CVLProxyMetadata(); // Workaround for lack of ability to summarize metadata // function Cache.loadVault() internal returns (Liquidation.VaultCache memory) => CVLLoadVault(); From ee2f901cfb206e8c75b15a8e90e7b1528a7d3d3b Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 10 Apr 2024 11:44:07 +0100 Subject: [PATCH 035/152] Mainly BalanceForwarder. Also Vault, CheckLiquidation --- certora/conf/CER-131-Vault.conf | 2 +- .../CER-162-CheckLiquidation.conf | 8 +-- certora/conf/CER-182-BalanceForwarder.conf | 28 ++++++++ certora/specs/CER-131-Vault.spec | 21 ------ .../CER-162-CheckLiquidation.spec | 30 +++++++-- certora/specs/CER-182-BalanceForwarder.spec | 66 +++++++++++++++++++ 6 files changed, 125 insertions(+), 30 deletions(-) create mode 100644 certora/conf/CER-182-BalanceForwarder.conf create mode 100644 certora/specs/CER-182-BalanceForwarder.spec diff --git a/certora/conf/CER-131-Vault.conf b/certora/conf/CER-131-Vault.conf index 72f9e5eb..1a1e6904 100644 --- a/certora/conf/CER-131-Vault.conf +++ b/certora/conf/CER-131-Vault.conf @@ -19,7 +19,7 @@ // "balance_forwarding_called_deposit" // ], "coverage_info" : "advanced", - "parametric_contracts": ["Vault"], + "parametric_contracts": ["VaultHarness"], "optimistic_loop": true, "loop_iter": "2", "solc_via_ir": true, diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf index f2703c79..908714d1 100644 --- a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf +++ b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf @@ -16,10 +16,10 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - // "rule": [ - // "checkLiquidation_maxYieldGreater" - // // "debugCheckLiquidation" - // ], + "rule": [ + "checkLiquidation_healthy" + // "debugCheckLiquidation" + ], // "coverage_info" : "advanced", "parametric_contracts": ["LiquidationHarness"], "optimistic_loop": true, diff --git a/certora/conf/CER-182-BalanceForwarder.conf b/certora/conf/CER-182-BalanceForwarder.conf new file mode 100644 index 00000000..a9034c10 --- /dev/null +++ b/certora/conf/CER-182-BalanceForwarder.conf @@ -0,0 +1,28 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "src/EVault/modules/BalanceForwarder.sol", + "certora/harness/VaultHarness.sol" + ], + "link": [ + "BalanceForwarder:evc=EthereumVaultConnector", + ], + "verify": "BalanceForwarder:certora/specs/CER-182-BalanceForwarder.spec", + "solc": "solc8.23", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": ["-smt_bitVectorTheory", "true"], + // "coverage_info" : "advanced", + "parametric_contracts": ["BalanceForwarder"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, +} \ No newline at end of file diff --git a/certora/specs/CER-131-Vault.spec b/certora/specs/CER-131-Vault.spec index 2db8e785..d3814196 100644 --- a/certora/specs/CER-131-Vault.spec +++ b/certora/specs/CER-131-Vault.spec @@ -107,14 +107,6 @@ methods { function BalanceUtils.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal returns (bool) => CVLCalledBalanceForwarder(account, newAccountBalance); - // TypesLib -- in practice these cause vacuity errors without summaries - function _.toAssets(uint256 amount) internal => - CVLToAssets(amount) expect (uint112); - function _.toShares(uint256 amount) internal => - CVLToShares(amount) expect (uint112); - function _.toOwed(uint256 amount) internal => - CVLToOwed(amount) expect (uint144); - // Workaround for lack of ability to summarize metadata function Cache.loadVault() internal returns (Vault.VaultCache memory) => CVLLoadVault(); function Cache.updateVault() internal returns (Vault.VaultCache memory) => CVLLoadVault(); @@ -131,18 +123,6 @@ methods { function _.transferFrom(address,address,uint256) external => DISPATCHER(true); } -function CVLToAssets(uint256 amount) returns uint112 { - return require_uint112(amount); -} - -function CVLToShares(uint256 amount) returns uint112 { - return require_uint112(amount); -} - -function CVLToOwed(uint256 amount) returns uint144 { - return require_uint144(amount); -} - persistent ghost bool calledStatusCheck; function CVLRequireStatusCheck(address account) { calledStatusCheck = true; @@ -188,7 +168,6 @@ rule balance_forwarding_called_deposit { uint256 balance; bool forwarderEnabled; - require !calledForwarder; diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index b02770e1..851a6924 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -40,6 +40,13 @@ methods { // Workaround for lack of ability to summarize metadata // function Cache.loadVault() internal returns (Liquidation.VaultCache memory) => CVLLoadVault(); + function LiquidityUtils.calculateLiquidity( + Liquidation.VaultCache memory vaultCache, + address account, + address[] memory collaterals, + Liquidation.LTVType ltvType + ) internal returns (uint256, uint256) => calcLiquidity(account, collaterals, ltvType); + // IPriceOracle function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); function _.getQuotes(uint256 amount, address base, address quote) external => CVLGetQuotes(amount, base, quote) expect (uint256, uint256); @@ -80,6 +87,13 @@ function CVLLoadVault() returns Liquidation.VaultCache { return vaultCache; } +persistent ghost uint256 dummy_collateral; +persistent ghost uint256 dummy_liquidity; +function calcLiquidity(address account, address[] collaterals, Liquidation.LTVType ltvType) returns (uint256, uint256) { + // unconstrained but same value for same returns + return (dummy_collateral, dummy_liquidity); +} + rule checkLiquidation_healthy() { env e; address liquidator; @@ -92,14 +106,22 @@ rule checkLiquidation_healthy() { require vaultCache.oracle != 0; require oracleAddress != 0; - address[] collaterals = getCollateralsExt(e, violator); + // address[] collaterals = getCollateralsExt(e, violator); + + // uint256 liquidityCollateralValue = getLiquidityValue(e, violator, vaultCache, collaterals); + // uint256 liquidityLiabilityValue = getLiabilityValue(e, violator, vaultCache, collaterals); + + // uint256 liquidityCollateralValue; + // uint256 liquidityLiabilityValue; + // (liquidityCollateralValue, liquidityLiabilityValue) = + // calculateLiquidityExternal(e, violator); - uint256 liquidityCollateralValue = getLiquidityValue(e, violator, vaultCache, collaterals); - uint256 liquidityLiabilityValue = getLiabilityValue(e, violator, vaultCache, collaterals); + require dummy_collateral > 0; + require dummy_liquidity > 0; + require dummy_collateral > dummy_liquidity; (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); - require liquidityCollateralValue >= liquidityLiabilityValue; assert maxRepay == 0; assert maxYield == 0; } diff --git a/certora/specs/CER-182-BalanceForwarder.spec b/certora/specs/CER-182-BalanceForwarder.spec new file mode 100644 index 00000000..5d61748e --- /dev/null +++ b/certora/specs/CER-182-BalanceForwarder.spec @@ -0,0 +1,66 @@ +/* +CER-182 / Verify BalanceForwarder + +EVK-16 enableBalanceForwarder: +If balance tracker specified, enableBalanceForwarder enables the balance +forwarding to the balance tracker for the authenticated account. The balance +tracker hook should be invoked with current balance of the account. + +EVK-17 disableBalanceForwarder +disables the balance forwarding to the +balance tracker for the authenticated account. The balance tracker +hook should be invoked with 0 as the balance of the account. +*/ + +using EthereumVaultConnector as evc; + +methods { + // function _.isBalanceForwarderEnabled(BalanceForwarder.UserStorage storage self) internal => CVLIsBalanceForwarderEnabled(self.data) expect (bool); +} + +function actualCaller(env e) returns address { + if(e.msg.sender == evc) { + address onBehalf; + bool unused; + onBehalf, unused = evc.getCurrentOnBehalfOfAccount(e, 0); + return onBehalf; + } else { + return e.msg.sender; + } +} + + +// NOTE: Unused currently +ghost mapping(uint256 => bool) ghost_balanceForwarderFlag; +function CVLIsBalanceForwarderEnabled(uint256 userStorage_data) returns bool { + return ghost_balanceForwarderFlag[userStorage_data]; +} + +rule enableBalanceForwarder { + address account; + env e1; + env e2; + // require e1.msg.sender != evc; + // require e1.msg.sender == account; + require actualCaller(e1) == account; + enableBalanceForwarder(e1); + assert balanceForwarderEnabled(e2, account); +} + +rule disableBalanceForwarder { + address account; + env e1; + env e2; + // require e1.msg.sender != evc; + // require e1.msg.sender == account; + require actualCaller(e1) == account; + disableBalanceForwarder(e1); + assert !balanceForwarderEnabled(e2, account); +} + +rule sanity (method f) { + env e; + calldataarg args; + f(e, args); + satisfy true; +} \ No newline at end of file From 53d9433fe5ef2da39fd112c1d4687ebec26074e1 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 11 Apr 2024 09:41:27 +0100 Subject: [PATCH 036/152] RiskManager revert cases. CheckLiquidation healthy --- certora/conf/CER-155-RiskManager.conf | 29 +++ .../CER-162-CheckLiquidation.conf | 7 +- certora/harness/RiskManagerHarness.sol | 26 +++ certora/specs/CER-155-RiskManager.spec | 183 ++++++++++++++++++ .../CER-162-CheckLiquidation.spec | 50 +++-- certora/specs/reformatScrap | 63 ++++++ 6 files changed, 330 insertions(+), 28 deletions(-) create mode 100644 certora/conf/CER-155-RiskManager.conf create mode 100644 certora/harness/RiskManagerHarness.sol create mode 100644 certora/specs/CER-155-RiskManager.spec create mode 100644 certora/specs/reformatScrap diff --git a/certora/conf/CER-155-RiskManager.conf b/certora/conf/CER-155-RiskManager.conf new file mode 100644 index 00000000..32df1d17 --- /dev/null +++ b/certora/conf/CER-155-RiskManager.conf @@ -0,0 +1,29 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "src/EVault/modules/RiskManager.sol", + "certora/harness/RiskManagerHarness.sol", + ], + "link": [ + "RiskManagerHarness:evc=EthereumVaultConnector", + ], + "verify": "RiskManagerHarness:certora/specs/CER-155-RiskManager.spec", + "solc": "solc8.23", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + // "rule": [ + // ], + "coverage_info" : "advanced", + "parametric_contracts": ["RiskManagerHarness"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : true, +} \ No newline at end of file diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf index 908714d1..41e36b77 100644 --- a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf +++ b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf @@ -16,8 +16,13 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "prover_args": [ + "-depth 0", + "-smt_nonLinearArithmetic true", + "-adaptiveSolverConfig false" + ], "rule": [ - "checkLiquidation_healthy" + "checkLiquidation_maxYieldGreater" // "debugCheckLiquidation" ], // "coverage_info" : "advanced", diff --git a/certora/harness/RiskManagerHarness.sol b/certora/harness/RiskManagerHarness.sol new file mode 100644 index 00000000..bf5d1aaa --- /dev/null +++ b/certora/harness/RiskManagerHarness.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import "../../src/EVault/modules/RiskManager.sol"; +import "../../src/EVault/shared/types/Types.sol"; +import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; +import "../../src/interfaces/IPriceOracle.sol"; +import {IERC20} from "../../src/EVault/IEVault.sol"; +import {ERC20} from "../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; + +contract RiskManagerHarness is RiskManager { + constructor(Integrations memory integrations) RiskManager(integrations) {} + + function getCollateralsExt(address account) public view returns (address[] memory) { + return getCollaterals(account); + } + + function vaultIsOnlyController(address account) external view returns (bool) { + address[] memory controllers = IEVC(evc).getControllers(account); + return controllers.length == 1 && controllers[0] == address(this); + } + + function vaultCacheOracleConfigured() external returns (bool) { + return address(loadVault().oracle) != address(0); + } + +} \ No newline at end of file diff --git a/certora/specs/CER-155-RiskManager.spec b/certora/specs/CER-155-RiskManager.spec new file mode 100644 index 00000000..8c0faa1b --- /dev/null +++ b/certora/specs/CER-155-RiskManager.spec @@ -0,0 +1,183 @@ +/* +//----------------------------------------------------------------------------- +// accountLiquidity +//----------------------------------------------------------------------------- +For a given account, accountLiquidity calculates and returns the sum of risk +adjusted values of enabled, and accepted, collaterals and the value of +liability. + +If liquidation parameter is true, the risk adjusted value of collateral is the +value of collateral multiplied by the current LTV factor calculated using the +original LTV factor, the target LTV and ramp duration (assuming the LTV factor +changes linearly from the original LTV to the target LTV in ramp duration time). + +If liquidation parameter is false, the risk adjusted value of collateral is the +value of collateral multiplied by the target LTV. + +accountLiquidity must revert if: + - liability vault is not enabled as the only controller of the account + - price oracle is not configured + +//----------------------------------------------------------------------------- +// accountLiquidityFull +//----------------------------------------------------------------------------- +For a given account, accountLiquidityFull calculates and returns the risk +adjusted values of enabled, and accepted, collaterals and the value of +liability. + +If liquidation parameter is true, the risk adjusted value of collateral is the +value of collateral multiplied by the current LTV factor calculated using the +original LTV factor, the target LTV and ramp duration (assuming the LTV factor +changes linearly from the original LTV to the target LTV in ramp duration time). + +If liquidation parameter is false, the risk adjusted value of collateral is the +value of collateral multiplied by the target LTV. + +accountLiquidityFull must revert if: + - liability vault is not enabled as the only controller of the account + - price oracle is not configured + + +//----------------------------------------------------------------------------- +// checkAccountStatus +//----------------------------------------------------------------------------- +If the authenticated account does not have an outstanding liability, +disableController disables liability vault as a controller for the authenticated +account. disableController must revert if the authenticated account has an +outstanding liability. + +If account healthy, considering the risk adjusted value of collateral is the +value of collateral multiplied by the target LTV, checkAccountStatus returns the +selector of itself. + +checkAccountStatus must revert if: + - not called by the EVC + - not called when checks in progress + - account unhealthy + +//----------------------------------------------------------------------------- +// checkVaultStatus +//----------------------------------------------------------------------------- +If vault status is valid, checkVaultStatus updates the interest rate, clears the +snapshot (if created) and returns the selector of itself. + +The interest rate is updated by calling into the configured interest rate model +contract and cannot exceed the MAX_ALLOWED_INTEREST_RATE. If the interest rate +model contract is not configured OR it reverts, the interest rate must remain +unchanged. + +checkVaultStatus must revert if: + - not called by the EVC + - not called when checks in progress + - vault status invalid + */ + +using ERC20 as erc20; +using EthereumVaultConnector as evc; +methods { + // envfree + function vaultIsOnlyController(address account) external returns (bool) envfree; + + function ProxyUtils.metadata() internal returns (address, address, address)=> CVLProxyMetadata(); + + // IPriceOracle + function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); + function _.getQuotes(uint256 amount, address base, address quote) external => CVLGetQuotes(amount, base, quote) expect (uint256, uint256); + + // IERC20 + function _.name() external => DISPATCHER(true); + function _.symbol() external => DISPATCHER(true); + function _.decimals() external => DISPATCHER(true); + function _.totalSupply() external => DISPATCHER(true); + function _.balanceOf(address) external => DISPATCHER(true); + function _.allowance(address,address) external => DISPATCHER(true); + function _.approve(address,uint256) external => DISPATCHER(true); + function _.transfer(address,uint256) external => DISPATCHER(true); + function _.transferFrom(address,address,uint256) external => DISPATCHER(true); +} + + +function CVLGetQuote(uint256 amount, address base, address quote) returns uint256 { + uint256 out; + return out; +} + +function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint256, uint256) { + uint256 bidOut; + uint256 askOut; + return (bidOut, askOut); +} + +ghost address oracleAddress; +ghost address unitOfAccount; +function CVLProxyMetadata() returns (address, address, address) { + return (erc20, oracleAddress, unitOfAccount); +} + +function CVLLoadVault() returns RiskManager.VaultCache { + RiskManager.VaultCache vaultCache; + require vaultCache.oracle != 0; + return vaultCache; +} + +rule accountLiquidityMustRevert { + env e; + calldataarg args; + address account; + bool liquidation; + + require oracleAddress != 0; + + bool vaultControlsAccount = vaultIsOnlyController(account); + bool oracleConfigured = vaultCacheOracleConfigured(e); + + accountLiquidity(e, account, liquidation); + // If we did not revert then: ... + assert vaultControlsAccount; + assert oracleConfigured; + +} + +rule accountLiquidityFullMustRevert { + env e; + calldataarg args; + address account; + bool liquidation; + + require oracleAddress != 0; + + bool vaultControlsAccount = vaultIsOnlyController(account); + bool oracleConfigured = vaultCacheOracleConfigured(e); + + accountLiquidityFull(e, account, liquidation); + // If we did not revert then: ... + assert vaultControlsAccount; + assert oracleConfigured; +} + +rule checkAccountStatusMustRevert { + env e; + calldataarg args; + address account; + address[] collaterals; + bool checksInProgress = evc.areChecksInProgress(e); + checkAccountStatus(e, account, collaterals); + assert e.msg.sender == evc; + assert checksInProgress; +} + +rule checkVaultStatusMustRevert { + env e; + calldataarg args; + bool checksInProgress = evc.areChecksInProgress(e); + checkVaultStatus(e); + assert e.msg.sender == evc; + assert checksInProgress; +} + +rule sanity (method f) { + env e; + calldataarg args; + f(e, args); + satisfy true; +} \ No newline at end of file diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index 851a6924..725e8fc2 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -40,12 +40,12 @@ methods { // Workaround for lack of ability to summarize metadata // function Cache.loadVault() internal returns (Liquidation.VaultCache memory) => CVLLoadVault(); - function LiquidityUtils.calculateLiquidity( - Liquidation.VaultCache memory vaultCache, - address account, - address[] memory collaterals, - Liquidation.LTVType ltvType - ) internal returns (uint256, uint256) => calcLiquidity(account, collaterals, ltvType); + // function LiquidityUtils.calculateLiquidity( + // Liquidation.VaultCache memory vaultCache, + // address account, + // address[] memory collaterals, + // Liquidation.LTVType ltvType + // ) internal returns (uint256, uint256) => calcLiquidity(account, collaterals, ltvType); // IPriceOracle function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); @@ -64,17 +64,20 @@ methods { function _.transferFrom(address,address,uint256) external => DISPATCHER(true); } -function CVLGetQuote(uint256 amount, address base, address quote) returns uint256 { - uint256 out; - return out; -} +ghost CVLGetQuotes_bidOut(uint256, address, address) returns uint256; +ghost CVLGetQuotes_askOut(uint256, address, address) returns uint256; + +ghost CVLGetQuote(uint256, address, address) returns uint256; + function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint256, uint256) { - uint256 bidOut; - uint256 askOut; - return (bidOut, askOut); + return ( + CVLGetQuotes_bidOut(amount, base, quote), + CVLGetQuotes_askOut(amount, base, quote) + ); } + ghost address oracleAddress; ghost address unitOfAccount; function CVLProxyMetadata() returns (address, address, address) { @@ -102,23 +105,16 @@ rule checkLiquidation_healthy() { uint256 maxRepay; uint256 maxYield; - Liquidation.VaultCache vaultCache; - require vaultCache.oracle != 0; require oracleAddress != 0; - // address[] collaterals = getCollateralsExt(e, violator); - - // uint256 liquidityCollateralValue = getLiquidityValue(e, violator, vaultCache, collaterals); - // uint256 liquidityLiabilityValue = getLiabilityValue(e, violator, vaultCache, collaterals); - - // uint256 liquidityCollateralValue; - // uint256 liquidityLiabilityValue; - // (liquidityCollateralValue, liquidityLiabilityValue) = - // calculateLiquidityExternal(e, violator); + uint256 liquidityCollateralValue; + uint256 liquidityLiabilityValue; + (liquidityCollateralValue, liquidityLiabilityValue) = + calculateLiquidityExternal(e, violator); - require dummy_collateral > 0; - require dummy_liquidity > 0; - require dummy_collateral > dummy_liquidity; + require liquidityCollateralValue > liquidityLiabilityValue; + // require liquidityCollateralValue > 0; + // require liquidityLiabilityValue > 0; (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); diff --git a/certora/specs/reformatScrap b/certora/specs/reformatScrap new file mode 100644 index 00000000..5595b9f0 --- /dev/null +++ b/certora/specs/reformatScrap @@ -0,0 +1,63 @@ + +accountLiquidity +For a given account, accountLiquidity calculates and returns the sum of risk +adjusted values of enabled, and accepted, collaterals and the value of +liability. + +If liquidation parameter is true, the risk adjusted value of collateral is the +value of collateral multiplied by the current LTV factor calculated using the +original LTV factor, the target LTV and ramp duration (assuming the LTV factor +changes linearly from the original LTV to the target LTV in ramp duration time). + +If liquidation parameter is false, the risk adjusted value of collateral is the +value of collateral multiplied by the target LTV. + +accountLiquidity must revert if: + - liability vault is not enabled as the only controller of the account + - price oracle is not configured + +accountLiquidityFull +For a given account, accountLiquidityFull calculates and returns the risk +adjusted values of enabled, and accepted, collaterals and the value of +liability. + +If liquidation parameter is true, the risk adjusted value of collateral is the +value of collateral multiplied by the current LTV factor calculated using the +original LTV factor, the target LTV and ramp duration (assuming the LTV factor +changes linearly from the original LTV to the target LTV in ramp duration time). + +If liquidation parameter is false, the risk adjusted value of collateral is the +value of collateral multiplied by the target LTV. + +accountLiquidityFull must revert if: + - liability vault is not enabled as the only controller of the account + - price oracle is not configured + +checkAccountStatus +If the authenticated account does not have an outstanding liability, +disableController disables liability vault as a controller for the authenticated +account. disableController must revert if the authenticated account has an +outstanding liability. + +If account healthy, considering the risk adjusted value of collateral is the +value of collateral multiplied by the target LTV, checkAccountStatus returns the +selector of itself. + +checkAccountStatus must revert if: + - not called by the EVC + - not called when checks in progress + - account unhealthy + +checkVaultStatus +If vault status is valid, checkVaultStatus updates the interest rate, clears the +snapshot (if created) and returns the selector of itself. + +The interest rate is updated by calling into the configured interest rate model +contract and cannot exceed the MAX_ALLOWED_INTEREST_RATE. If the interest rate +model contract is not configured OR it reverts, the interest rate must remain +unchanged. + +checkVaultStatus must revert if: + - not called by the EVC + - not called when checks in progress + - vault status invalid \ No newline at end of file From 05d91cb7c6d23070add9a0c2b00d87e8806b9b6f Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 11 Apr 2024 17:28:00 +0100 Subject: [PATCH 037/152] Mainly fix quotes summary, SMT config --- certora/conf/CER-155-RiskManager.conf | 10 ++++- .../CER-162-CheckLiquidation.conf | 3 +- certora/harness/RiskManagerHarness.sol | 1 + certora/specs/CER-155-RiskManager.spec | 41 +++++++++++++------ .../CER-162-CheckLiquidation.spec | 13 +++--- 5 files changed, 46 insertions(+), 22 deletions(-) diff --git a/certora/conf/CER-155-RiskManager.conf b/certora/conf/CER-155-RiskManager.conf index 32df1d17..9c347239 100644 --- a/certora/conf/CER-155-RiskManager.conf +++ b/certora/conf/CER-155-RiskManager.conf @@ -15,8 +15,14 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - // "rule": [ - // ], + "rule": [ + "liquidations_equal_for_one" + ], + "prover_args": [ + "-depth 3", + "-smt_nonLinearArithmetic true", + "-adaptiveSolverConfig false" + ], "coverage_info" : "advanced", "parametric_contracts": ["RiskManagerHarness"], "optimistic_loop": true, diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf index 41e36b77..efc56dd8 100644 --- a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf +++ b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf @@ -17,13 +17,12 @@ "forge-std=lib/forge-std/src" ], "prover_args": [ - "-depth 0", + "-depth 10", "-smt_nonLinearArithmetic true", "-adaptiveSolverConfig false" ], "rule": [ "checkLiquidation_maxYieldGreater" - // "debugCheckLiquidation" ], // "coverage_info" : "advanced", "parametric_contracts": ["LiquidationHarness"], diff --git a/certora/harness/RiskManagerHarness.sol b/certora/harness/RiskManagerHarness.sol index bf5d1aaa..8ed58ecd 100644 --- a/certora/harness/RiskManagerHarness.sol +++ b/certora/harness/RiskManagerHarness.sol @@ -23,4 +23,5 @@ contract RiskManagerHarness is RiskManager { return address(loadVault().oracle) != address(0); } + } \ No newline at end of file diff --git a/certora/specs/CER-155-RiskManager.spec b/certora/specs/CER-155-RiskManager.spec index 8c0faa1b..22361300 100644 --- a/certora/specs/CER-155-RiskManager.spec +++ b/certora/specs/CER-155-RiskManager.spec @@ -77,6 +77,7 @@ using EthereumVaultConnector as evc; methods { // envfree function vaultIsOnlyController(address account) external returns (bool) envfree; + // function getCollateralsExt(address account) public returns (address[] memory) envfree; function ProxyUtils.metadata() internal returns (address, address, address)=> CVLProxyMetadata(); @@ -96,16 +97,12 @@ methods { function _.transferFrom(address,address,uint256) external => DISPATCHER(true); } - -function CVLGetQuote(uint256 amount, address base, address quote) returns uint256 { - uint256 out; - return out; -} - +ghost CVLGetQuote(uint256, address, address) returns uint256; function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint256, uint256) { - uint256 bidOut; - uint256 askOut; - return (bidOut, askOut); + return ( + CVLGetQuote(amount, base, quote), + CVLGetQuote(amount, base, quote) + ); } ghost address oracleAddress; @@ -114,10 +111,28 @@ function CVLProxyMetadata() returns (address, address, address) { return (erc20, oracleAddress, unitOfAccount); } -function CVLLoadVault() returns RiskManager.VaultCache { - RiskManager.VaultCache vaultCache; - require vaultCache.oracle != 0; - return vaultCache; +rule liquidations_equal_for_one { + env e; + calldataarg args; + address account; + bool liquidation; + + uint256 collateralValue; + uint256 liabilityValue; + + require oracleAddress != 0; + require unitOfAccount != 0; + + address[] collaterals = getCollateralsExt(e, account); + require collaterals.length == 1; + (collateralValue, liabilityValue) = accountLiquidity(e, account, liquidation); + address[] collaterals_full; + uint256[] collateralValues; + uint256 liabilityValue_full; + (collaterals_full, collateralValues, liabilityValue_full) = accountLiquidityFull(e, account, liquidation); + assert collateralValue == collateralValues[1]; + assert liabilityValue == liabilityValue_full; + } rule accountLiquidityMustRevert { diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index 725e8fc2..e1eb4c2d 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -64,26 +64,29 @@ methods { function _.transferFrom(address,address,uint256) external => DISPATCHER(true); } -ghost CVLGetQuotes_bidOut(uint256, address, address) returns uint256; -ghost CVLGetQuotes_askOut(uint256, address, address) returns uint256; +// ghost CVLGetQuotes_bidOut(uint256, address, address) returns uint256; +// ghost CVLGetQuotes_askOut(uint256, address, address) returns uint256; ghost CVLGetQuote(uint256, address, address) returns uint256; function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint256, uint256) { return ( - CVLGetQuotes_bidOut(amount, base, quote), - CVLGetQuotes_askOut(amount, base, quote) + CVLGetQuote(amount, base, quote), + CVLGetQuote(amount, base, quote) ); } - ghost address oracleAddress; ghost address unitOfAccount; function CVLProxyMetadata() returns (address, address, address) { return (erc20, oracleAddress, unitOfAccount); } +// CRITICAL: [main] ERROR ALWAYS - Found errors in certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec: +// CRITICAL: [main] ERROR ALWAYS - Error in spec file (CER-162-CheckLiquidation.spec:87:1): The type VaultCache is not allowed in a return position of a ghost functio +// ghost CVLLoadVaultUninterp() returns Liquidation.VaultCache; + function CVLLoadVault() returns Liquidation.VaultCache { Liquidation.VaultCache vaultCache; require vaultCache.oracle != 0; From 5d2027fe144a0842a59a361155b0a3bdb292b244 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 15 Apr 2024 15:04:38 +0100 Subject: [PATCH 038/152] Mainly getCollateralValue_borrowing_lower --- certora/conf/CER-155-RiskManager.conf | 4 +- .../CER-162-CheckLiquidation.conf | 2 +- certora/harness/LiquidationHarness.sol | 10 +++ certora/harness/RiskManagerHarness.sol | 4 ++ certora/specs/CER-155-RiskManager.spec | 36 ++++++++++- .../CER-162-CheckLiquidation.spec | 46 +++++++++++++- .../CER-163-Liquidate.spec | 4 +- certora/specs/reformatScrap | 63 ------------------- 8 files changed, 99 insertions(+), 70 deletions(-) delete mode 100644 certora/specs/reformatScrap diff --git a/certora/conf/CER-155-RiskManager.conf b/certora/conf/CER-155-RiskManager.conf index 9c347239..2ed060a9 100644 --- a/certora/conf/CER-155-RiskManager.conf +++ b/certora/conf/CER-155-RiskManager.conf @@ -16,10 +16,10 @@ "forge-std=lib/forge-std/src" ], "rule": [ - "liquidations_equal_for_one" + "ltv_borrowing_lower" ], "prover_args": [ - "-depth 3", + "-depth 0", "-smt_nonLinearArithmetic true", "-adaptiveSolverConfig false" ], diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf index efc56dd8..6960d9e6 100644 --- a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf +++ b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf @@ -22,7 +22,7 @@ "-adaptiveSolverConfig false" ], "rule": [ - "checkLiquidation_maxYieldGreater" + "getCollateralValue_borrowing_lower" ], // "coverage_info" : "advanced", "parametric_contracts": ["LiquidationHarness"], diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/LiquidationHarness.sol index 83a83827..ecf09278 100644 --- a/certora/harness/LiquidationHarness.sol +++ b/certora/harness/LiquidationHarness.sol @@ -90,5 +90,15 @@ contract LiquidationHarness is Liquidation { return getCurrentOwed(vaultCache, violator).toAssetsUp(); } + function getCollateralValueExt(VaultCache memory vaultCache, address account, address collateral, LTVType ltvType) + external + view + returns (uint256 value) { + return getCollateralValue(vaultCache, account, collateral, ltvType); + } + + function getLTVConfig(address collateral) external view returns (LTVConfig memory) { + return vaultStorage.ltvLookup[collateral]; + } } \ No newline at end of file diff --git a/certora/harness/RiskManagerHarness.sol b/certora/harness/RiskManagerHarness.sol index 8ed58ecd..f3dd1cab 100644 --- a/certora/harness/RiskManagerHarness.sol +++ b/certora/harness/RiskManagerHarness.sol @@ -23,5 +23,9 @@ contract RiskManagerHarness is RiskManager { return address(loadVault().oracle) != address(0); } + function getLTVConfig(address collateral) external view returns (LTVConfig memory) { + return vaultStorage.ltvLookup[collateral]; + } + } \ No newline at end of file diff --git a/certora/specs/CER-155-RiskManager.spec b/certora/specs/CER-155-RiskManager.spec index 22361300..09e1e304 100644 --- a/certora/specs/CER-155-RiskManager.spec +++ b/certora/specs/CER-155-RiskManager.spec @@ -77,7 +77,8 @@ using EthereumVaultConnector as evc; methods { // envfree function vaultIsOnlyController(address account) external returns (bool) envfree; - // function getCollateralsExt(address account) public returns (address[] memory) envfree; + function getCollateralsExt(address account) external returns (address[] memory) envfree; + function getLTVConfig(address collateral) external returns (RiskManager.LTVConfig memory) envfree; function ProxyUtils.metadata() internal returns (address, address, address)=> CVLProxyMetadata(); @@ -111,6 +112,8 @@ function CVLProxyMetadata() returns (address, address, address) { return (erc20, oracleAddress, unitOfAccount); } + +// timeout rule liquidations_equal_for_one { env e; calldataarg args; @@ -132,7 +135,38 @@ rule liquidations_equal_for_one { (collaterals_full, collateralValues, liabilityValue_full) = accountLiquidityFull(e, account, liquidation); assert collateralValue == collateralValues[1]; assert liabilityValue == liabilityValue_full; +} + +function ltvs_configuration_assumption(address account) returns bool { + address[] collaterals = getCollateralsExt(account); + uint256 i; + return i < collaterals.length => + (getLTVConfig(collaterals[i]).targetLTV < + getLTVConfig(collaterals[i]).originalLTV); +} + +// timeout +rule ltv_borrowing_lower { + env e; + calldataarg args; + address account; + + require ltvs_configuration_assumption(account); + + uint256 collateralValue_liquidation; + uint256 liabilityValue_liquidation; + (collateralValue_liquidation, liabilityValue_liquidation) = accountLiquidity(e, account, true); + + uint256 collateralValue_borrowing; + uint256 liabilityValue_borrowing; + (collateralValue_borrowing, liabilityValue_borrowing) = accountLiquidity(e, account, false); + + require collateralValue_liquidation > 0; + require collateralValue_borrowing > 0; + + assert collateralValue_liquidation >= collateralValue_borrowing; + } rule accountLiquidityMustRevert { diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index e1eb4c2d..d8ef91ea 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -67,7 +67,14 @@ methods { // ghost CVLGetQuotes_bidOut(uint256, address, address) returns uint256; // ghost CVLGetQuotes_askOut(uint256, address, address) returns uint256; -ghost CVLGetQuote(uint256, address, address) returns uint256; +ghost CVLGetQuote(uint256, address, address) returns uint256 { + // The total value returned by the oracle is assumed < 2**230-1. + // There will be overflows without an upper bound on this number. + // (For example, it must be less than 2**242-1 to avoid overflow in + // LTVConfig.mul) + axiom forall uint256 x. forall address y. forall address z. + CVLGetQuote(x, y, z) < 1725436586697640946858688965569256363112777243042596638790631055949823; +} function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint256, uint256) { @@ -172,4 +179,41 @@ rule checkLiquidation_mustRevert { assert !violatorStatusCheckDeferred; assert oracleConfigured; +} + +function LTVConfigAssumptions(env e, address collateral) returns bool { + Liquidation.LTVConfig ltvConfig = getLTVConfig(e, collateral); + // the LTV should be less than 1. Here 1e4 is the scaling factor. + // So we assume governance sets these GT 1. + bool targetLTVLessOne = ltvConfig.targetLTV < 10000; + bool originalLTVLessOne = ltvConfig.originalLTV < 10000; + bool target_less_original = ltvConfig.targetLTV < ltvConfig.originalLTV; + mathint timeRemaining = ltvConfig.targetTimestamp - e.block.timestamp; + return targetLTVLessOne && + originalLTVLessOne && + target_less_original && + require_uint32(timeRemaining) < ltvConfig.rampDuration; +} + +rule getCollateralValue_borrowing_lower { + env e; + Liquidation.VaultCache vaultCache; + address account; + address collateral; + + // Not enough. Counterexample: + // https://prover.certora.com/output/65266/83f92155749f42d98cadd58754511ebe/?anonymousKey=b3bbd7dcc5b9cec2dbc6104528456fd908ad9057 + // require getLTVConfig(e, collateral).targetLTV < getLTVConfig(e, collateral).originalLTV; + // Need to also assume about ramp duration + require LTVConfigAssumptions(e, collateral); + + uint256 collateralValue_borrowing = getCollateralValueExt(e, vaultCache, account, collateral, Liquidation.LTVType.BORROWING); + + uint256 collateralValue_liquidation = getCollateralValueExt(e, vaultCache, account, collateral, Liquidation.LTVType.LIQUIDATION); + + require collateralValue_liquidation > 0; + require collateralValue_borrowing > 0; + + assert collateralValue_borrowing <= collateralValue_liquidation; + } \ No newline at end of file diff --git a/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec b/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec index f213f7b0..24a9869b 100644 --- a/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec +++ b/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec @@ -135,5 +135,5 @@ rule liquidate_mustRevert { assert vaultControlsViolator; assert !violatorStatusCheckDeferred; assert oracleConfigured; - -} \ No newline at end of file +} + diff --git a/certora/specs/reformatScrap b/certora/specs/reformatScrap deleted file mode 100644 index 5595b9f0..00000000 --- a/certora/specs/reformatScrap +++ /dev/null @@ -1,63 +0,0 @@ - -accountLiquidity -For a given account, accountLiquidity calculates and returns the sum of risk -adjusted values of enabled, and accepted, collaterals and the value of -liability. - -If liquidation parameter is true, the risk adjusted value of collateral is the -value of collateral multiplied by the current LTV factor calculated using the -original LTV factor, the target LTV and ramp duration (assuming the LTV factor -changes linearly from the original LTV to the target LTV in ramp duration time). - -If liquidation parameter is false, the risk adjusted value of collateral is the -value of collateral multiplied by the target LTV. - -accountLiquidity must revert if: - - liability vault is not enabled as the only controller of the account - - price oracle is not configured - -accountLiquidityFull -For a given account, accountLiquidityFull calculates and returns the risk -adjusted values of enabled, and accepted, collaterals and the value of -liability. - -If liquidation parameter is true, the risk adjusted value of collateral is the -value of collateral multiplied by the current LTV factor calculated using the -original LTV factor, the target LTV and ramp duration (assuming the LTV factor -changes linearly from the original LTV to the target LTV in ramp duration time). - -If liquidation parameter is false, the risk adjusted value of collateral is the -value of collateral multiplied by the target LTV. - -accountLiquidityFull must revert if: - - liability vault is not enabled as the only controller of the account - - price oracle is not configured - -checkAccountStatus -If the authenticated account does not have an outstanding liability, -disableController disables liability vault as a controller for the authenticated -account. disableController must revert if the authenticated account has an -outstanding liability. - -If account healthy, considering the risk adjusted value of collateral is the -value of collateral multiplied by the target LTV, checkAccountStatus returns the -selector of itself. - -checkAccountStatus must revert if: - - not called by the EVC - - not called when checks in progress - - account unhealthy - -checkVaultStatus -If vault status is valid, checkVaultStatus updates the interest rate, clears the -snapshot (if created) and returns the selector of itself. - -The interest rate is updated by calling into the configured interest rate model -contract and cannot exceed the MAX_ALLOWED_INTEREST_RATE. If the interest rate -model contract is not configured OR it reverts, the interest rate must remain -unchanged. - -checkVaultStatus must revert if: - - not called by the EVC - - not called when checks in progress - - vault status invalid \ No newline at end of file From 991dffc840f743c25b0f92e7a3a97146027c537e Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 15 Apr 2024 15:11:05 +0100 Subject: [PATCH 039/152] edit comments --- .../specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index d8ef91ea..acf984cb 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -123,8 +123,6 @@ rule checkLiquidation_healthy() { calculateLiquidityExternal(e, violator); require liquidityCollateralValue > liquidityLiabilityValue; - // require liquidityCollateralValue > 0; - // require liquidityLiabilityValue > 0; (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); @@ -201,10 +199,10 @@ rule getCollateralValue_borrowing_lower { address account; address collateral; + // require getLTVConfig(e, collateral).targetLTV < getLTVConfig(e, collateral).originalLTV; // Not enough. Counterexample: // https://prover.certora.com/output/65266/83f92155749f42d98cadd58754511ebe/?anonymousKey=b3bbd7dcc5b9cec2dbc6104528456fd908ad9057 - // require getLTVConfig(e, collateral).targetLTV < getLTVConfig(e, collateral).originalLTV; - // Need to also assume about ramp duration + // Need to also assume about ramp duration and the LTVs require LTVConfigAssumptions(e, collateral); uint256 collateralValue_borrowing = getCollateralValueExt(e, vaultCache, account, collateral, Liquidation.LTVType.BORROWING); From 6625d9c3c58e61fa0347c1b4a5e28da4a0d077db Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 16 Apr 2024 14:30:49 +0100 Subject: [PATCH 040/152] Mainly move assumptions from CheckLiquidation to RiskManager --- certora/conf/CER-155-RiskManager.conf | 4 +- .../CER-162-CheckLiquidation.conf | 1 - certora/specs/CER-155-RiskManager.spec | 45 ++++++++++++++----- .../CER-162-CheckLiquidation.spec | 8 ++-- 4 files changed, 40 insertions(+), 18 deletions(-) diff --git a/certora/conf/CER-155-RiskManager.conf b/certora/conf/CER-155-RiskManager.conf index 2ed060a9..659c10a1 100644 --- a/certora/conf/CER-155-RiskManager.conf +++ b/certora/conf/CER-155-RiskManager.conf @@ -17,13 +17,14 @@ ], "rule": [ "ltv_borrowing_lower" + // "liquidations_equal_for_one" ], "prover_args": [ "-depth 0", "-smt_nonLinearArithmetic true", "-adaptiveSolverConfig false" ], - "coverage_info" : "advanced", + // "coverage_info" : "advanced", "parametric_contracts": ["RiskManagerHarness"], "optimistic_loop": true, "loop_iter": "2", @@ -31,5 +32,4 @@ "solc_optimize": "10000", "rule_sanity": "basic", "function_finder_mode" : "relaxed", - "finder_friendly_optimizer" : true, } \ No newline at end of file diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf index 6960d9e6..8d282c3a 100644 --- a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf +++ b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf @@ -32,5 +32,4 @@ "solc_optimize": "10000", "rule_sanity": "basic", "function_finder_mode" : "relaxed", - "finder_friendly_optimizer" : true, } \ No newline at end of file diff --git a/certora/specs/CER-155-RiskManager.spec b/certora/specs/CER-155-RiskManager.spec index 09e1e304..3fa74fca 100644 --- a/certora/specs/CER-155-RiskManager.spec +++ b/certora/specs/CER-155-RiskManager.spec @@ -98,7 +98,15 @@ methods { function _.transferFrom(address,address,uint256) external => DISPATCHER(true); } -ghost CVLGetQuote(uint256, address, address) returns uint256; +ghost CVLGetQuote(uint256, address, address) returns uint256 { + // The total value returned by the oracle is assumed < 2**230-1. + // There will be overflows without an upper bound on this number. + // (For example, it must be less than 2**242-1 to avoid overflow in + // LTVConfig.mul) + axiom forall uint256 x. forall address y. forall address z. + CVLGetQuote(x, y, z) < 1725436586697640946858688965569256363112777243042596638790631055949823; +} + function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint256, uint256) { return ( CVLGetQuote(amount, base, quote), @@ -112,6 +120,26 @@ function CVLProxyMetadata() returns (address, address, address) { return (erc20, oracleAddress, unitOfAccount); } +// TODO refactor to avoid duplicating this. Need a way around type declaration +// for LTVConfig. +function LTVConfigAssumptions(env e, RiskManager.LTVConfig ltvConfig) returns bool { + bool targetLTVLessOne = ltvConfig.targetLTV < 10000; + bool originalLTVLessOne = ltvConfig.originalLTV < 10000; + bool target_less_original = ltvConfig.targetLTV < ltvConfig.originalLTV; + mathint timeRemaining = ltvConfig.targetTimestamp - e.block.timestamp; + return targetLTVLessOne && + originalLTVLessOne && + target_less_original && + require_uint32(timeRemaining) < ltvConfig.rampDuration; +} + +function ltvs_configuration_assumption(env e, address account) returns bool { + address[] collaterals = getCollateralsExt(account); + uint256 i; + return i < collaterals.length => + LTVConfigAssumptions(e, getLTVConfig(collaterals[i])); +} + // timeout rule liquidations_equal_for_one { @@ -125,6 +153,7 @@ rule liquidations_equal_for_one { require oracleAddress != 0; require unitOfAccount != 0; + require ltvs_configuration_assumption(e, account); address[] collaterals = getCollateralsExt(e, account); require collaterals.length == 1; @@ -137,22 +166,18 @@ rule liquidations_equal_for_one { assert liabilityValue == liabilityValue_full; } -function ltvs_configuration_assumption(address account) returns bool { - address[] collaterals = getCollateralsExt(account); - uint256 i; - return i < collaterals.length => - (getLTVConfig(collaterals[i]).targetLTV < - getLTVConfig(collaterals[i]).originalLTV); -} -// timeout +// cex: https://prover.certora.com/output/40748/8c5b2eea4cc9452391b6739c357dbecd/?anonymousKey=a81b45f19e0a01b08f32ec2e7182479d7d5ab4ec rule ltv_borrowing_lower { env e; calldataarg args; address account; - require ltvs_configuration_assumption(account); + // try this to get around timeout + require getCollateralsExt(account).length == 1; + + require ltvs_configuration_assumption(e, account); uint256 collateralValue_liquidation; uint256 liabilityValue_liquidation; diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index acf984cb..e3000d35 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -186,11 +186,10 @@ function LTVConfigAssumptions(env e, address collateral) returns bool { bool targetLTVLessOne = ltvConfig.targetLTV < 10000; bool originalLTVLessOne = ltvConfig.originalLTV < 10000; bool target_less_original = ltvConfig.targetLTV < ltvConfig.originalLTV; - mathint timeRemaining = ltvConfig.targetTimestamp - e.block.timestamp; + // mathint timeRemaining = ltvConfig.targetTimestamp - e.block.timestamp; return targetLTVLessOne && originalLTVLessOne && - target_less_original && - require_uint32(timeRemaining) < ltvConfig.rampDuration; + target_less_original; } rule getCollateralValue_borrowing_lower { @@ -199,10 +198,9 @@ rule getCollateralValue_borrowing_lower { address account; address collateral; - // require getLTVConfig(e, collateral).targetLTV < getLTVConfig(e, collateral).originalLTV; // Not enough. Counterexample: // https://prover.certora.com/output/65266/83f92155749f42d98cadd58754511ebe/?anonymousKey=b3bbd7dcc5b9cec2dbc6104528456fd908ad9057 - // Need to also assume about ramp duration and the LTVs + // Need to also assume about ramp duration require LTVConfigAssumptions(e, collateral); uint256 collateralValue_borrowing = getCollateralValueExt(e, vaultCache, account, collateral, Liquidation.LTVType.BORROWING); From 04c85dd5fbd25b204cfdd356735ea8ae5e1381c3 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 16 Apr 2024 14:34:50 +0100 Subject: [PATCH 041/152] fix LTVConfigAssumptions --- .../specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index e3000d35..89899f78 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -186,10 +186,11 @@ function LTVConfigAssumptions(env e, address collateral) returns bool { bool targetLTVLessOne = ltvConfig.targetLTV < 10000; bool originalLTVLessOne = ltvConfig.originalLTV < 10000; bool target_less_original = ltvConfig.targetLTV < ltvConfig.originalLTV; - // mathint timeRemaining = ltvConfig.targetTimestamp - e.block.timestamp; + mathint timeRemaining = ltvConfig.targetTimestamp - e.block.timestamp; return targetLTVLessOne && originalLTVLessOne && - target_less_original; + target_less_original && + require_uint32(timeRemaining) < ltvConfig.rampDuration; } rule getCollateralValue_borrowing_lower { From 26e74e69244f07b1670fbe823c8c7bb633ae9218 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 16 Apr 2024 15:01:52 +0100 Subject: [PATCH 042/152] Use loop bound on RiskManager borrowing_lower --- certora/specs/CER-155-RiskManager.spec | 8 +++++--- .../CER-161-Liquidation/CER-162-CheckLiquidation.spec | 9 +-------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/certora/specs/CER-155-RiskManager.spec b/certora/specs/CER-155-RiskManager.spec index 3fa74fca..2a9172f1 100644 --- a/certora/specs/CER-155-RiskManager.spec +++ b/certora/specs/CER-155-RiskManager.spec @@ -174,10 +174,12 @@ rule ltv_borrowing_lower { address account; - // try this to get around timeout - require getCollateralsExt(account).length == 1; - require ltvs_configuration_assumption(e, account); + // based on loop bound + address[] collaterals = getCollateralsExt(account); + require collaterals.length == 2; + require LTVConfigAssumptions(e, getLTVConfig(collaterals[0])); + require LTVConfigAssumptions(e, getLTVConfig(collaterals[1])); uint256 collateralValue_liquidation; uint256 liabilityValue_liquidation; diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index 89899f78..7efa3409 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -67,14 +67,7 @@ methods { // ghost CVLGetQuotes_bidOut(uint256, address, address) returns uint256; // ghost CVLGetQuotes_askOut(uint256, address, address) returns uint256; -ghost CVLGetQuote(uint256, address, address) returns uint256 { - // The total value returned by the oracle is assumed < 2**230-1. - // There will be overflows without an upper bound on this number. - // (For example, it must be less than 2**242-1 to avoid overflow in - // LTVConfig.mul) - axiom forall uint256 x. forall address y. forall address z. - CVLGetQuote(x, y, z) < 1725436586697640946858688965569256363112777243042596638790631055949823; -} +ghost CVLGetQuote(uint256, address, address) returns uint256; function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint256, uint256) { From e1ed525fa14d340449787ac3bedb7e0a1ba727a2 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 17 Apr 2024 13:02:36 +0100 Subject: [PATCH 043/152] Lots of refactoring --- certora/conf/CER-155-RiskManager.conf | 12 ++- .../CER-162-CheckLiquidation.conf | 18 ++-- certora/harness/BaseHarness.sol | 12 +++ certora/specs/Base.spec | 55 ++++++++++ certora/specs/CER-155-RiskManager.spec | 65 +---------- .../CER-162-CheckLiquidation.spec | 101 ++++-------------- 6 files changed, 110 insertions(+), 153 deletions(-) create mode 100644 certora/harness/BaseHarness.sol create mode 100644 certora/specs/Base.spec diff --git a/certora/conf/CER-155-RiskManager.conf b/certora/conf/CER-155-RiskManager.conf index 659c10a1..9b13d61a 100644 --- a/certora/conf/CER-155-RiskManager.conf +++ b/certora/conf/CER-155-RiskManager.conf @@ -4,6 +4,7 @@ "lib/ethereum-vault-connector/src/ExecutionContext.sol", "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", "src/EVault/modules/RiskManager.sol", + "certora/harness/BaseHarness.sol", "certora/harness/RiskManagerHarness.sol", ], "link": [ @@ -19,17 +20,20 @@ "ltv_borrowing_lower" // "liquidations_equal_for_one" ], + "parametric_contracts": ["RiskManagerHarness"], + "rule_sanity": "basic", + // "coverage_info" : "advanced", + // Performance tuning options below this line "prover_args": [ "-depth 0", "-smt_nonLinearArithmetic true", "-adaptiveSolverConfig false" ], - // "coverage_info" : "advanced", - "parametric_contracts": ["RiskManagerHarness"], + "function_finder_mode": "relaxed", "optimistic_loop": true, "loop_iter": "2", "solc_via_ir": true, "solc_optimize": "10000", - "rule_sanity": "basic", - "function_finder_mode" : "relaxed", + "smt_timeout": "7000", + "prover_version": "master" } \ No newline at end of file diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf index 8d282c3a..77593086 100644 --- a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf +++ b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf @@ -4,6 +4,7 @@ "lib/ethereum-vault-connector/src/ExecutionContext.sol", "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", "src/EVault/modules/Liquidation.sol", + "certora/harness/BaseHarness.sol", "certora/harness/LiquidationHarness.sol" ], "link": [ @@ -16,20 +17,23 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + // "rule": [ + // "getCollateralValue_borrowing_lower" + // ], + "parametric_contracts": ["LiquidationHarness"], + "rule_sanity": "basic", + // "coverage_info" : "advanced", + // Performance tuing options below this line "prover_args": [ "-depth 10", "-smt_nonLinearArithmetic true", "-adaptiveSolverConfig false" ], - "rule": [ - "getCollateralValue_borrowing_lower" - ], - // "coverage_info" : "advanced", - "parametric_contracts": ["LiquidationHarness"], + "function_finder_mode": "relaxed", "optimistic_loop": true, "loop_iter": "2", "solc_via_ir": true, "solc_optimize": "10000", - "rule_sanity": "basic", - "function_finder_mode" : "relaxed", + "smt_timeout": "7000", + "prover_version": "master" } \ No newline at end of file diff --git a/certora/harness/BaseHarness.sol b/certora/harness/BaseHarness.sol new file mode 100644 index 00000000..b1c803ad --- /dev/null +++ b/certora/harness/BaseHarness.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import "../../src/EVault/shared/Base.sol"; + +// This mainly exists so that Base.LTVConfig and other type declarations +// are available in CVL and can be used across specs for different modules + +contract BaseHarness is Base { + constructor(Integrations memory integrations) Base(integrations) {} +} \ No newline at end of file diff --git a/certora/specs/Base.spec b/certora/specs/Base.spec new file mode 100644 index 00000000..fbbb6207 --- /dev/null +++ b/certora/specs/Base.spec @@ -0,0 +1,55 @@ +using ERC20 as erc20; +using EthereumVaultConnector as evc; + +methods { + // IPriceOracle + function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); + function _.getQuotes(uint256 amount, address base, address quote) external => CVLGetQuotes(amount, base, quote) expect (uint256, uint256); + + // ProxyUtils + function ProxyUtils.metadata() internal returns (address, address, address)=> CVLProxyMetadata(); + + // IERC20 + function _.name() external => DISPATCHER(true); + function _.symbol() external => DISPATCHER(true); + function _.decimals() external => DISPATCHER(true); + function _.totalSupply() external => DISPATCHER(true); + function _.balanceOf(address) external => DISPATCHER(true); + function _.allowance(address,address) external => DISPATCHER(true); + function _.approve(address,uint256) external => DISPATCHER(true); + function _.transfer(address,uint256) external => DISPATCHER(true); + function _.transferFrom(address,address,uint256) external => DISPATCHER(true); +} + +ghost CVLGetQuote(uint256, address, address) returns uint256 { + // The total value returned by the oracle is assumed < 2**230-1. + // There will be overflows without an upper bound on this number. + // (For example, it must be less than 2**242-1 to avoid overflow in + // LTVConfig.mul) + axiom forall uint256 x. forall address y. forall address z. + CVLGetQuote(x, y, z) < 1725436586697640946858688965569256363112777243042596638790631055949823; +} + +function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint256, uint256) { + return ( + CVLGetQuote(amount, base, quote), + CVLGetQuote(amount, base, quote) + ); +} + +ghost address oracleAddress; +ghost address unitOfAccount; +function CVLProxyMetadata() returns (address, address, address) { + return (erc20, oracleAddress, unitOfAccount); +} + +function LTVConfigAssumptions(env e, BaseHarness.LTVConfig ltvConfig) returns bool { + bool targetLTVLessOne = ltvConfig.targetLTV < 10000; + bool originalLTVLessOne = ltvConfig.originalLTV < 10000; + bool target_less_original = ltvConfig.targetLTV < ltvConfig.originalLTV; + mathint timeRemaining = ltvConfig.targetTimestamp - e.block.timestamp; + return targetLTVLessOne && + originalLTVLessOne && + target_less_original && + require_uint32(timeRemaining) < ltvConfig.rampDuration; +} \ No newline at end of file diff --git a/certora/specs/CER-155-RiskManager.spec b/certora/specs/CER-155-RiskManager.spec index 2a9172f1..3254c31b 100644 --- a/certora/specs/CER-155-RiskManager.spec +++ b/certora/specs/CER-155-RiskManager.spec @@ -72,75 +72,16 @@ checkVaultStatus must revert if: - vault status invalid */ -using ERC20 as erc20; -using EthereumVaultConnector as evc; +import "Base.spec"; + methods { // envfree function vaultIsOnlyController(address account) external returns (bool) envfree; function getCollateralsExt(address account) external returns (address[] memory) envfree; function getLTVConfig(address collateral) external returns (RiskManager.LTVConfig memory) envfree; - function ProxyUtils.metadata() internal returns (address, address, address)=> CVLProxyMetadata(); - - // IPriceOracle - function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); - function _.getQuotes(uint256 amount, address base, address quote) external => CVLGetQuotes(amount, base, quote) expect (uint256, uint256); - - // IERC20 - function _.name() external => DISPATCHER(true); - function _.symbol() external => DISPATCHER(true); - function _.decimals() external => DISPATCHER(true); - function _.totalSupply() external => DISPATCHER(true); - function _.balanceOf(address) external => DISPATCHER(true); - function _.allowance(address,address) external => DISPATCHER(true); - function _.approve(address,uint256) external => DISPATCHER(true); - function _.transfer(address,uint256) external => DISPATCHER(true); - function _.transferFrom(address,address,uint256) external => DISPATCHER(true); -} - -ghost CVLGetQuote(uint256, address, address) returns uint256 { - // The total value returned by the oracle is assumed < 2**230-1. - // There will be overflows without an upper bound on this number. - // (For example, it must be less than 2**242-1 to avoid overflow in - // LTVConfig.mul) - axiom forall uint256 x. forall address y. forall address z. - CVLGetQuote(x, y, z) < 1725436586697640946858688965569256363112777243042596638790631055949823; -} - -function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint256, uint256) { - return ( - CVLGetQuote(amount, base, quote), - CVLGetQuote(amount, base, quote) - ); -} - -ghost address oracleAddress; -ghost address unitOfAccount; -function CVLProxyMetadata() returns (address, address, address) { - return (erc20, oracleAddress, unitOfAccount); } -// TODO refactor to avoid duplicating this. Need a way around type declaration -// for LTVConfig. -function LTVConfigAssumptions(env e, RiskManager.LTVConfig ltvConfig) returns bool { - bool targetLTVLessOne = ltvConfig.targetLTV < 10000; - bool originalLTVLessOne = ltvConfig.originalLTV < 10000; - bool target_less_original = ltvConfig.targetLTV < ltvConfig.originalLTV; - mathint timeRemaining = ltvConfig.targetTimestamp - e.block.timestamp; - return targetLTVLessOne && - originalLTVLessOne && - target_less_original && - require_uint32(timeRemaining) < ltvConfig.rampDuration; -} - -function ltvs_configuration_assumption(env e, address account) returns bool { - address[] collaterals = getCollateralsExt(account); - uint256 i; - return i < collaterals.length => - LTVConfigAssumptions(e, getLTVConfig(collaterals[i])); -} - - // timeout rule liquidations_equal_for_one { env e; @@ -153,7 +94,7 @@ rule liquidations_equal_for_one { require oracleAddress != 0; require unitOfAccount != 0; - require ltvs_configuration_assumption(e, account); + // require ltvs_configuration_assumption(e, account); address[] collaterals = getCollateralsExt(e, account); require collaterals.length == 1; diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index 7efa3409..1d4c7bd7 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -26,9 +26,7 @@ checkLiquidation must revert if: - price oracle is not configured */ -// using SafeERC20Lib as safeERC20; -using ERC20 as erc20; - +import "../Base.spec"; methods { // envfree function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; @@ -36,70 +34,9 @@ methods { function vaultIsOnlyController(address account) external returns (bool) envfree; function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; - function ProxyUtils.metadata() internal returns (address, address, address)=> CVLProxyMetadata(); - // Workaround for lack of ability to summarize metadata - // function Cache.loadVault() internal returns (Liquidation.VaultCache memory) => CVLLoadVault(); - - // function LiquidityUtils.calculateLiquidity( - // Liquidation.VaultCache memory vaultCache, - // address account, - // address[] memory collaterals, - // Liquidation.LTVType ltvType - // ) internal returns (uint256, uint256) => calcLiquidity(account, collaterals, ltvType); - - // IPriceOracle - function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); - function _.getQuotes(uint256 amount, address base, address quote) external => CVLGetQuotes(amount, base, quote) expect (uint256, uint256); - - - // IERC20 - function _.name() external => DISPATCHER(true); - function _.symbol() external => DISPATCHER(true); - function _.decimals() external => DISPATCHER(true); - function _.totalSupply() external => DISPATCHER(true); - function _.balanceOf(address) external => DISPATCHER(true); - function _.allowance(address,address) external => DISPATCHER(true); - function _.approve(address,uint256) external => DISPATCHER(true); - function _.transfer(address,uint256) external => DISPATCHER(true); - function _.transferFrom(address,address,uint256) external => DISPATCHER(true); -} - -// ghost CVLGetQuotes_bidOut(uint256, address, address) returns uint256; -// ghost CVLGetQuotes_askOut(uint256, address, address) returns uint256; - -ghost CVLGetQuote(uint256, address, address) returns uint256; - - -function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint256, uint256) { - return ( - CVLGetQuote(amount, base, quote), - CVLGetQuote(amount, base, quote) - ); -} - -ghost address oracleAddress; -ghost address unitOfAccount; -function CVLProxyMetadata() returns (address, address, address) { - return (erc20, oracleAddress, unitOfAccount); -} - -// CRITICAL: [main] ERROR ALWAYS - Found errors in certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec: -// CRITICAL: [main] ERROR ALWAYS - Error in spec file (CER-162-CheckLiquidation.spec:87:1): The type VaultCache is not allowed in a return position of a ghost functio -// ghost CVLLoadVaultUninterp() returns Liquidation.VaultCache; - -function CVLLoadVault() returns Liquidation.VaultCache { - Liquidation.VaultCache vaultCache; - require vaultCache.oracle != 0; - return vaultCache; -} - -persistent ghost uint256 dummy_collateral; -persistent ghost uint256 dummy_liquidity; -function calcLiquidity(address account, address[] collaterals, Liquidation.LTVType ltvType) returns (uint256, uint256) { - // unconstrained but same value for same returns - return (dummy_collateral, dummy_liquidity); } +// passing rule checkLiquidation_healthy() { env e; address liquidator; @@ -123,6 +60,7 @@ rule checkLiquidation_healthy() { assert maxYield == 0; } +// counterexample rule checkLiquidation_maxYieldGreater { env e; address liquidator; @@ -145,6 +83,7 @@ rule checkLiquidation_maxYieldGreater { assert maxRepay > 0 => maxRepay <= maxYield; } +// passing rule checkLiquidation_mustRevert { env e; address liquidator; @@ -172,20 +111,21 @@ rule checkLiquidation_mustRevert { } -function LTVConfigAssumptions(env e, address collateral) returns bool { - Liquidation.LTVConfig ltvConfig = getLTVConfig(e, collateral); - // the LTV should be less than 1. Here 1e4 is the scaling factor. - // So we assume governance sets these GT 1. - bool targetLTVLessOne = ltvConfig.targetLTV < 10000; - bool originalLTVLessOne = ltvConfig.originalLTV < 10000; - bool target_less_original = ltvConfig.targetLTV < ltvConfig.originalLTV; - mathint timeRemaining = ltvConfig.targetTimestamp - e.block.timestamp; - return targetLTVLessOne && - originalLTVLessOne && - target_less_original && - require_uint32(timeRemaining) < ltvConfig.rampDuration; -} - +// function LTVConfigAssumptions(env e, address collateral) returns bool { +// Liquidation.LTVConfig ltvConfig = getLTVConfig(e, collateral); +// // the LTV should be less than 1. Here 1e4 is the scaling factor. +// // So we assume governance sets these GT 1. +// bool targetLTVLessOne = ltvConfig.targetLTV < 10000; +// bool originalLTVLessOne = ltvConfig.originalLTV < 10000; +// bool target_less_original = ltvConfig.targetLTV < ltvConfig.originalLTV; +// mathint timeRemaining = ltvConfig.targetTimestamp - e.block.timestamp; +// return targetLTVLessOne && +// originalLTVLessOne && +// target_less_original && +// require_uint32(timeRemaining) < ltvConfig.rampDuration; +// } + +// Passing. Assumptions can be reduced with Euler's fix. rule getCollateralValue_borrowing_lower { env e; Liquidation.VaultCache vaultCache; @@ -195,7 +135,8 @@ rule getCollateralValue_borrowing_lower { // Not enough. Counterexample: // https://prover.certora.com/output/65266/83f92155749f42d98cadd58754511ebe/?anonymousKey=b3bbd7dcc5b9cec2dbc6104528456fd908ad9057 // Need to also assume about ramp duration - require LTVConfigAssumptions(e, collateral); + // Liquidation.LTVConfig ltvConfig = getLTVConfig(e, collateral); + require LTVConfigAssumptions(e, getLTVConfig(e, collateral)); uint256 collateralValue_borrowing = getCollateralValueExt(e, vaultCache, account, collateral, Liquidation.LTVType.BORROWING); From cc3d13802896972feb7c3cab5b673b524e520049 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 17 Apr 2024 13:42:44 +0100 Subject: [PATCH 044/152] More refactoring with common harness functions in abstract --- certora/harness/AbstractBaseHarness.sol | 19 +++++++++++++++++++ certora/harness/BaseHarness.sol | 14 ++++++++++++-- certora/harness/RiskManagerHarness.sol | 9 +++++---- certora/specs/Base.spec | 6 ++++++ certora/specs/CER-155-RiskManager.spec | 2 +- 5 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 certora/harness/AbstractBaseHarness.sol diff --git a/certora/harness/AbstractBaseHarness.sol b/certora/harness/AbstractBaseHarness.sol new file mode 100644 index 00000000..eaf14c05 --- /dev/null +++ b/certora/harness/AbstractBaseHarness.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import "../../src/EVault/shared/Base.sol"; + +// This mainly exists so that Base.LTVConfig and other type declarations +// are available in CVL and can be used across specs for different modules. +// It also exports some functions common across the modules. + +abstract contract AbstractBaseHarness is Base { + function getCollateralsExt(address account) public view returns (address[] memory) { + return getCollaterals(account); + } + + // function getLTVConfig(address collateral) external view returns (LTVConfig memory) { + // return vaultStorage.ltvLookup[collateral]; + // } +} \ No newline at end of file diff --git a/certora/harness/BaseHarness.sol b/certora/harness/BaseHarness.sol index b1c803ad..8a33e6de 100644 --- a/certora/harness/BaseHarness.sol +++ b/certora/harness/BaseHarness.sol @@ -3,10 +3,20 @@ pragma solidity ^0.8.0; import "../../src/EVault/shared/Base.sol"; +import "../../certora/harness/AbstractBaseHarness.sol"; // This mainly exists so that Base.LTVConfig and other type declarations -// are available in CVL and can be used across specs for different modules +// are available in CVL and can be used across specs for different modules. +// It also exports some functions common across the modules. -contract BaseHarness is Base { +contract BaseHarness is Base, AbstractBaseHarness { constructor(Integrations memory integrations) Base(integrations) {} + + // function getCollateralsExt(address account) public view returns (address[] memory) { + // return getCollaterals(account); + // } + + // function getLTVConfig(address collateral) external view returns (LTVConfig memory) { + // return vaultStorage.ltvLookup[collateral]; + // } } \ No newline at end of file diff --git a/certora/harness/RiskManagerHarness.sol b/certora/harness/RiskManagerHarness.sol index f3dd1cab..ed048020 100644 --- a/certora/harness/RiskManagerHarness.sol +++ b/certora/harness/RiskManagerHarness.sol @@ -6,13 +6,14 @@ import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector. import "../../src/interfaces/IPriceOracle.sol"; import {IERC20} from "../../src/EVault/IEVault.sol"; import {ERC20} from "../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../certora/harness/AbstractBaseHarness.sol"; -contract RiskManagerHarness is RiskManager { +contract RiskManagerHarness is RiskManager, AbstractBaseHarness { constructor(Integrations memory integrations) RiskManager(integrations) {} - function getCollateralsExt(address account) public view returns (address[] memory) { - return getCollaterals(account); - } + // function getCollateralsExt(address account) public view returns (address[] memory) { + // return getCollaterals(account); + // } function vaultIsOnlyController(address account) external view returns (bool) { address[] memory controllers = IEVC(evc).getControllers(account); diff --git a/certora/specs/Base.spec b/certora/specs/Base.spec index fbbb6207..41010044 100644 --- a/certora/specs/Base.spec +++ b/certora/specs/Base.spec @@ -2,6 +2,12 @@ using ERC20 as erc20; using EthereumVaultConnector as evc; methods { + // envfree + // function _.getCollateralsExt(address account) external returns (address[] memory) envfree; + // function _.getCollateralsExt(address account) external envfree; + function getCollateralsExt(address account) external returns (address[] memory) envfree; + // function getLTVConfig(address collateral) external returns (RiskManager.LTVConfig memory) envfree; + // IPriceOracle function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); function _.getQuotes(uint256 amount, address base, address quote) external => CVLGetQuotes(amount, base, quote) expect (uint256, uint256); diff --git a/certora/specs/CER-155-RiskManager.spec b/certora/specs/CER-155-RiskManager.spec index 3254c31b..7e2b7fc8 100644 --- a/certora/specs/CER-155-RiskManager.spec +++ b/certora/specs/CER-155-RiskManager.spec @@ -77,7 +77,7 @@ import "Base.spec"; methods { // envfree function vaultIsOnlyController(address account) external returns (bool) envfree; - function getCollateralsExt(address account) external returns (address[] memory) envfree; + // function getCollateralsExt(address account) external returns (address[] memory) envfree; function getLTVConfig(address collateral) external returns (RiskManager.LTVConfig memory) envfree; } From 7f07f9e75aef8461ebef4f01744228dfdccf206d Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 17 Apr 2024 14:41:02 +0100 Subject: [PATCH 045/152] More refactoring --- certora/conf/CER-155-RiskManager.conf | 1 + certora/harness/AbstractBaseHarness.sol | 20 ++++++++++++++++--- certora/harness/BaseHarness.sol | 12 +---------- certora/harness/LiquidationHarness.sol | 15 -------------- certora/harness/RiskManagerHarness.sol | 19 ------------------ certora/specs/Base.spec | 4 +--- certora/specs/CER-155-RiskManager.spec | 2 -- .../CER-162-CheckLiquidation.spec | 14 ------------- 8 files changed, 20 insertions(+), 67 deletions(-) diff --git a/certora/conf/CER-155-RiskManager.conf b/certora/conf/CER-155-RiskManager.conf index 9b13d61a..c7bba759 100644 --- a/certora/conf/CER-155-RiskManager.conf +++ b/certora/conf/CER-155-RiskManager.conf @@ -22,6 +22,7 @@ ], "parametric_contracts": ["RiskManagerHarness"], "rule_sanity": "basic", + "server": "production", // "coverage_info" : "advanced", // Performance tuning options below this line "prover_args": [ diff --git a/certora/harness/AbstractBaseHarness.sol b/certora/harness/AbstractBaseHarness.sol index eaf14c05..71778612 100644 --- a/certora/harness/AbstractBaseHarness.sol +++ b/certora/harness/AbstractBaseHarness.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; +import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; import "../../src/EVault/shared/Base.sol"; // This mainly exists so that Base.LTVConfig and other type declarations @@ -13,7 +14,20 @@ abstract contract AbstractBaseHarness is Base { return getCollaterals(account); } - // function getLTVConfig(address collateral) external view returns (LTVConfig memory) { - // return vaultStorage.ltvLookup[collateral]; - // } + function getLTVConfig(address collateral) external view returns (LTVConfig memory) { + return vaultStorage.ltvLookup[collateral]; + } + + function vaultCacheOracleConfigured() external returns (bool) { + return address(loadVault().oracle) != address(0); + } + + function vaultIsOnlyController(address account) external view returns (bool) { + address[] memory controllers = IEVC(evc).getControllers(account); + return controllers.length == 1 && controllers[0] == address(this); + } + + function vaultIsController(address account) external view returns (bool) { + return IEVC(evc).isControllerEnabled(account, address(this)); + } } \ No newline at end of file diff --git a/certora/harness/BaseHarness.sol b/certora/harness/BaseHarness.sol index 8a33e6de..f07c3227 100644 --- a/certora/harness/BaseHarness.sol +++ b/certora/harness/BaseHarness.sol @@ -5,18 +5,8 @@ pragma solidity ^0.8.0; import "../../src/EVault/shared/Base.sol"; import "../../certora/harness/AbstractBaseHarness.sol"; -// This mainly exists so that Base.LTVConfig and other type declarations +// This exists so that Base.LTVConfig and other type declarations // are available in CVL and can be used across specs for different modules. -// It also exports some functions common across the modules. - contract BaseHarness is Base, AbstractBaseHarness { constructor(Integrations memory integrations) Base(integrations) {} - - // function getCollateralsExt(address account) public view returns (address[] memory) { - // return getCollaterals(account); - // } - - // function getLTVConfig(address collateral) external view returns (LTVConfig memory) { - // return vaultStorage.ltvLookup[collateral]; - // } } \ No newline at end of file diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/LiquidationHarness.sol index ecf09278..98657250 100644 --- a/certora/harness/LiquidationHarness.sol +++ b/certora/harness/LiquidationHarness.sol @@ -9,8 +9,6 @@ import {IERC20} from "../../src/EVault/IEVault.sol"; import {ERC20} from "../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; contract LiquidationHarness is Liquidation { - // VaultCache vaultCache_; - // LiquidationCache liqCache_; constructor(Integrations memory integrations) Liquidation(integrations) {} @@ -55,10 +53,6 @@ contract LiquidationHarness is Liquidation { return isAccountStatusCheckDeferred(account); } - function vaultCacheOracleConfigured() external returns (bool) { - return address(loadVault().oracle) != address(0); - } - function validateOracleExt(VaultCache memory vaultCache) external pure { validateOracle(vaultCache); } @@ -67,15 +61,6 @@ contract LiquidationHarness is Liquidation { (, liquidator) = initOperation(OP_LIQUIDATE, CHECKACCOUNT_CALLER); } - function vaultIsOnlyController(address account) external view returns (bool) { - address[] memory controllers = IEVC(evc).getControllers(account); - return controllers.length == 1 && controllers[0] == address(this); - } - - function vaultIsController(address account) external view returns (bool) { - return IEVC(evc).isControllerEnabled(account, address(this)); - } - function calculateLiquidationExt( VaultCache memory vaultCache, address liquidator, diff --git a/certora/harness/RiskManagerHarness.sol b/certora/harness/RiskManagerHarness.sol index ed048020..e965a7f0 100644 --- a/certora/harness/RiskManagerHarness.sol +++ b/certora/harness/RiskManagerHarness.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.0; import "../../src/EVault/modules/RiskManager.sol"; import "../../src/EVault/shared/types/Types.sol"; -import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; import "../../src/interfaces/IPriceOracle.sol"; import {IERC20} from "../../src/EVault/IEVault.sol"; import {ERC20} from "../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; @@ -11,22 +10,4 @@ import "../../certora/harness/AbstractBaseHarness.sol"; contract RiskManagerHarness is RiskManager, AbstractBaseHarness { constructor(Integrations memory integrations) RiskManager(integrations) {} - // function getCollateralsExt(address account) public view returns (address[] memory) { - // return getCollaterals(account); - // } - - function vaultIsOnlyController(address account) external view returns (bool) { - address[] memory controllers = IEVC(evc).getControllers(account); - return controllers.length == 1 && controllers[0] == address(this); - } - - function vaultCacheOracleConfigured() external returns (bool) { - return address(loadVault().oracle) != address(0); - } - - function getLTVConfig(address collateral) external view returns (LTVConfig memory) { - return vaultStorage.ltvLookup[collateral]; - } - - } \ No newline at end of file diff --git a/certora/specs/Base.spec b/certora/specs/Base.spec index 41010044..33c4e416 100644 --- a/certora/specs/Base.spec +++ b/certora/specs/Base.spec @@ -3,10 +3,8 @@ using EthereumVaultConnector as evc; methods { // envfree - // function _.getCollateralsExt(address account) external returns (address[] memory) envfree; - // function _.getCollateralsExt(address account) external envfree; function getCollateralsExt(address account) external returns (address[] memory) envfree; - // function getLTVConfig(address collateral) external returns (RiskManager.LTVConfig memory) envfree; + function getLTVConfig(address collateral) external returns (BaseHarness.LTVConfig memory) envfree; // IPriceOracle function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); diff --git a/certora/specs/CER-155-RiskManager.spec b/certora/specs/CER-155-RiskManager.spec index 7e2b7fc8..a31bdd26 100644 --- a/certora/specs/CER-155-RiskManager.spec +++ b/certora/specs/CER-155-RiskManager.spec @@ -77,8 +77,6 @@ import "Base.spec"; methods { // envfree function vaultIsOnlyController(address account) external returns (bool) envfree; - // function getCollateralsExt(address account) external returns (address[] memory) envfree; - function getLTVConfig(address collateral) external returns (RiskManager.LTVConfig memory) envfree; } diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec index 1d4c7bd7..d8f55577 100644 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec @@ -111,20 +111,6 @@ rule checkLiquidation_mustRevert { } -// function LTVConfigAssumptions(env e, address collateral) returns bool { -// Liquidation.LTVConfig ltvConfig = getLTVConfig(e, collateral); -// // the LTV should be less than 1. Here 1e4 is the scaling factor. -// // So we assume governance sets these GT 1. -// bool targetLTVLessOne = ltvConfig.targetLTV < 10000; -// bool originalLTVLessOne = ltvConfig.originalLTV < 10000; -// bool target_less_original = ltvConfig.targetLTV < ltvConfig.originalLTV; -// mathint timeRemaining = ltvConfig.targetTimestamp - e.block.timestamp; -// return targetLTVLessOne && -// originalLTVLessOne && -// target_less_original && -// require_uint32(timeRemaining) < ltvConfig.rampDuration; -// } - // Passing. Assumptions can be reduced with Euler's fix. rule getCollateralValue_borrowing_lower { env e; From e11a143faf9536eaabf3b8b80a3f47e0936b7071 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 18 Apr 2024 11:45:22 +0100 Subject: [PATCH 046/152] More refactoring. Use BaseHarness for BalanceForwarder --- ...ceForwarder.conf => BalanceForwarder.conf} | 7 +- .../{CER-131-Vault.conf => DELETE_Vault.conf} | 2 +- certora/conf/EVault/shared/Constants.conf | 15 ---- ...-155-RiskManager.conf => RiskManager.conf} | 10 +-- certora/harness/AbstractBaseHarness.sol | 43 +++++++++- certora/harness/BalanceForwarderHarness.sol | 12 +++ certora/harness/BaseHarness.sol | 4 + certora/harness/ConstHarness.sol | 11 --- ...ultHarness.sol => DELETE_VaultHarness.sol} | 0 certora/harness/RiskManagerHarness.sol | 1 - ...ceForwarder.spec => BalanceForwarder.spec} | 35 ++++---- certora/specs/Base.spec | 14 ++++ certora/specs/EVault/EVault.spec | 72 ----------------- certora/specs/EVault/modules/DToken.spec | 71 ---------------- certora/specs/EVault/modules/Token.spec | 81 ------------------- certora/specs/EVault/shared/Constants.spec | 7 -- ...-155-RiskManager.spec => RiskManager.spec} | 3 +- .../specs/{CER-131-Vault.spec => Vault.spec} | 0 18 files changed, 97 insertions(+), 291 deletions(-) rename certora/conf/{CER-182-BalanceForwarder.conf => BalanceForwarder.conf} (77%) rename certora/conf/{CER-131-Vault.conf => DELETE_Vault.conf} (93%) delete mode 100644 certora/conf/EVault/shared/Constants.conf rename certora/conf/{CER-155-RiskManager.conf => RiskManager.conf} (87%) create mode 100644 certora/harness/BalanceForwarderHarness.sol delete mode 100644 certora/harness/ConstHarness.sol rename certora/harness/{VaultHarness.sol => DELETE_VaultHarness.sol} (100%) rename certora/specs/{CER-182-BalanceForwarder.spec => BalanceForwarder.spec} (63%) delete mode 100644 certora/specs/EVault/EVault.spec delete mode 100644 certora/specs/EVault/modules/DToken.spec delete mode 100644 certora/specs/EVault/modules/Token.spec delete mode 100644 certora/specs/EVault/shared/Constants.spec rename certora/specs/{CER-155-RiskManager.spec => RiskManager.spec} (97%) rename certora/specs/{CER-131-Vault.spec => Vault.spec} (100%) diff --git a/certora/conf/CER-182-BalanceForwarder.conf b/certora/conf/BalanceForwarder.conf similarity index 77% rename from certora/conf/CER-182-BalanceForwarder.conf rename to certora/conf/BalanceForwarder.conf index a9034c10..e94d99c1 100644 --- a/certora/conf/CER-182-BalanceForwarder.conf +++ b/certora/conf/BalanceForwarder.conf @@ -4,12 +4,13 @@ "lib/ethereum-vault-connector/src/ExecutionContext.sol", "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", "src/EVault/modules/BalanceForwarder.sol", - "certora/harness/VaultHarness.sol" + "certora/harness/BaseHarness.sol", + "certora/harness/BalanceForwarderHarness.sol", ], "link": [ - "BalanceForwarder:evc=EthereumVaultConnector", + "BalanceForwarderHarness:evc=EthereumVaultConnector", ], - "verify": "BalanceForwarder:certora/specs/CER-182-BalanceForwarder.spec", + "verify": "BalanceForwarderHarness:certora/specs/BalanceForwarder.spec", "solc": "solc8.23", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", diff --git a/certora/conf/CER-131-Vault.conf b/certora/conf/DELETE_Vault.conf similarity index 93% rename from certora/conf/CER-131-Vault.conf rename to certora/conf/DELETE_Vault.conf index 1a1e6904..85965b24 100644 --- a/certora/conf/CER-131-Vault.conf +++ b/certora/conf/DELETE_Vault.conf @@ -9,7 +9,7 @@ "link": [ "VaultHarness:evc=EthereumVaultConnector", ], - "verify": "VaultHarness:certora/specs/CER-131-Vault.spec", + "verify": "VaultHarness:certora/specs/Vault.spec", "solc": "solc8.23", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", diff --git a/certora/conf/EVault/shared/Constants.conf b/certora/conf/EVault/shared/Constants.conf deleted file mode 100644 index 898d2679..00000000 --- a/certora/conf/EVault/shared/Constants.conf +++ /dev/null @@ -1,15 +0,0 @@ -{ - "files": [ - "certora/harness/ConstHarness.sol", - ], - "verify": "ConstHarness:certora/specs/EVault/shared/Constants.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "msg": "Constants", - "packages": [ - "forge-std=lib/forge-std/src" - ], - "prover_args": [ - "-useBitVectorTheory" - ] -} \ No newline at end of file diff --git a/certora/conf/CER-155-RiskManager.conf b/certora/conf/RiskManager.conf similarity index 87% rename from certora/conf/CER-155-RiskManager.conf rename to certora/conf/RiskManager.conf index c7bba759..3f6ce3b3 100644 --- a/certora/conf/CER-155-RiskManager.conf +++ b/certora/conf/RiskManager.conf @@ -10,16 +10,16 @@ "link": [ "RiskManagerHarness:evc=EthereumVaultConnector", ], - "verify": "RiskManagerHarness:certora/specs/CER-155-RiskManager.spec", + "verify": "RiskManagerHarness:certora/specs/RiskManager.spec", "solc": "solc8.23", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "rule": [ - "ltv_borrowing_lower" - // "liquidations_equal_for_one" - ], + // "rule": [ + // "ltv_borrowing_lower" + // // "liquidations_equal_for_one" + // ], "parametric_contracts": ["RiskManagerHarness"], "rule_sanity": "basic", "server": "production", diff --git a/certora/harness/AbstractBaseHarness.sol b/certora/harness/AbstractBaseHarness.sol index 71778612..285ef0d3 100644 --- a/certora/harness/AbstractBaseHarness.sol +++ b/certora/harness/AbstractBaseHarness.sol @@ -5,10 +5,12 @@ pragma solidity ^0.8.0; import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; import "../../src/EVault/shared/Base.sol"; -// This mainly exists so that Base.LTVConfig and other type declarations +// This exists so that Base.LTVConfig and other type declarations // are available in CVL and can be used across specs for different modules. -// It also exports some functions common across the modules. - +// We need to split this into a concrete contract and an Abstract contract +// so that we can refer to Base.LTVConfig as a type in shared CVL functions +// while also making function definitions sharable among harnesses via +// AbstractBase. AbstractBaseHarness includes the shared function definitions. abstract contract AbstractBaseHarness is Base { function getCollateralsExt(address account) public view returns (address[] memory) { return getCollaterals(account); @@ -30,4 +32,39 @@ abstract contract AbstractBaseHarness is Base { function vaultIsController(address account) external view returns (bool) { return IEVC(evc).isControllerEnabled(account, address(this)); } + + function getBalanceAndForwarderExt(address account) public returns (Shares, bool) { + return vaultStorage.users[account].getBalanceAndBalanceForwarder(); + } + + //-------------------------------------------------------------------------- + // Operation disable checks + //-------------------------------------------------------------------------- + function isOperationDisabledExt(uint32 operation) public returns (bool) { + // This is based on the check in callHook. + VaultCache memory vaultCache = updateVault(); + return vaultCache.hookedOps.isNotSet(operation); + } + + function isDepositDisabled() public returns (bool) { + return isOperationDisabledExt(OP_DEPOSIT); + } + + function isMintDisabled() public returns (bool) { + return isOperationDisabledExt(OP_MINT); + } + + function isWithdrawDisabled() public returns (bool) { + return isOperationDisabledExt(OP_WITHDRAW); + } + + function isRedeemDisabled() public returns (bool) { + return isOperationDisabledExt(OP_REDEEM); + } + + function isSkimDisabled() public returns (bool) { + return isOperationDisabledExt(OP_SKIM); + } + + } \ No newline at end of file diff --git a/certora/harness/BalanceForwarderHarness.sol b/certora/harness/BalanceForwarderHarness.sol new file mode 100644 index 00000000..cf01e20b --- /dev/null +++ b/certora/harness/BalanceForwarderHarness.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import "../../src/EVault/modules/BalanceForwarder.sol"; +import "../../src/EVault/shared/types/Types.sol"; +import "../../src/interfaces/IPriceOracle.sol"; +import {IERC20} from "../../src/EVault/IEVault.sol"; +import {ERC20} from "../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../certora/harness/AbstractBaseHarness.sol"; + +contract BalanceForwarderHarness is BalanceForwarder, AbstractBaseHarness { + constructor(Integrations memory integrations) BalanceForwarder(integrations) {} +} \ No newline at end of file diff --git a/certora/harness/BaseHarness.sol b/certora/harness/BaseHarness.sol index f07c3227..304eeeca 100644 --- a/certora/harness/BaseHarness.sol +++ b/certora/harness/BaseHarness.sol @@ -7,6 +7,10 @@ import "../../certora/harness/AbstractBaseHarness.sol"; // This exists so that Base.LTVConfig and other type declarations // are available in CVL and can be used across specs for different modules. +// We need to split this into a concrete contract and an Abstract contract +// so that we can refer to Base.LTVConfig as a type in shared CVL functions +// while also making function definitions sharable among harnesses via +// AbstractBase. contract BaseHarness is Base, AbstractBaseHarness { constructor(Integrations memory integrations) Base(integrations) {} } \ No newline at end of file diff --git a/certora/harness/ConstHarness.sol b/certora/harness/ConstHarness.sol deleted file mode 100644 index 14415f3a..00000000 --- a/certora/harness/ConstHarness.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.8.0; -import "../../src/EVault/shared/Constants.sol"; - -// An empty contract to verify constant properties independently -contract ConstHarness { - // Constants undeclared. Circular dependency if dropped. - uint32 public constant OP_DEPOSIT_ = OP_DEPOSIT; - uint32 public constant OP_MINT_ = OP_MINT; -} \ No newline at end of file diff --git a/certora/harness/VaultHarness.sol b/certora/harness/DELETE_VaultHarness.sol similarity index 100% rename from certora/harness/VaultHarness.sol rename to certora/harness/DELETE_VaultHarness.sol diff --git a/certora/harness/RiskManagerHarness.sol b/certora/harness/RiskManagerHarness.sol index e965a7f0..c3d57d83 100644 --- a/certora/harness/RiskManagerHarness.sol +++ b/certora/harness/RiskManagerHarness.sol @@ -9,5 +9,4 @@ import "../../certora/harness/AbstractBaseHarness.sol"; contract RiskManagerHarness is RiskManager, AbstractBaseHarness { constructor(Integrations memory integrations) RiskManager(integrations) {} - } \ No newline at end of file diff --git a/certora/specs/CER-182-BalanceForwarder.spec b/certora/specs/BalanceForwarder.spec similarity index 63% rename from certora/specs/CER-182-BalanceForwarder.spec rename to certora/specs/BalanceForwarder.spec index 5d61748e..e5fe84bc 100644 --- a/certora/specs/CER-182-BalanceForwarder.spec +++ b/certora/specs/BalanceForwarder.spec @@ -12,29 +12,26 @@ balance tracker for the authenticated account. The balance tracker hook should be invoked with 0 as the balance of the account. */ -using EthereumVaultConnector as evc; +import "Base.spec"; +// using EthereumVaultConnector as evc; -methods { - // function _.isBalanceForwarderEnabled(BalanceForwarder.UserStorage storage self) internal => CVLIsBalanceForwarderEnabled(self.data) expect (bool); -} - -function actualCaller(env e) returns address { - if(e.msg.sender == evc) { - address onBehalf; - bool unused; - onBehalf, unused = evc.getCurrentOnBehalfOfAccount(e, 0); - return onBehalf; - } else { - return e.msg.sender; - } -} +// function actualCaller(env e) returns address { +// if(e.msg.sender == evc) { +// address onBehalf; +// bool unused; +// onBehalf, unused = evc.getCurrentOnBehalfOfAccount(e, 0); +// return onBehalf; +// } else { +// return e.msg.sender; +// } +// } // NOTE: Unused currently -ghost mapping(uint256 => bool) ghost_balanceForwarderFlag; -function CVLIsBalanceForwarderEnabled(uint256 userStorage_data) returns bool { - return ghost_balanceForwarderFlag[userStorage_data]; -} +// ghost mapping(uint256 => bool) ghost_balanceForwarderFlag; +// function CVLIsBalanceForwarderEnabled(uint256 userStorage_data) returns bool { +// return ghost_balanceForwarderFlag[userStorage_data]; +// } rule enableBalanceForwarder { address account; diff --git a/certora/specs/Base.spec b/certora/specs/Base.spec index 33c4e416..568dec6a 100644 --- a/certora/specs/Base.spec +++ b/certora/specs/Base.spec @@ -6,6 +6,9 @@ methods { function getCollateralsExt(address account) external returns (address[] memory) envfree; function getLTVConfig(address collateral) external returns (BaseHarness.LTVConfig memory) envfree; + // Inline assembly here gives the tool problems + function _.calculateDTokenAddress() internal => NONDET; + // IPriceOracle function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); function _.getQuotes(uint256 amount, address base, address quote) external => CVLGetQuotes(amount, base, quote) expect (uint256, uint256); @@ -56,4 +59,15 @@ function LTVConfigAssumptions(env e, BaseHarness.LTVConfig ltvConfig) returns bo originalLTVLessOne && target_less_original && require_uint32(timeRemaining) < ltvConfig.rampDuration; +} + +function actualCaller(env e) returns address { + if(e.msg.sender == evc) { + address onBehalf; + bool unused; + onBehalf, unused = evc.getCurrentOnBehalfOfAccount(e, 0); + return onBehalf; + } else { + return e.msg.sender; + } } \ No newline at end of file diff --git a/certora/specs/EVault/EVault.spec b/certora/specs/EVault/EVault.spec deleted file mode 100644 index 722cd241..00000000 --- a/certora/specs/EVault/EVault.spec +++ /dev/null @@ -1,72 +0,0 @@ -use builtin rule sanity; -use builtin rule hasDelegateCalls; -use builtin rule msgValueInLoopRule; -use builtin rule viewReentrancy; - -methods { - function _.requireVaultStatusCheck() external => NONDET; - function _.requireAccountStatusCheck(address account) external => NONDET; - function _.requireAccountAndVaultStatusCheck(address account) external => NONDET; -} - -rule noRevert(method f) { - env e; - calldataarg arg; - require e.msg.value == 0; - f@withrevert(e, arg); - assert !lastReverted; -} - - -rule alwaysRevert(method f) { - env e; - calldataarg arg; - f@withrevert(e, arg); - assert lastReverted; -} - -/* -This rule find which functions that can be called, may fail due to someone else calling a function right before. - -This is n expensive rule - might fail on the demo site on big contracts -*/ -rule simpleFrontRunning(method f, address privileged) filtered { f-> !f.isView } { - env e1; - calldataarg arg; - require e1.msg.sender == privileged; - storage initialStorage = lastStorage; - f(e1, arg); - bool firstSucceeded = !lastReverted; - env e2; - calldataarg arg2; - require e2.msg.sender != e1.msg.sender; - f(e2, arg2) at initialStorage; - f@withrevert(e1, arg); - bool succeeded = !lastReverted; - assert succeeded; -} - - -/* -This rule find which functions are privileged. -A function is privileged if there is only one address that can call it. - -The rules finds this by finding which functions can be called by two different users. -*/ -rule privilegedOperation(method f, address privileged) { - env e1; - calldataarg arg; - require e1.msg.sender == privileged; - - storage initialStorage = lastStorage; - f@withrevert(e1, arg); // privileged succeeds executing candidate privileged operation. - bool firstSucceeded = !lastReverted; - - env e2; - calldataarg arg2; - require e2.msg.sender != privileged; - f@withrevert(e2, arg2) at initialStorage; // unprivileged - bool secondSucceeded = !lastReverted; - - assert !(firstSucceeded && secondSucceeded); -} diff --git a/certora/specs/EVault/modules/DToken.spec b/certora/specs/EVault/modules/DToken.spec deleted file mode 100644 index c4c3c27a..00000000 --- a/certora/specs/EVault/modules/DToken.spec +++ /dev/null @@ -1,71 +0,0 @@ -methods { -} - -// Below this line is same as Benchmarking.spec -use builtin rule sanity; -use builtin rule hasDelegateCalls; -use builtin rule msgValueInLoopRule; -use builtin rule viewReentrancy; - - -rule noRevert(method f) { - env e; - calldataarg arg; - require e.msg.value == 0; - f@withrevert(e, arg); - assert !lastReverted; -} - - -rule alwaysRevert(method f) { - env e; - calldataarg arg; - f@withrevert(e, arg); - assert lastReverted; -} - -/* -This rule find which functions that can be called, may fail due to someone else calling a function right before. - -This is n expensive rule - might fail on the demo site on big contracts -*/ -rule simpleFrontRunning(method f, address privileged) filtered { f-> !f.isView } { - env e1; - calldataarg arg; - require e1.msg.sender == privileged; - storage initialStorage = lastStorage; - f(e1, arg); - bool firstSucceeded = !lastReverted; - env e2; - calldataarg arg2; - require e2.msg.sender != e1.msg.sender; - f(e2, arg2) at initialStorage; - f@withrevert(e1, arg); - bool succeeded = !lastReverted; - assert succeeded; -} - - -/* -This rule find which functions are privileged. -A function is privileged if there is only one address that can call it. - -The rules finds this by finding which functions can be called by two different users. -*/ -rule privilegedOperation(method f, address privileged) { - env e1; - calldataarg arg; - require e1.msg.sender == privileged; - - storage initialStorage = lastStorage; - f@withrevert(e1, arg); // privileged succeeds executing candidate privileged operation. - bool firstSucceeded = !lastReverted; - - env e2; - calldataarg arg2; - require e2.msg.sender != privileged; - f@withrevert(e2, arg2) at initialStorage; // unprivileged - bool secondSucceeded = !lastReverted; - - assert !(firstSucceeded && secondSucceeded); -} diff --git a/certora/specs/EVault/modules/Token.spec b/certora/specs/EVault/modules/Token.spec deleted file mode 100644 index 965af76d..00000000 --- a/certora/specs/EVault/modules/Token.spec +++ /dev/null @@ -1,81 +0,0 @@ -// erc20.spec -methods { - function _.name() external => DISPATCHER(true); - function _.symbol() external => DISPATCHER(true); - function _.decimals() external => DISPATCHER(true); - function _.totalSupply() external => DISPATCHER(true); - function _.balanceOf(address) external => DISPATCHER(true); - function _.allowance(address,address) external => DISPATCHER(true); - function _.approve(address,uint256) external => DISPATCHER(true); - function _.transfer(address,uint256) external => DISPATCHER(true); - function _.transferFrom(address,address,uint256) external => DISPATCHER(true); -} - -// Below this line is same as Benchmarking.spec -use builtin rule sanity; -use builtin rule hasDelegateCalls; -use builtin rule msgValueInLoopRule; -use builtin rule viewReentrancy; - - -rule noRevert(method f) { - env e; - calldataarg arg; - require e.msg.value == 0; - f@withrevert(e, arg); - assert !lastReverted; -} - - -rule alwaysRevert(method f) { - env e; - calldataarg arg; - f@withrevert(e, arg); - assert lastReverted; -} - -/* -This rule find which functions that can be called, may fail due to someone else calling a function right before. - -This is n expensive rule - might fail on the demo site on big contracts -*/ -rule simpleFrontRunning(method f, address privileged) filtered { f-> !f.isView } { - env e1; - calldataarg arg; - require e1.msg.sender == privileged; - storage initialStorage = lastStorage; - f(e1, arg); - bool firstSucceeded = !lastReverted; - env e2; - calldataarg arg2; - require e2.msg.sender != e1.msg.sender; - f(e2, arg2) at initialStorage; - f@withrevert(e1, arg); - bool succeeded = !lastReverted; - assert succeeded; -} - - -/* -This rule find which functions are privileged. -A function is privileged if there is only one address that can call it. - -The rules finds this by finding which functions can be called by two different users. -*/ -rule privilegedOperation(method f, address privileged) { - env e1; - calldataarg arg; - require e1.msg.sender == privileged; - - storage initialStorage = lastStorage; - f@withrevert(e1, arg); // privileged succeeds executing candidate privileged operation. - bool firstSucceeded = !lastReverted; - - env e2; - calldataarg arg2; - require e2.msg.sender != privileged; - f@withrevert(e2, arg2) at initialStorage; // unprivileged - bool secondSucceeded = !lastReverted; - - assert !(firstSucceeded && secondSucceeded); -} diff --git a/certora/specs/EVault/shared/Constants.spec b/certora/specs/EVault/shared/Constants.spec deleted file mode 100644 index f50c1ba4..00000000 --- a/certora/specs/EVault/shared/Constants.spec +++ /dev/null @@ -1,7 +0,0 @@ -methods { - function OP_DEPOSIT() external returns (uint32) envfree; - function OP_MINT() external returns (uint32) envfree; -} -rule bitmasks_disjoint { - assert (OP_DEPOSIT() & OP_MINT()) == 0; -} \ No newline at end of file diff --git a/certora/specs/CER-155-RiskManager.spec b/certora/specs/RiskManager.spec similarity index 97% rename from certora/specs/CER-155-RiskManager.spec rename to certora/specs/RiskManager.spec index a31bdd26..36bb58bb 100644 --- a/certora/specs/CER-155-RiskManager.spec +++ b/certora/specs/RiskManager.spec @@ -92,7 +92,6 @@ rule liquidations_equal_for_one { require oracleAddress != 0; require unitOfAccount != 0; - // require ltvs_configuration_assumption(e, account); address[] collaterals = getCollateralsExt(e, account); require collaterals.length == 1; @@ -106,7 +105,7 @@ rule liquidations_equal_for_one { } -// cex: https://prover.certora.com/output/40748/8c5b2eea4cc9452391b6739c357dbecd/?anonymousKey=a81b45f19e0a01b08f32ec2e7182479d7d5ab4ec +// passing: https://prover.certora.com/output/65266/8b94c232c4b14e3aab917cd7e94d501c/?anonymousKey=27f680520b4d7cbb9f387563d3f1bb45de8fc9a7 rule ltv_borrowing_lower { env e; calldataarg args; diff --git a/certora/specs/CER-131-Vault.spec b/certora/specs/Vault.spec similarity index 100% rename from certora/specs/CER-131-Vault.spec rename to certora/specs/Vault.spec From aaa731d97da54f2cc8d965a101609ee3580a6c51 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 18 Apr 2024 13:25:07 +0100 Subject: [PATCH 047/152] Refactoring in Liquidation --- .../CER-162-CheckLiquidation.conf | 39 ----- .../CER-163-Liquidate.conf | 32 ---- certora/harness/AbstractBaseHarness.sol | 29 +++- certora/harness/DELETE_VaultHarness.sol | 1 - certora/harness/LiquidationHarness.sol | 58 ++------ certora/specs/Base.spec | 6 +- .../CER-162-CheckLiquidation.spec | 136 ----------------- .../CER-163-Liquidate.spec | 139 ------------------ 8 files changed, 38 insertions(+), 402 deletions(-) delete mode 100644 certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf delete mode 100644 certora/conf/CER-161-Liquidation/CER-163-Liquidate.conf delete mode 100644 certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec delete mode 100644 certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec diff --git a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf b/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf deleted file mode 100644 index 77593086..00000000 --- a/certora/conf/CER-161-Liquidation/CER-162-CheckLiquidation.conf +++ /dev/null @@ -1,39 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", - "src/EVault/modules/Liquidation.sol", - "certora/harness/BaseHarness.sol", - "certora/harness/LiquidationHarness.sol" - ], - "link": [ - "LiquidationHarness:evc=EthereumVaultConnector", - ], - "verify": "LiquidationHarness:certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec", - "solc": "solc8.23", - "msg": "Liquidation benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - // "rule": [ - // "getCollateralValue_borrowing_lower" - // ], - "parametric_contracts": ["LiquidationHarness"], - "rule_sanity": "basic", - // "coverage_info" : "advanced", - // Performance tuing options below this line - "prover_args": [ - "-depth 10", - "-smt_nonLinearArithmetic true", - "-adaptiveSolverConfig false" - ], - "function_finder_mode": "relaxed", - "optimistic_loop": true, - "loop_iter": "2", - "solc_via_ir": true, - "solc_optimize": "10000", - "smt_timeout": "7000", - "prover_version": "master" -} \ No newline at end of file diff --git a/certora/conf/CER-161-Liquidation/CER-163-Liquidate.conf b/certora/conf/CER-161-Liquidation/CER-163-Liquidate.conf deleted file mode 100644 index 6df2511d..00000000 --- a/certora/conf/CER-161-Liquidation/CER-163-Liquidate.conf +++ /dev/null @@ -1,32 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", - "src/EVault/modules/Liquidation.sol", - "certora/harness/LiquidationHarness.sol" - ], - "link": [ - "LiquidationHarness:evc=EthereumVaultConnector", - ], - "verify": "LiquidationHarness:certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec", - "solc": "solc8.23", - "msg": "Liquidation benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - // "rule": [ - // "calculateLiquidation_setViolator" - // ], - // "coverage_info" : "advanced", - "parametric_contracts": ["LiquidationHarness"], - "optimistic_loop": true, - "loop_iter": "2", - "solc_via_ir": true, - "solc_optimize": "10000", - "rule_sanity": "basic", - "function_finder_mode" : "relaxed", - "finder_friendly_optimizer" : false, - "prover_version" : "master" // need for dev fix -} \ No newline at end of file diff --git a/certora/harness/AbstractBaseHarness.sol b/certora/harness/AbstractBaseHarness.sol index 285ef0d3..de445ea8 100644 --- a/certora/harness/AbstractBaseHarness.sol +++ b/certora/harness/AbstractBaseHarness.sol @@ -12,9 +12,6 @@ import "../../src/EVault/shared/Base.sol"; // while also making function definitions sharable among harnesses via // AbstractBase. AbstractBaseHarness includes the shared function definitions. abstract contract AbstractBaseHarness is Base { - function getCollateralsExt(address account) public view returns (address[] memory) { - return getCollaterals(account); - } function getLTVConfig(address collateral) external view returns (LTVConfig memory) { return vaultStorage.ltvLookup[collateral]; @@ -24,6 +21,18 @@ abstract contract AbstractBaseHarness is Base { return address(loadVault().oracle) != address(0); } + function isAccountStatusCheckDeferredExt(address account) external view returns (bool) { + return isAccountStatusCheckDeferred(account); + } + + function getBalanceAndForwarderExt(address account) public returns (Shares, bool) { + return vaultStorage.users[account].getBalanceAndBalanceForwarder(); + } + + + //-------------------------------------------------------------------------- + // Controllers + //-------------------------------------------------------------------------- function vaultIsOnlyController(address account) external view returns (bool) { address[] memory controllers = IEVC(evc).getControllers(account); return controllers.length == 1 && controllers[0] == address(this); @@ -32,11 +41,19 @@ abstract contract AbstractBaseHarness is Base { function vaultIsController(address account) external view returns (bool) { return IEVC(evc).isControllerEnabled(account, address(this)); } - - function getBalanceAndForwarderExt(address account) public returns (Shares, bool) { - return vaultStorage.users[account].getBalanceAndBalanceForwarder(); + + //-------------------------------------------------------------------------- + // Collaterals + //-------------------------------------------------------------------------- + function getCollateralsExt(address account) public view returns (address[] memory) { + return getCollaterals(account); } + function isCollateralEnabledExt(address account, address market) external view returns (bool) { + return isCollateralEnabled(account, market); + } + + //-------------------------------------------------------------------------- // Operation disable checks //-------------------------------------------------------------------------- diff --git a/certora/harness/DELETE_VaultHarness.sol b/certora/harness/DELETE_VaultHarness.sol index 67010c49..763047b3 100644 --- a/certora/harness/DELETE_VaultHarness.sol +++ b/certora/harness/DELETE_VaultHarness.sol @@ -31,7 +31,6 @@ contract VaultHarness is Vault { return isOperationDisabledExt(OP_SKIM); } - function getBalanceAndForwarderExt(address account) public returns (Shares, bool) { return vaultStorage.users[account].getBalanceAndBalanceForwarder(); } diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/LiquidationHarness.sol index 98657250..7e6c1aa2 100644 --- a/certora/harness/LiquidationHarness.sol +++ b/certora/harness/LiquidationHarness.sol @@ -7,8 +7,9 @@ import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector. import "../../src/interfaces/IPriceOracle.sol"; import {IERC20} from "../../src/EVault/IEVault.sol"; import {ERC20} from "../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../certora/harness/AbstractBaseHarness.sol"; -contract LiquidationHarness is Liquidation { +contract LiquidationHarness is AbstractBaseHarness, Liquidation { constructor(Integrations memory integrations) Liquidation(integrations) {} @@ -18,49 +19,6 @@ contract LiquidationHarness is Liquidation { return calculateLiquidity(loadVault(), account, getCollaterals(account), LTVType.LIQUIDATION); } - function getLiquidityValue(address account, VaultCache memory vaultCache, address[] memory collaterals) public view returns (uint256 collateralValue) { - (collateralValue, ) = calculateLiquidity(vaultCache, account, collaterals, LTVType.LIQUIDATION); - } - - function getLiabilityValue(address account, VaultCache memory vaultCache, address[] memory collaterals) public view returns (uint256 liabilityValue) { - (,liabilityValue) = calculateLiquidity(vaultCache, account, collaterals, LTVType.LIQUIDATION); - } - - function loadVaultExt() public returns (VaultCache memory vaultCache) { - return loadVault(); - } - - function initOperationExternal(uint32 operation, address accountToCheck) - public - returns (VaultCache memory vaultCache, address account) - { - return initOperation(operation, accountToCheck); - } - - function getCollateralsExt(address account) public view returns (address[] memory) { - return getCollaterals(account); - } - - function isRecognizedCollateralExt(address collateral) external view virtual returns (bool) { - return isRecognizedCollateral(collateral); - } - - function isCollateralEnabledExt(address account, address market) external view returns (bool) { - return isCollateralEnabled(account, market); - } - - function isAccountStatusCheckDeferredExt(address account) external view returns (bool) { - return isAccountStatusCheckDeferred(account); - } - - function validateOracleExt(VaultCache memory vaultCache) external pure { - validateOracle(vaultCache); - } - - function getLiquidator() external returns (address liquidator) { - (, liquidator) = initOperation(OP_LIQUIDATE, CHECKACCOUNT_CALLER); - } - function calculateLiquidationExt( VaultCache memory vaultCache, address liquidator, @@ -71,6 +29,14 @@ contract LiquidationHarness is Liquidation { return calculateLiquidation(vaultCache, liquidator, violator, collateral, desiredRepay); } + function isRecognizedCollateralExt(address collateral) external view virtual returns (bool) { + return isRecognizedCollateral(collateral); + } + + function getLiquidator() external returns (address liquidator) { + (, liquidator) = initOperation(OP_LIQUIDATE, CHECKACCOUNT_CALLER); + } + function getCurrentOwedExt(VaultCache memory vaultCache, address violator) external view returns (Assets) { return getCurrentOwed(vaultCache, violator).toAssetsUp(); } @@ -82,8 +48,4 @@ contract LiquidationHarness is Liquidation { return getCollateralValue(vaultCache, account, collateral, ltvType); } - function getLTVConfig(address collateral) external view returns (LTVConfig memory) { - return vaultStorage.ltvLookup[collateral]; - } - } \ No newline at end of file diff --git a/certora/specs/Base.spec b/certora/specs/Base.spec index 568dec6a..464f9204 100644 --- a/certora/specs/Base.spec +++ b/certora/specs/Base.spec @@ -3,8 +3,12 @@ using EthereumVaultConnector as evc; methods { // envfree - function getCollateralsExt(address account) external returns (address[] memory) envfree; function getLTVConfig(address collateral) external returns (BaseHarness.LTVConfig memory) envfree; + function getCollateralsExt(address account) external returns (address[] memory) envfree; + function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; + function vaultIsOnlyController(address account) external returns (bool) envfree; + function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; + function vaultIsController(address account) external returns (bool) envfree; // Inline assembly here gives the tool problems function _.calculateDTokenAddress() internal => NONDET; diff --git a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec b/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec deleted file mode 100644 index d8f55577..00000000 --- a/certora/specs/CER-161-Liquidation/CER-162-CheckLiquidation.spec +++ /dev/null @@ -1,136 +0,0 @@ -/* -CER-162 / Verify EVK-31 -If violator unhealthy, checkLiquidation returns the maximum amount of the debt -asset the liquidator is allowed to liquidate (maxRepay) in exchange for the -returned maximum amount of collateral shares from violator (maxYield). - -If violator healthy, checkLiquidation returns maxRepay and maxYield as 0. - -Unless violator healthy, considering the liquidator bonus is positive and grows -linearly as the health of the violator deteriorates, the value of maxYield is -greater than the value of maxRepay. - -If needed, checkLiquidation must limit the maxRepay as per available amount of -collateral to be seized from the violator. - -If needed, checkLiquidation must limit the maxRepay and the maxYield as per -desired amount to be repaid (desiredRepay) parameter. - -checkLiquidation must revert if: - - violator is the same account as liquidator - - collateral is not accepted - - collateral is not enabled collateral for the violator - - liability vault is not enabled as the only controller of the violator - - violator account status check is deferred - - price oracle is not configured - - price oracle is not configured -*/ - -import "../Base.spec"; -methods { - // envfree - function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; - function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; - function vaultIsOnlyController(address account) external returns (bool) envfree; - function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; - -} - -// passing -rule checkLiquidation_healthy() { - env e; - address liquidator; - address violator; - address collateral; - uint256 maxRepay; - uint256 maxYield; - - require oracleAddress != 0; - - uint256 liquidityCollateralValue; - uint256 liquidityLiabilityValue; - (liquidityCollateralValue, liquidityLiabilityValue) = - calculateLiquidityExternal(e, violator); - - require liquidityCollateralValue > liquidityLiabilityValue; - - (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); - - assert maxRepay == 0; - assert maxYield == 0; -} - -// counterexample -rule checkLiquidation_maxYieldGreater { - env e; - address liquidator; - address violator; - address collateral; - uint256 maxRepay; - uint256 maxYield; - - uint256 collateralValue; - uint256 liabilityValue; - (collateralValue, liabilityValue) = - calculateLiquidityExternal(e, violator); - - require oracleAddress != 0; - require collateralValue > 0; - require liabilityValue > 0; - require collateralValue < liabilityValue; - - (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); - assert maxRepay > 0 => maxRepay <= maxYield; -} - -// passing -rule checkLiquidation_mustRevert { - env e; - address liquidator; - address violator; - address collateral; - uint256 maxRepay; - uint256 maxYield; - - require oracleAddress != 0; - bool selfLiquidate = liquidator == violator; - bool badCollateral = !isRecognizedCollateralExt(collateral); - bool enabledCollateral = isCollateralEnabledExt(violator, collateral); - bool vaultControlsViolator = vaultIsOnlyController(violator); - bool violatorStatusCheckDeferred = isAccountStatusCheckDeferredExt(violator); - bool oracleConfigured = vaultCacheOracleConfigured(e); - - (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); - - assert !selfLiquidate; - assert !badCollateral; - assert enabledCollateral; - assert vaultControlsViolator; - assert !violatorStatusCheckDeferred; - assert oracleConfigured; - -} - -// Passing. Assumptions can be reduced with Euler's fix. -rule getCollateralValue_borrowing_lower { - env e; - Liquidation.VaultCache vaultCache; - address account; - address collateral; - - // Not enough. Counterexample: - // https://prover.certora.com/output/65266/83f92155749f42d98cadd58754511ebe/?anonymousKey=b3bbd7dcc5b9cec2dbc6104528456fd908ad9057 - // Need to also assume about ramp duration - // Liquidation.LTVConfig ltvConfig = getLTVConfig(e, collateral); - require LTVConfigAssumptions(e, getLTVConfig(e, collateral)); - - uint256 collateralValue_borrowing = getCollateralValueExt(e, vaultCache, account, collateral, Liquidation.LTVType.BORROWING); - - uint256 collateralValue_liquidation = getCollateralValueExt(e, vaultCache, account, collateral, Liquidation.LTVType.LIQUIDATION); - - require collateralValue_liquidation > 0; - require collateralValue_borrowing > 0; - - assert collateralValue_borrowing <= collateralValue_liquidation; - -} \ No newline at end of file diff --git a/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec b/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec deleted file mode 100644 index 24a9869b..00000000 --- a/certora/specs/CER-161-Liquidation/CER-163-Liquidate.spec +++ /dev/null @@ -1,139 +0,0 @@ -/* -CER-163 / Verify EVK-7 -If operation enabled AND violator unhealthy, liquidate: - -liquidates the debt of the violator and transfers it to the liquidator, up to -the amount returned by checkLiquidation as per desired amount to be repaid -specified (repayAssets) - -seizes the collateral shares of the violator and transfers them to the -liquidator, up to the amount returned by checkLiquidation as per desired amount -to be repaid specified (repayAssets) - -If collateral is worthless, it can be seized without taking on any debt by the -liquidator. - -If operation enabled AND violator healthy, liquidate must be a no-op. - -If debt socialization enabled AND the violator has outstanding debt after the -liquidation AND the violator has no more accepted collaterals, the debt must be -socialized amongst all the lenders in the vault. - -liquidate must revert if: - - liquidate operation disabled - - violator is the same account as liquidator - - collateral is not accepted - - collateral is not enabled collateral for the violator - - liability vault is not enabled as the only controller of the violator - - liability vault is not enabled as the only controller of the liquidator - - violator account status check is deferred - - price oracle is not configured - - amount of collateral to be seized is less than the desired amount of - yieldspecified (minYieldBalance) - -This operation is always called through the EVC. -This operation schedules the account status check on the liquidator address. -This operation schedules the vault status check. - -Refer to the EVC documentation to learn how the collateral seizing mechanism -works: -*/ - -methods { - // envfree - function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; - function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; - function isCollateralEnabledExt(address account, address market) external returns (bool) envfree; - function isAccountStatusCheckDeferredExt(address account) external returns (bool) envfree; - function vaultIsOnlyController(address account) external returns (bool) envfree; - function vaultIsController(address account) external returns (bool) envfree; - - // IPriceOracle - function _.getQuote(uint256 amount, address base, address quote) external => CVLGetQuote(amount, base, quote) expect (uint256); - function _.getQuotes(uint256 amount, address base, address quote) external => CVLGetQuotes(amount, base, quote) expect (uint256, uint256); - - // Workaround for lack of ability to summarize metadata - // function ProxyUtils.metadata() internal returns (address, address, address)=> NONDET; - function Cache.loadVault() internal returns (Liquidation.VaultCache memory) => CVLLoadVault(); - - function _.requireVaultStatusCheck() external => NONDET; - function _.requireAccountAndVaultStatusCheck(address account) external => NONDET; - function _.calculateDTokenAddress() internal => NONDET; - function EVCClient.EVCRequireStatusChecks(address account) internal => NONDET; - function _.validateAndCallHook(Liquidation.Flags hookedOps, uint32 operation, address caller) internal => NONDET; - - // IERC20 - function _.name() external => DISPATCHER(true); - function _.symbol() external => DISPATCHER(true); - function _.decimals() external => DISPATCHER(true); - function _.totalSupply() external => DISPATCHER(true); - function _.balanceOf(address) external => DISPATCHER(true); - function _.allowance(address,address) external => DISPATCHER(true); - function _.approve(address,uint256) external => DISPATCHER(true); - function _.transfer(address,uint256) external => DISPATCHER(true); - function _.transferFrom(address,address,uint256) external => DISPATCHER(true); -} - -function CVLGetQuote(uint256 amount, address base, address quote) returns uint256 { - uint256 out; - return out; -} - -function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint256, uint256) { - uint256 bidOut; - uint256 askOut; - return (bidOut, askOut); -} - -function CVLLoadVault() returns Liquidation.VaultCache { - Liquidation.VaultCache vaultCache; - require vaultCache.oracle != 0; - return vaultCache; -} - -rule calculateLiquidation_setViolator { - env e; - Liquidation.VaultCache vaultCache; - address liquidator; - address violator; - address collateral; - uint256 desiredRepay; - LiquidationModule.LiquidationCache liqCache = calculateLiquidationExt(e, - vaultCache, - liquidator, - violator, - collateral, - desiredRepay); - assert liqCache.violator == violator; - assert liqCache.liquidator == liquidator; - assert violator != liquidator; -} - -rule liquidate_mustRevert { - env e; - address violator; - address collateral; - uint256 repayAssets; - uint256 minYieldBalance; - - address liquidator = getLiquidator(e); - bool selfLiquidation = violator == liquidator; - bool recognizedCollateral = isRecognizedCollateralExt(collateral); - bool enabledCollateral = isCollateralEnabledExt(violator, collateral); - bool violatorStatusCheckDeferred = isAccountStatusCheckDeferredExt(violator); - bool vaultControlsLiquidator = vaultIsController(liquidator); - bool vaultControlsViolator = vaultIsOnlyController(violator); - bool oracleConfigured = vaultCacheOracleConfigured(e); - - liquidate(e, violator, collateral, repayAssets, minYieldBalance); - // TODO liquidate operation not disabled - // TODO amount of collateral to be seized is less than the desired amount of - assert !selfLiquidation; - assert recognizedCollateral; - assert enabledCollateral; - assert vaultControlsLiquidator; - assert vaultControlsViolator; - assert !violatorStatusCheckDeferred; - assert oracleConfigured; -} - From 2ef76c447977ec0559a0357a80dd85697d6b9e12 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 18 Apr 2024 13:26:58 +0100 Subject: [PATCH 048/152] Delete vaults --- certora/conf/DELETE_Vault.conf | 30 ------------------- certora/harness/DELETE_VaultHarness.sol | 39 ------------------------- 2 files changed, 69 deletions(-) delete mode 100644 certora/conf/DELETE_Vault.conf delete mode 100644 certora/harness/DELETE_VaultHarness.sol diff --git a/certora/conf/DELETE_Vault.conf b/certora/conf/DELETE_Vault.conf deleted file mode 100644 index 85965b24..00000000 --- a/certora/conf/DELETE_Vault.conf +++ /dev/null @@ -1,30 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", - "src/EVault/modules/Vault.sol", - "certora/harness/VaultHarness.sol" - ], - "link": [ - "VaultHarness:evc=EthereumVaultConnector", - ], - "verify": "VaultHarness:certora/specs/Vault.spec", - "solc": "solc8.23", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - // "rule": [ - // "balance_forwarding_called_deposit" - // ], - "coverage_info" : "advanced", - "parametric_contracts": ["VaultHarness"], - "optimistic_loop": true, - "loop_iter": "2", - "solc_via_ir": true, - "solc_optimize": "10000", - "rule_sanity": "basic", - "function_finder_mode" : "relaxed", - "finder_friendly_optimizer" : false, -} \ No newline at end of file diff --git a/certora/harness/DELETE_VaultHarness.sol b/certora/harness/DELETE_VaultHarness.sol deleted file mode 100644 index 763047b3..00000000 --- a/certora/harness/DELETE_VaultHarness.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.8.0; -import "../../src/EVault/modules/Vault.sol"; - -contract VaultHarness is Vault { - constructor(Integrations memory integrations) Vault(integrations) {} - function isOperationDisabledExt(uint32 operation) public returns (bool) { - // This is based on the check in callHook. - VaultCache memory vaultCache = updateVault(); - return vaultCache.hookedOps.isNotSet(operation); - } - - function isDepositDisabled() public returns (bool) { - return isOperationDisabledExt(OP_DEPOSIT); - } - - function isMintDisabled() public returns (bool) { - return isOperationDisabledExt(OP_MINT); - } - - function isWithdrawDisabled() public returns (bool) { - return isOperationDisabledExt(OP_WITHDRAW); - } - - function isRedeemDisabled() public returns (bool) { - return isOperationDisabledExt(OP_REDEEM); - } - - function isSkimDisabled() public returns (bool) { - return isOperationDisabledExt(OP_SKIM); - } - - function getBalanceAndForwarderExt(address account) public returns (Shares, bool) { - return vaultStorage.users[account].getBalanceAndBalanceForwarder(); - } - - -} \ No newline at end of file From 691965eb37dce6ae45ac6e0c103855fcaaf00d2e Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 18 Apr 2024 14:09:21 +0100 Subject: [PATCH 049/152] Add missing liquidation files --- certora/conf/Liquidation.conf | 40 ++++++++ certora/specs/Liquidation.spec | 174 +++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 certora/conf/Liquidation.conf create mode 100644 certora/specs/Liquidation.spec diff --git a/certora/conf/Liquidation.conf b/certora/conf/Liquidation.conf new file mode 100644 index 00000000..7b56e1ed --- /dev/null +++ b/certora/conf/Liquidation.conf @@ -0,0 +1,40 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "src/EVault/modules/Liquidation.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/LiquidationHarness.sol" + ], + "link": [ + "LiquidationHarness:evc=EthereumVaultConnector", + ], + "verify": "LiquidationHarness:certora/specs/Liquidation.spec", + "solc": "solc8.23", + "msg": "Liquidation benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + // "rule": [ + // "getCollateralValue_borrowing_lower" + // ], + "parametric_contracts": ["LiquidationHarness"], + "rule_sanity": "basic", + "prover_version": "master", + "server" : "production", + // "coverage_info" : "advanced", + // Performance tuing options below this line + "prover_args": [ + "-depth 10", + "-smt_nonLinearArithmetic true", + "-adaptiveSolverConfig false" + ], + "function_finder_mode": "relaxed", + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "smt_timeout": "7000" +} \ No newline at end of file diff --git a/certora/specs/Liquidation.spec b/certora/specs/Liquidation.spec new file mode 100644 index 00000000..9912c5ab --- /dev/null +++ b/certora/specs/Liquidation.spec @@ -0,0 +1,174 @@ +/* +CER-162 / Verify EVK-31 +If violator unhealthy, checkLiquidation returns the maximum amount of the debt +asset the liquidator is allowed to liquidate (maxRepay) in exchange for the +returned maximum amount of collateral shares from violator (maxYield). + +If violator healthy, checkLiquidation returns maxRepay and maxYield as 0. + +Unless violator healthy, considering the liquidator bonus is positive and grows +linearly as the health of the violator deteriorates, the value of maxYield is +greater than the value of maxRepay. + +If needed, checkLiquidation must limit the maxRepay as per available amount of +collateral to be seized from the violator. + +If needed, checkLiquidation must limit the maxRepay and the maxYield as per +desired amount to be repaid (desiredRepay) parameter. + +checkLiquidation must revert if: + - violator is the same account as liquidator + - collateral is not accepted + - collateral is not enabled collateral for the violator + - liability vault is not enabled as the only controller of the violator + - violator account status check is deferred + - price oracle is not configured + - price oracle is not configured +*/ + +import "Base.spec"; +methods { + function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; +} + +// passing +rule checkLiquidation_healthy() { + env e; + address liquidator; + address violator; + address collateral; + uint256 maxRepay; + uint256 maxYield; + + require oracleAddress != 0; + + uint256 liquidityCollateralValue; + uint256 liquidityLiabilityValue; + (liquidityCollateralValue, liquidityLiabilityValue) = + calculateLiquidityExternal(e, violator); + + require liquidityCollateralValue > liquidityLiabilityValue; + + (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); + + assert maxRepay == 0; + assert maxYield == 0; +} + +// counterexample +rule checkLiquidation_maxYieldGreater { + env e; + address liquidator; + address violator; + address collateral; + uint256 maxRepay; + uint256 maxYield; + + uint256 collateralValue; + uint256 liabilityValue; + (collateralValue, liabilityValue) = + calculateLiquidityExternal(e, violator); + + require oracleAddress != 0; + require collateralValue > 0; + require liabilityValue > 0; + require collateralValue < liabilityValue; + + (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); + assert maxRepay > 0 => maxRepay <= maxYield; +} + +// passing +rule checkLiquidation_mustRevert { + env e; + address liquidator; + address violator; + address collateral; + uint256 maxRepay; + uint256 maxYield; + + require oracleAddress != 0; + bool selfLiquidate = liquidator == violator; + bool badCollateral = !isRecognizedCollateralExt(collateral); + bool enabledCollateral = isCollateralEnabledExt(violator, collateral); + bool vaultControlsViolator = vaultIsOnlyController(violator); + bool violatorStatusCheckDeferred = isAccountStatusCheckDeferredExt(violator); + bool oracleConfigured = vaultCacheOracleConfigured(e); + + (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); + + assert !selfLiquidate; + assert !badCollateral; + assert enabledCollateral; + assert vaultControlsViolator; + assert !violatorStatusCheckDeferred; + assert oracleConfigured; + +} + +// Passing. Assumptions can be reduced with Euler's fix. +rule getCollateralValue_borrowing_lower { + env e; + Liquidation.VaultCache vaultCache; + address account; + address collateral; + + require LTVConfigAssumptions(e, getLTVConfig(e, collateral)); + + uint256 collateralValue_borrowing = getCollateralValueExt(e, vaultCache, account, collateral, Liquidation.LTVType.BORROWING); + + uint256 collateralValue_liquidation = getCollateralValueExt(e, vaultCache, account, collateral, Liquidation.LTVType.LIQUIDATION); + + require collateralValue_liquidation > 0; + require collateralValue_borrowing > 0; + + assert collateralValue_borrowing <= collateralValue_liquidation; + +} + +rule calculateLiquidation_setViolator { + env e; + Liquidation.VaultCache vaultCache; + address liquidator; + address violator; + address collateral; + uint256 desiredRepay; + LiquidationModule.LiquidationCache liqCache = calculateLiquidationExt(e, + vaultCache, + liquidator, + violator, + collateral, + desiredRepay); + assert liqCache.violator == violator; + assert liqCache.liquidator == liquidator; + assert violator != liquidator; +} + +rule liquidate_mustRevert { + env e; + address violator; + address collateral; + uint256 repayAssets; + uint256 minYieldBalance; + + address liquidator = getLiquidator(e); + bool selfLiquidation = violator == liquidator; + bool recognizedCollateral = isRecognizedCollateralExt(collateral); + bool enabledCollateral = isCollateralEnabledExt(violator, collateral); + bool violatorStatusCheckDeferred = isAccountStatusCheckDeferredExt(violator); + bool vaultControlsLiquidator = vaultIsController(liquidator); + bool vaultControlsViolator = vaultIsOnlyController(violator); + bool oracleConfigured = vaultCacheOracleConfigured(e); + + liquidate(e, violator, collateral, repayAssets, minYieldBalance); + // TODO liquidate operation not disabled + // TODO amount of collateral to be seized is less than the desired amount of + assert !selfLiquidation; + assert recognizedCollateral; + assert enabledCollateral; + assert vaultControlsLiquidator; + assert vaultControlsViolator; + assert !violatorStatusCheckDeferred; + assert oracleConfigured; +} + From 3228018bb406da2db8246b32774a46a595f0412b Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 18 Apr 2024 14:11:10 +0100 Subject: [PATCH 050/152] comment about status in Liquidation --- certora/specs/Liquidation.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/certora/specs/Liquidation.spec b/certora/specs/Liquidation.spec index 9912c5ab..cd2e4acf 100644 --- a/certora/specs/Liquidation.spec +++ b/certora/specs/Liquidation.spec @@ -144,6 +144,7 @@ rule calculateLiquidation_setViolator { assert violator != liquidator; } +// formerly passing but broke. must fix rule liquidate_mustRevert { env e; address violator; From 9cb542746a86c66cc98cbc30d2273c572872c77c Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 18 Apr 2024 15:08:30 +0100 Subject: [PATCH 051/152] Lots of refactoring, making harnesses for new pattern --- certora/conf/BalanceForwarder.conf | 29 -------------- .../conf/EVault/modules/BalanceForwarder.conf | 24 +++++++---- certora/conf/EVault/modules/Borrowing.conf | 18 ++++++--- certora/conf/EVault/modules/Governance.conf | 18 ++++++--- certora/conf/EVault/modules/Initialize.conf | 14 +++++-- certora/conf/EVault/modules/Liquidation.conf | 26 +++++++----- certora/conf/EVault/modules/RiskManager.conf | 35 ++++++++++++---- certora/conf/EVault/modules/Token.conf | 16 +++++--- certora/conf/EVault/modules/Vault.conf | 18 ++++++--- certora/conf/Liquidation.conf | 40 ------------------- certora/conf/RiskManager.conf | 40 ------------------- certora/harness/BalanceForwarderHarness.sol | 12 ------ certora/harness/RiskManagerHarness.sol | 12 ------ .../modules/BalanceForwarderHarness.sol | 10 +++++ certora/harness/modules/BorrowingHarness.sol | 9 +++++ certora/harness/modules/GovernanceHarness.sol | 9 +++++ certora/harness/modules/InitializeHarness.sol | 9 +++++ .../{ => modules}/LiquidationHarness.sol | 11 ++--- .../harness/modules/RiskManagerHarness.sol | 10 +++++ certora/harness/modules/TokenHarness.sol | 9 +++++ certora/harness/modules/VaultHarness.sol | 9 +++++ 21 files changed, 187 insertions(+), 191 deletions(-) delete mode 100644 certora/conf/BalanceForwarder.conf delete mode 100644 certora/conf/Liquidation.conf delete mode 100644 certora/conf/RiskManager.conf delete mode 100644 certora/harness/BalanceForwarderHarness.sol delete mode 100644 certora/harness/RiskManagerHarness.sol create mode 100644 certora/harness/modules/BalanceForwarderHarness.sol create mode 100644 certora/harness/modules/BorrowingHarness.sol create mode 100644 certora/harness/modules/GovernanceHarness.sol create mode 100644 certora/harness/modules/InitializeHarness.sol rename certora/harness/{ => modules}/LiquidationHarness.sol (77%) create mode 100644 certora/harness/modules/RiskManagerHarness.sol create mode 100644 certora/harness/modules/TokenHarness.sol create mode 100644 certora/harness/modules/VaultHarness.sol diff --git a/certora/conf/BalanceForwarder.conf b/certora/conf/BalanceForwarder.conf deleted file mode 100644 index e94d99c1..00000000 --- a/certora/conf/BalanceForwarder.conf +++ /dev/null @@ -1,29 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", - "src/EVault/modules/BalanceForwarder.sol", - "certora/harness/BaseHarness.sol", - "certora/harness/BalanceForwarderHarness.sol", - ], - "link": [ - "BalanceForwarderHarness:evc=EthereumVaultConnector", - ], - "verify": "BalanceForwarderHarness:certora/specs/BalanceForwarder.spec", - "solc": "solc8.23", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "prover_args": ["-smt_bitVectorTheory", "true"], - // "coverage_info" : "advanced", - "parametric_contracts": ["BalanceForwarder"], - "optimistic_loop": true, - "loop_iter": "2", - "solc_via_ir": true, - "solc_optimize": "10000", - "rule_sanity": "basic", - "function_finder_mode" : "relaxed", - "finder_friendly_optimizer" : false, -} \ No newline at end of file diff --git a/certora/conf/EVault/modules/BalanceForwarder.conf b/certora/conf/EVault/modules/BalanceForwarder.conf index 757d2fe8..0f48b4d3 100644 --- a/certora/conf/EVault/modules/BalanceForwarder.conf +++ b/certora/conf/EVault/modules/BalanceForwarder.conf @@ -2,20 +2,30 @@ "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "src/EVault/modules/BalanceForwarder.sol" + "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "src/EVault/modules/BalanceForwarder.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/BalanceForwarderHarness.sol", ], "link": [ - "BalanceForwarder:evc=EthereumVaultConnector", + "BalanceForwarderHarness:evc=EthereumVaultConnector", ], - "verify": "BalanceForwarder:certora/specs/benchmarking/Benchmarking.spec", + "verify": "BalanceForwarderHarness:certora/specs/BalanceForwarder.spec", "solc": "solc8.23", - "solc_via_ir": true, - "solc_optimize": "10000", - "rule_sanity": "basic", - "msg": "BalanceForwarder benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "prover_version": "master", + "server" : "production", + // "coverage_info" : "advanced", "parametric_contracts": ["BalanceForwarder"], + "prover_args": ["-smt_bitVectorTheory", "true"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Borrowing.conf b/certora/conf/EVault/modules/Borrowing.conf index 43b9cf11..539fb293 100644 --- a/certora/conf/EVault/modules/Borrowing.conf +++ b/certora/conf/EVault/modules/Borrowing.conf @@ -2,20 +2,26 @@ "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "src/EVault/modules/Borrowing.sol" + "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "src/EVault/modules/Borrowing.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/modules/BorrowingHarness.sol", ], "link": [ - "Borrowing:evc=EthereumVaultConnector", + "BorrowingHarness:evc=EthereumVaultConnector", ], - "verify": "Borrowing:certora/specs/benchmarking/Benchmarking.spec", + "verify": "BorrowingHarness:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", - "solc_via_ir": true, - "solc_optimize": "10000", "rule_sanity": "basic", "msg": "Borrowing benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "parametric_contracts": ["Borrowing"] + "parametric_contracts": ["BorrowingHarness"], + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Governance.conf b/certora/conf/EVault/modules/Governance.conf index 47488184..0b2e0d37 100644 --- a/certora/conf/EVault/modules/Governance.conf +++ b/certora/conf/EVault/modules/Governance.conf @@ -2,21 +2,27 @@ "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", "src/ProtocolConfig/ProtocolConfig.sol", - "src/EVault/modules/Governance.sol" + "src/EVault/modules/Governance.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/modules/GovernanceHarness.sol", ], "link": [ - "Governance:protocolConfig=ProtocolConfig", - "Governance:evc=EthereumVaultConnector" + "GovernanceHarness:protocolConfig=ProtocolConfig", + "GovernanceHarness:evc=EthereumVaultConnector" ], - "verify": "Governance:certora/specs/benchmarking/Benchmarking.spec", + "verify": "GovernanceHarness:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", - "solc_via_ir": true, - "solc_optimize": "10000", "rule_sanity": "basic", "msg": "Governance benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Initialize.conf b/certora/conf/EVault/modules/Initialize.conf index 205c3458..c491daf3 100644 --- a/certora/conf/EVault/modules/Initialize.conf +++ b/certora/conf/EVault/modules/Initialize.conf @@ -1,8 +1,16 @@ { "files": [ - "src/EVault/modules/Initialize.sol" + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "src/EVault/modules/Initialize.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/modules/InitializeHarness.sol", ], - "verify": "Initialize:certora/specs/benchmarking/Benchmarking.spec", + "link": [ + "InitializeHarness:evc=EthereumVaultConnector", + ], + "verify": "InitializeHarness:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", "solc_via_ir": true, "solc_optimize": "10000", @@ -12,5 +20,5 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "parametric_contracts": ["Initialize"], + "parametric_contracts": ["InitializeHarness"], } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index 99a6bab0..7d5674b9 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -2,14 +2,15 @@ "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", - // "src/EVault/shared/types/Types.sol", + "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", "src/EVault/modules/Liquidation.sol", - "certora/harness/LiquidationHarness.sol" + "certora/harness/BaseHarness.sol", + "certora/harness/modules/LiquidationHarness.sol" ], "link": [ "LiquidationHarness:evc=EthereumVaultConnector", ], - "verify": "LiquidationHarness:certora/specs/EVault/modules/Liquidation.spec", + "verify": "LiquidationHarness:certora/specs/Liquidation.spec", "solc": "solc8.23", "msg": "Liquidation benchmarking", "packages": [ @@ -17,16 +18,23 @@ "forge-std=lib/forge-std/src" ], // "rule": [ - // "checkLiquidation_maxYieldGreater" + // "getCollateralValue_borrowing_lower" // ], - // "coverage_info" : "advanced", "parametric_contracts": ["LiquidationHarness"], + "rule_sanity": "basic", + "prover_version": "master", + "server" : "production", + // "coverage_info" : "advanced", + // Performance tuing options below this line + "prover_args": [ + "-depth 10", + "-smt_nonLinearArithmetic true", + "-adaptiveSolverConfig false" + ], + "function_finder_mode": "relaxed", "optimistic_loop": true, "loop_iter": "2", "solc_via_ir": true, "solc_optimize": "10000", - "rule_sanity": "basic", - "function_finder_mode" : "relaxed", - "finder_friendly_optimizer" : "no", - "prover_version" : "master" // need for dev fix + "smt_timeout": "7000" } \ No newline at end of file diff --git a/certora/conf/EVault/modules/RiskManager.conf b/certora/conf/EVault/modules/RiskManager.conf index c6cb3380..bd7379db 100644 --- a/certora/conf/EVault/modules/RiskManager.conf +++ b/certora/conf/EVault/modules/RiskManager.conf @@ -2,20 +2,39 @@ "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "src/EVault/modules/RiskManager.sol" + "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "src/EVault/modules/RiskManager.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/modules/RiskManagerHarness.sol", ], "link": [ - "RiskManager:evc=EthereumVaultConnector", + "RiskManagerHarness:evc=EthereumVaultConnector", ], - "verify": "RiskManager:certora/specs/benchmarking/Benchmarking.spec", + "verify": "RiskManagerHarness:certora/specs/RiskManager.spec", "solc": "solc8.23", - "solc_via_ir": true, - "solc_optimize": "10000", - "rule_sanity": "basic", - "msg": "RiskManager benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "parametric_contracts": ["RiskManager"], + // "rule": [ + // "ltv_borrowing_lower" + // // "liquidations_equal_for_one" + // ], + "parametric_contracts": ["RiskManagerHarness"], + "rule_sanity": "basic", + "server": "production", + // "coverage_info" : "advanced", + // Performance tuning options below this line + "prover_args": [ + "-depth 0", + "-smt_nonLinearArithmetic true", + "-adaptiveSolverConfig false" + ], + "function_finder_mode": "relaxed", + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "smt_timeout": "7000", + "prover_version": "master" } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Token.conf b/certora/conf/EVault/modules/Token.conf index 08621bad..a34610db 100644 --- a/certora/conf/EVault/modules/Token.conf +++ b/certora/conf/EVault/modules/Token.conf @@ -2,20 +2,24 @@ "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "src/EVault/modules/Token.sol" + "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "src/EVault/modules/Token.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/modules/TokenHarness.sol", ], "link" : [ - "Token:evc=EthereumVaultConnector", + "TokenHarness:evc=EthereumVaultConnector", ], - "parametric_contracts": ["Token"], - "verify": "Token:certora/specs/EVault/modules/Token.spec", + "parametric_contracts": ["TokenHarness"], + "verify": "TokenHarness:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", - "solc_via_ir": true, - "solc_optimize": "10000", "rule_sanity": "basic", "msg": "Token benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Vault.conf b/certora/conf/EVault/modules/Vault.conf index c863b279..52b0fab7 100644 --- a/certora/conf/EVault/modules/Vault.conf +++ b/certora/conf/EVault/modules/Vault.conf @@ -2,20 +2,26 @@ "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "src/EVault/modules/Vault.sol" + "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "src/EVault/modules/Vault.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/modules/VaultHarness.sol", ], "link" : [ - "Vault:evc=EthereumVaultConnector", + "VaultHarness:evc=EthereumVaultConnector", ], - "verify": "Vault:certora/specs/benchmarking/Benchmarking.spec", + "verify": "VaultHarness:certora/specs/benchmarking/Benchmarking.spec", "solc": "solc8.23", - "solc_via_ir": true, - "solc_optimize": "10000", "rule_sanity": "basic", "msg": "Vault benchmarking", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "parametric_contracts": ["Vault"], + "parametric_contracts": ["VaultHarness"], + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", } \ No newline at end of file diff --git a/certora/conf/Liquidation.conf b/certora/conf/Liquidation.conf deleted file mode 100644 index 7b56e1ed..00000000 --- a/certora/conf/Liquidation.conf +++ /dev/null @@ -1,40 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", - "src/EVault/modules/Liquidation.sol", - "certora/harness/BaseHarness.sol", - "certora/harness/LiquidationHarness.sol" - ], - "link": [ - "LiquidationHarness:evc=EthereumVaultConnector", - ], - "verify": "LiquidationHarness:certora/specs/Liquidation.spec", - "solc": "solc8.23", - "msg": "Liquidation benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - // "rule": [ - // "getCollateralValue_borrowing_lower" - // ], - "parametric_contracts": ["LiquidationHarness"], - "rule_sanity": "basic", - "prover_version": "master", - "server" : "production", - // "coverage_info" : "advanced", - // Performance tuing options below this line - "prover_args": [ - "-depth 10", - "-smt_nonLinearArithmetic true", - "-adaptiveSolverConfig false" - ], - "function_finder_mode": "relaxed", - "optimistic_loop": true, - "loop_iter": "2", - "solc_via_ir": true, - "solc_optimize": "10000", - "smt_timeout": "7000" -} \ No newline at end of file diff --git a/certora/conf/RiskManager.conf b/certora/conf/RiskManager.conf deleted file mode 100644 index 3f6ce3b3..00000000 --- a/certora/conf/RiskManager.conf +++ /dev/null @@ -1,40 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", - "src/EVault/modules/RiskManager.sol", - "certora/harness/BaseHarness.sol", - "certora/harness/RiskManagerHarness.sol", - ], - "link": [ - "RiskManagerHarness:evc=EthereumVaultConnector", - ], - "verify": "RiskManagerHarness:certora/specs/RiskManager.spec", - "solc": "solc8.23", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - // "rule": [ - // "ltv_borrowing_lower" - // // "liquidations_equal_for_one" - // ], - "parametric_contracts": ["RiskManagerHarness"], - "rule_sanity": "basic", - "server": "production", - // "coverage_info" : "advanced", - // Performance tuning options below this line - "prover_args": [ - "-depth 0", - "-smt_nonLinearArithmetic true", - "-adaptiveSolverConfig false" - ], - "function_finder_mode": "relaxed", - "optimistic_loop": true, - "loop_iter": "2", - "solc_via_ir": true, - "solc_optimize": "10000", - "smt_timeout": "7000", - "prover_version": "master" -} \ No newline at end of file diff --git a/certora/harness/BalanceForwarderHarness.sol b/certora/harness/BalanceForwarderHarness.sol deleted file mode 100644 index cf01e20b..00000000 --- a/certora/harness/BalanceForwarderHarness.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; -import "../../src/EVault/modules/BalanceForwarder.sol"; -import "../../src/EVault/shared/types/Types.sol"; -import "../../src/interfaces/IPriceOracle.sol"; -import {IERC20} from "../../src/EVault/IEVault.sol"; -import {ERC20} from "../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; -import "../../certora/harness/AbstractBaseHarness.sol"; - -contract BalanceForwarderHarness is BalanceForwarder, AbstractBaseHarness { - constructor(Integrations memory integrations) BalanceForwarder(integrations) {} -} \ No newline at end of file diff --git a/certora/harness/RiskManagerHarness.sol b/certora/harness/RiskManagerHarness.sol deleted file mode 100644 index c3d57d83..00000000 --- a/certora/harness/RiskManagerHarness.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; -import "../../src/EVault/modules/RiskManager.sol"; -import "../../src/EVault/shared/types/Types.sol"; -import "../../src/interfaces/IPriceOracle.sol"; -import {IERC20} from "../../src/EVault/IEVault.sol"; -import {ERC20} from "../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; -import "../../certora/harness/AbstractBaseHarness.sol"; - -contract RiskManagerHarness is RiskManager, AbstractBaseHarness { - constructor(Integrations memory integrations) RiskManager(integrations) {} -} \ No newline at end of file diff --git a/certora/harness/modules/BalanceForwarderHarness.sol b/certora/harness/modules/BalanceForwarderHarness.sol new file mode 100644 index 00000000..53709dbe --- /dev/null +++ b/certora/harness/modules/BalanceForwarderHarness.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import "../../../src/interfaces/IPriceOracle.sol"; +import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/BalanceForwarder.sol"; + +contract BalanceForwarderHarness is BalanceForwarder, AbstractBaseHarness { + constructor(Integrations memory integrations) BalanceForwarder(integrations) {} +} \ No newline at end of file diff --git a/certora/harness/modules/BorrowingHarness.sol b/certora/harness/modules/BorrowingHarness.sol new file mode 100644 index 00000000..0fde5614 --- /dev/null +++ b/certora/harness/modules/BorrowingHarness.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/Borrowing.sol"; + +contract BorrowingHarness is Borrowing, AbstractBaseHarness { + constructor(Integrations memory integrations) Borrowing (integrations) {} +} \ No newline at end of file diff --git a/certora/harness/modules/GovernanceHarness.sol b/certora/harness/modules/GovernanceHarness.sol new file mode 100644 index 00000000..d8267a7b --- /dev/null +++ b/certora/harness/modules/GovernanceHarness.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/Governance.sol"; + +contract GovernanceHarness is Governance, AbstractBaseHarness { + constructor(Integrations memory integrations) Governance (integrations) {} +} \ No newline at end of file diff --git a/certora/harness/modules/InitializeHarness.sol b/certora/harness/modules/InitializeHarness.sol new file mode 100644 index 00000000..f71b9ce7 --- /dev/null +++ b/certora/harness/modules/InitializeHarness.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/Initialize.sol"; + +contract InitializeHarness is Initialize, AbstractBaseHarness { + constructor(Integrations memory integrations) Initialize(integrations) {} +} \ No newline at end of file diff --git a/certora/harness/LiquidationHarness.sol b/certora/harness/modules/LiquidationHarness.sol similarity index 77% rename from certora/harness/LiquidationHarness.sol rename to certora/harness/modules/LiquidationHarness.sol index 7e6c1aa2..00ab89c7 100644 --- a/certora/harness/LiquidationHarness.sol +++ b/certora/harness/modules/LiquidationHarness.sol @@ -1,13 +1,10 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import "../../src/EVault/shared/types/Types.sol"; -import "../../src/EVault/modules/Liquidation.sol"; -import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; -import "../../src/interfaces/IPriceOracle.sol"; -import {IERC20} from "../../src/EVault/IEVault.sol"; -import {ERC20} from "../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; -import "../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/interfaces/IPriceOracle.sol"; +import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/Liquidation.sol"; contract LiquidationHarness is AbstractBaseHarness, Liquidation { diff --git a/certora/harness/modules/RiskManagerHarness.sol b/certora/harness/modules/RiskManagerHarness.sol new file mode 100644 index 00000000..568dc9bf --- /dev/null +++ b/certora/harness/modules/RiskManagerHarness.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import "../../../src/interfaces/IPriceOracle.sol"; +import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/RiskManager.sol"; + +contract RiskManagerHarness is RiskManager, AbstractBaseHarness { + constructor(Integrations memory integrations) RiskManager(integrations) {} +} \ No newline at end of file diff --git a/certora/harness/modules/TokenHarness.sol b/certora/harness/modules/TokenHarness.sol new file mode 100644 index 00000000..9c9fa98e --- /dev/null +++ b/certora/harness/modules/TokenHarness.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/Token.sol"; + +contract TokenHarness is Token, AbstractBaseHarness { + constructor(Integrations memory integrations) Token(integrations) {} +} \ No newline at end of file diff --git a/certora/harness/modules/VaultHarness.sol b/certora/harness/modules/VaultHarness.sol new file mode 100644 index 00000000..46a6403b --- /dev/null +++ b/certora/harness/modules/VaultHarness.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/Vault.sol"; + +contract VaultHarness is Vault, AbstractBaseHarness { + constructor(Integrations memory integrations) Vault(integrations) {} +} \ No newline at end of file From 29f13731d8b6b3179d31164bacca362ff502befb Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Apr 2024 11:58:15 +0100 Subject: [PATCH 052/152] ERC4626 compiles with Vault+Token --- certora/conf/EVaultERC4626.conf | 29 ++ certora/conf/VaultERC4626.conf | 37 +++ certora/harness/ERC4626Harness.sol | 19 ++ certora/specs/EvaultERC4626.spec | 480 ++++++++++++++++++++++++++++ certora/specs/VaultERC4626.spec | 489 +++++++++++++++++++++++++++++ 5 files changed, 1054 insertions(+) create mode 100644 certora/conf/EVaultERC4626.conf create mode 100644 certora/conf/VaultERC4626.conf create mode 100644 certora/harness/ERC4626Harness.sol create mode 100644 certora/specs/EvaultERC4626.spec create mode 100644 certora/specs/VaultERC4626.spec diff --git a/certora/conf/EVaultERC4626.conf b/certora/conf/EVaultERC4626.conf new file mode 100644 index 00000000..8dacc5c0 --- /dev/null +++ b/certora/conf/EVaultERC4626.conf @@ -0,0 +1,29 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", + // "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "certora/harness/EVaultHarness.sol", + + ], + "link" : [ + "EVaultHarness:evc=EthereumVaultConnector", + ], + "verify": "EVaultHarness:certora/specs/EVaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "parametric_contracts": ["EVaultHarness"], + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "build_cache": true, + "server": "production", + "prover_version": "master" +} \ No newline at end of file diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf new file mode 100644 index 00000000..6125d0ab --- /dev/null +++ b/certora/conf/VaultERC4626.conf @@ -0,0 +1,37 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", + // "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "src/EVault/modules/Vault.sol", + "src/EVault/modules/Token.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "link" : [ + "ERC4626Harness:evc=EthereumVaultConnector", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault benchmarking", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + // options borrowed from Liquidation + "prover_args": [ + "-depth 10", + "-smt_nonLinearArithmetic true", + "-adaptiveSolverConfig false" + ], + "smt_timeout": "7000" +} \ No newline at end of file diff --git a/certora/harness/ERC4626Harness.sol b/certora/harness/ERC4626Harness.sol new file mode 100644 index 00000000..17cc6f51 --- /dev/null +++ b/certora/harness/ERC4626Harness.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; +// import {IERC20} from "../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "../../certora/harness/AbstractBaseHarness.sol"; +import "../../src/EVault/modules/Vault.sol"; +import "../../src/EVault/modules/Token.sol"; + +contract ERC4626Harness is VaultModule, TokenModule, AbstractBaseHarness { + constructor(Integrations memory integrations) Base(integrations) {} + + // function totalAssets() public view override returns (uint256) { + // return asset.balanceOf(address(this)); + // } + + function userAssets(address user) public view returns (uint256) { // harnessed + return IERC20(asset()).balanceOf(user); + } +} diff --git a/certora/specs/EvaultERC4626.spec b/certora/specs/EvaultERC4626.spec new file mode 100644 index 00000000..2b96f7d9 --- /dev/null +++ b/certora/specs/EvaultERC4626.spec @@ -0,0 +1,480 @@ +/* + * This is a specification file to formally verify BorrowSystem.sol + * smart contract using the Certora Prover. For more information, + * visit: https://www.certora.com/ + * + */ + + +// reference from the spec to additional contracts used in the verification + +// using DummyERC20A as ERC20a; +// using DummyERC20B as ERC20b; +using EVaultHarness as ERC20a; + +/* + Declaration of methods that are used in the rules. envfree indicate that + the method is not dependent on the environment (msg.value, msg.sender). + Methods that are not declared here are assumed to be dependent on env. +*/ +methods { + function name() external returns string envfree; + function symbol() external returns string envfree; + function decimals() external returns uint8 envfree; + function asset() external returns address envfree; + + function totalSupply() external returns uint256 envfree; + function balanceOf(address) external returns uint256 envfree; + // Not implemented by EVault + // function nonces(address) external returns uint256 envfree; + + function approve(address,uint256) external returns bool; + function deposit(uint256,address) external; + function mint(uint256,address) external; + function withdraw(uint256,address,address) external; + function redeem(uint256,address,address) external; + + function totalAssets() external returns uint256 envfree; + // Not implemented by EVault + // function userAssets(address) external returns uint256 envfree; + function convertToShares(uint256) external returns uint256 envfree; + function convertToAssets(uint256) external returns uint256 envfree; + function previewDeposit(uint256) external returns uint256 envfree; + function previewMint(uint256) external returns uint256 envfree; + function previewWithdraw(uint256) external returns uint256 envfree; + function previewRedeem(uint256) external returns uint256 envfree; + + function maxDeposit(address) external returns uint256 envfree; + function maxMint(address) external returns uint256 envfree; + function maxWithdraw(address) external returns uint256 envfree; + function maxRedeem(address) external returns uint256 envfree; + + function permit(address,address,uint256,uint256,uint8,bytes32,bytes32) external; + function DOMAIN_SEPARATOR() external returns bytes32; + + //// #ERC20 methods + function _.balanceOf(address) external => DISPATCHER(true); + function _.transfer(address,uint256) external => DISPATCHER(true); + function _.transferFrom(address,address,uint256) external => DISPATCHER(true); + + function ERC20a.balanceOf(address) external returns uint256 envfree; + function ERC20a.transferFrom(address,address,uint256) external returns bool; +} + + + +//////////////////////////////////////////////////////////////////////////////// +//// # asset To shares mathematical properties ///// +//////////////////////////////////////////////////////////////////////////////// + +rule conversionOfZero { + uint256 convertZeroShares = convertToAssets(0); + uint256 convertZeroAssets = convertToShares(0); + + assert convertZeroShares == 0, + "converting zero shares must return zero assets"; + assert convertZeroAssets == 0, + "converting zero assets must return zero shares"; +} + +rule convertToAssetsWeakAdditivity() { + uint256 sharesA; uint256 sharesB; + require sharesA + sharesB < max_uint128 + && convertToAssets(sharesA) + convertToAssets(sharesB) < to_mathint(max_uint256) + && convertToAssets(require_uint256(sharesA + sharesB)) < max_uint256; + assert convertToAssets(sharesA) + convertToAssets(sharesB) <= to_mathint(convertToAssets(require_uint256(sharesA + sharesB))), + "converting sharesA and sharesB to assets then summing them must yield a smaller or equal result to summing them then converting"; +} + +rule convertToSharesWeakAdditivity() { + uint256 assetsA; uint256 assetsB; + require assetsA + assetsB < max_uint128 + && convertToAssets(assetsA) + convertToAssets(assetsB) < to_mathint(max_uint256) + && convertToAssets(require_uint256(assetsA + assetsB)) < max_uint256; + assert convertToAssets(assetsA) + convertToAssets(assetsB) <= to_mathint(convertToAssets(require_uint256(assetsA + assetsB))), + "converting assetsA and assetsB to shares then summing them must yield a smaller or equal result to summing them then converting"; +} + +rule conversionWeakMonotonicity { + uint256 smallerShares; uint256 largerShares; + uint256 smallerAssets; uint256 largerAssets; + + assert smallerShares < largerShares => convertToAssets(smallerShares) <= convertToAssets(largerShares), + "converting more shares must yield equal or greater assets"; + assert smallerAssets < largerAssets => convertToShares(smallerAssets) <= convertToShares(largerAssets), + "converting more assets must yield equal or greater shares"; +} + +rule conversionWeakIntegrity() { + uint256 sharesOrAssets; + assert convertToShares(convertToAssets(sharesOrAssets)) <= sharesOrAssets, + "converting shares to assets then back to shares must return shares less than or equal to the original amount"; + assert convertToAssets(convertToShares(sharesOrAssets)) <= sharesOrAssets, + "converting assets to shares then back to assets must return assets less than or equal to the original amount"; +} + +rule convertToCorrectness(uint256 amount, uint256 shares) +{ + assert amount >= convertToAssets(convertToShares(amount)); + assert shares >= convertToShares(convertToAssets(shares)); +} + + +//////////////////////////////////////////////////////////////////////////////// +//// # Unit Test ///// +//////////////////////////////////////////////////////////////////////////////// + +rule depositMonotonicity() { + env e; storage start = lastStorage; + + uint256 smallerAssets; uint256 largerAssets; + address receiver; + require currentContract != e.msg.sender && currentContract != receiver; + + safeAssumptions(e, e.msg.sender, receiver); + + deposit(e, smallerAssets, receiver); + uint256 smallerShares = balanceOf(receiver) ; + + deposit(e, largerAssets, receiver) at start; + uint256 largerShares = balanceOf(receiver) ; + + assert smallerAssets < largerAssets => smallerShares <= largerShares, + "when supply tokens outnumber asset tokens, a larger deposit of assets must produce an equal or greater number of shares"; +} + + +rule zeroDepositZeroShares(uint assets, address receiver) +{ + env e; + + uint shares = deposit(e,assets, receiver); + + assert shares == 0 <=> assets == 0; +} + +//////////////////////////////////////////////////////////////////////////////// +//// # Valid State ///// +//////////////////////////////////////////////////////////////////////////////// + +invariant assetsMoreThanSupply() + totalAssets() >= totalSupply() + { + preserved with (env e) { + require e.msg.sender != currentContract; + address any; + safeAssumptions(e, any , e.msg.sender); + } + } + +invariant noAssetsIfNoSupply() + ( userAssets(currentContract) == 0 => totalSupply() == 0 ) && + ( totalAssets() == 0 => ( totalSupply() == 0 )) + + { + preserved with (env e) { + address any; + safeAssumptions(e, any, e.msg.sender); + } + } + +invariant noSupplyIfNoAssets() + noSupplyIfNoAssetsDef() // see defition in "helpers and miscellaneous" section + { + preserved with (env e) { + safeAssumptions(e, _, e.msg.sender); + } + } + + + +ghost mathint sumOfBalances { + init_state axiom sumOfBalances == 0; +} + +hook Sstore balanceOf[KEY address addy] uint256 newValue (uint256 oldValue) { + sumOfBalances = sumOfBalances + newValue - oldValue; +} + +hook Sload uint256 val balanceOf[KEY address addy] { + require sumOfBalances >= to_mathint(val); +} + +invariant totalSupplyIsSumOfBalances() + to_mathint(totalSupply()) == sumOfBalances; + + + +//////////////////////////////////////////////////////////////////////////////// +//// # State Transition ///// +//////////////////////////////////////////////////////////////////////////////// + + +rule totalsMonotonicity() { + method f; env e; calldataarg args; + require e.msg.sender != currentContract; + uint256 totalSupplyBefore = totalSupply(); + uint256 totalAssetsBefore = totalAssets(); + address receiver; + safeAssumptions(e, receiver, e.msg.sender); + callReceiverFunctions(f, e, receiver); + + uint256 totalSupplyAfter = totalSupply(); + uint256 totalAssetsAfter = totalAssets(); + + // possibly assert totalSupply and totalAssets must not change in opposite directions + assert totalSupplyBefore < totalSupplyAfter <=> totalAssetsBefore < totalAssetsAfter, + "if totalSupply changes by a larger amount, the corresponding change in totalAssets must remain the same or grow"; + assert totalSupplyAfter == totalSupplyBefore => totalAssetsBefore == totalAssetsAfter, + "equal size changes to totalSupply must yield equal size changes to totalAssets"; +} + +rule underlyingCannotChange() { + address originalAsset = asset(); + + method f; env e; calldataarg args; + f(e, args); + + address newAsset = asset(); + + assert originalAsset == newAsset, + "the underlying asset of a contract must not change"; +} + +//////////////////////////////////////////////////////////////////////////////// +//// # High Level ///// +//////////////////////////////////////////////////////////////////////////////// + +//// # This rules timeout - we will show how to deal with timeouts +/* rule totalAssetsOfUser(method f, address user ) { + env e; + calldataarg args; + safeAssumptions(e, e.msg.sender, user); + require user != currentContract; + mathint before = userAssets(user) + maxWithdraw(user); + + // need to ignore cases where user is msg.sender but someone else the receiver + address receiver; + require e.msg.sender != user; + uint256 assets; uint256 shares; + callFunctionsWithReceiverAndOwner(e, f, assets, shares, receiver, e.msg.sender); + mathint after = userAssets(user) + maxWithdraw(user); + assert after >= before; +} +*/ + +rule dustFavorsTheHouse(uint assetsIn ) +{ + env e; + + require e.msg.sender != currentContract; + safeAssumptions(e,e.msg.sender,e.msg.sender); + uint256 totalSupplyBefore = totalSupply(); + + uint balanceBefore = ERC20a.balanceOf(currentContract); + + uint shares = deposit(e,assetsIn, e.msg.sender); + uint assetsOut = redeem(e,shares,e.msg.sender,e.msg.sender); + + uint balanceAfter = ERC20a.balanceOf(currentContract); + + assert balanceAfter >= balanceBefore; +} + +//////////////////////////////////////////////////////////////////////////////// +//// # Risk Analysis ///////// +//////////////////////////////////////////////////////////////////////////////// + + +invariant vaultSolvency() + totalAssets() >= totalSupply() && userAssets(currentContract) >= totalAssets() { + preserved with(env e){ + requireInvariant totalSupplyIsSumOfBalances(); + require e.msg.sender != currentContract; + require currentContract != asset(); + } + } + + + +rule redeemingAllValidity() { + address owner; + uint256 shares; require shares == balanceOf(owner); + + env e; safeAssumptions(e, _, owner); + redeem(e, shares, _, owner); + uint256 ownerBalanceAfter = balanceOf(owner); + assert ownerBalanceAfter == 0; +} + + +//////////////////////////////////////////////////////////////////////////////// +//// # stakeholder properties (Risk Analysis ) ////////// +//////////////////////////////////////////////////////////////////////////////// + +rule contributingProducesShares(method f) +filtered { + f -> f.selector == sig:deposit(uint256,address).selector + || f.selector == sig:mint(uint256,address).selector +} +{ + env e; uint256 assets; uint256 shares; + address contributor; require contributor == e.msg.sender; + address receiver; + require currentContract != contributor + && currentContract != receiver; + + require previewDeposit(assets) + balanceOf(receiver) <= max_uint256; // safe assumption because call to _mint will revert if totalSupply += amount overflows + require shares + balanceOf(receiver) <= max_uint256; // same as above + + safeAssumptions(e, contributor, receiver); + + uint256 contributorAssetsBefore = userAssets(contributor); + uint256 receiverSharesBefore = balanceOf(receiver); + + callContributionMethods(e, f, assets, shares, receiver); + + uint256 contributorAssetsAfter = userAssets(contributor); + uint256 receiverSharesAfter = balanceOf(receiver); + + assert contributorAssetsBefore > contributorAssetsAfter <=> receiverSharesBefore < receiverSharesAfter, + "a contributor's assets must decrease if and only if the receiver's shares increase"; +} + +rule onlyContributionMethodsReduceAssets(method f) { + address user; require user != currentContract; + uint256 userAssetsBefore = userAssets(user); + + env e; calldataarg args; + safeAssumptions(e, user, _); + + f(e, args); + + uint256 userAssetsAfter = userAssets(user); + + assert userAssetsBefore > userAssetsAfter => + (f.selector == sig:deposit(uint256,address).selector || + f.selector == sig:mint(uint256,address).selector), + "a user's assets must not go down except on calls to contribution methods"; +} + +rule reclaimingProducesAssets(method f) +filtered { + f -> f.selector == sig:withdraw(uint256,address,address).selector + || f.selector == sig:redeem(uint256,address,address).selector +} +{ + env e; uint256 assets; uint256 shares; + address receiver; address owner; + require currentContract != e.msg.sender + && currentContract != receiver + && currentContract != owner; + + safeAssumptions(e, receiver, owner); + + uint256 ownerSharesBefore = balanceOf(owner); + uint256 receiverAssetsBefore = userAssets(receiver); + + callReclaimingMethods(e, f, assets, shares, receiver, owner); + + uint256 ownerSharesAfter = balanceOf(owner); + uint256 receiverAssetsAfter = userAssets(receiver); + + assert ownerSharesBefore > ownerSharesAfter <=> receiverAssetsBefore < receiverAssetsAfter, + "an owner's shares must decrease if and only if the receiver's assets increase"; +} + + + +//////////////////////////////////////////////////////////////////////////////// +//// # helpers and miscellaneous ////////// +//////////////////////////////////////////////////////////////////////////////// + +definition noSupplyIfNoAssetsDef() returns bool = + ( userAssets(currentContract) == 0 => totalSupply() == 0 ) && + ( totalAssets() == 0 => ( totalSupply() == 0 )); + +// definition noSupplyIfNoAssetsStrongerDef() returns bool = // fails for ERC4626BalanceOfHarness as explained in the readme +// ( userAssets(currentContract) == 0 => totalSupply() == 0 ) && +// ( totalAssets() == 0 <=> ( totalSupply() == 0 )); + + +function safeAssumptions(env e, address receiver, address owner) { + require currentContract != asset(); // Although this is not disallowed, we assume the contract's underlying asset is not the contract itself + requireInvariant totalSupplyIsSumOfBalances(); + requireInvariant vaultSolvency(); + requireInvariant noAssetsIfNoSupply(); + requireInvariant noSupplyIfNoAssets(); + requireInvariant assetsMoreThanSupply(); + + //// # Note : we don't want to use singleBalanceBounded and singleBalanceBounded invariants + /* requireInvariant sumOfBalancePairsBounded(receiver, owner ); + requireInvariant singleBalanceBounded(receiver); + requireInvariant singleBalanceBounded(owner); + */ + ///// # but, it safe to assume that a single balance is less than sum of balances + require ( (receiver != owner => balanceOf(owner) + balanceOf(receiver) <= to_mathint(totalSupply())) && + balanceOf(receiver) <= totalSupply() && + balanceOf(owner) <= totalSupply()); +} + + +// A helper function to set the receiver +function callReceiverFunctions(method f, env e, address receiver) { + uint256 amount; + if (f.selector == sig:deposit(uint256,address).selector) { + deposit(e, amount, receiver); + } else if (f.selector == sig:mint(uint256,address).selector) { + mint(e, amount, receiver); + } else if (f.selector == sig:withdraw(uint256,address,address).selector) { + address owner; + withdraw(e, amount, receiver, owner); + } else if (f.selector == sig:redeem(uint256,address,address).selector) { + address owner; + redeem(e, amount, receiver, owner); + } else { + calldataarg args; + f(e, args); + } +} + + +function callContributionMethods(env e, method f, uint256 assets, uint256 shares, address receiver) { + if (f.selector == sig:deposit(uint256,address).selector) { + deposit(e, assets, receiver); + } + if (f.selector == sig:mint(uint256,address).selector) { + mint(e, shares, receiver); + } +} + +function callReclaimingMethods(env e, method f, uint256 assets, uint256 shares, address receiver, address owner) { + if (f.selector == sig:withdraw(uint256,address,address).selector) { + withdraw(e, assets, receiver, owner); + } + if (f.selector == sig:redeem(uint256,address,address).selector) { + redeem(e, shares, receiver, owner); + } +} + +function callFunctionsWithReceiverAndOwner(env e, method f, uint256 assets, uint256 shares, address receiver, address owner) { + if (f.selector == sig:withdraw(uint256,address,address).selector) { + withdraw(e, assets, receiver, owner); + } + if (f.selector == sig:redeem(uint256,address,address).selector) { + redeem(e, shares, receiver, owner); + } + if (f.selector == sig:deposit(uint256,address).selector) { + deposit(e, assets, receiver); + } + if (f.selector == sig:mint(uint256,address).selector) { + mint(e, shares, receiver); + } + if (f.selector == sig:transferFrom(address,address,uint256).selector) { + transferFrom(e, owner, receiver, shares); + } + else { + calldataarg args; + f(e, args); + } +} diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec new file mode 100644 index 00000000..73c89103 --- /dev/null +++ b/certora/specs/VaultERC4626.spec @@ -0,0 +1,489 @@ +/* + * This is a specification file to formally verify BorrowSystem.sol + * smart contract using the Certora Prover. For more information, + * visit: https://www.certora.com/ + * + */ + + +// reference from the spec to additional contracts used in the verification + +// using DummyERC20A as ERC20a; +// using DummyERC20B as ERC20b; +using ERC4626Harness as ERC20a; + +/* + Declaration of methods that are used in the rules. envfree indicate that + the method is not dependent on the environment (msg.value, msg.sender). + Methods that are not declared here are assumed to be dependent on env. +*/ +methods { + // ERC20 methods not in Vault. Possibly combine Vault and Token + function name() external returns string envfree; + function symbol() external returns string envfree; + function decimals() external returns uint8 envfree; + function asset() external returns address envfree; + + function totalSupply() external returns uint256 envfree; + function balanceOf(address) external returns uint256 envfree; + // Not implemented by EVault + // function nonces(address) external returns uint256 envfree; + + function approve(address,uint256) external returns bool; + function deposit(uint256,address) external; + function mint(uint256,address) external; + function withdraw(uint256,address,address) external; + function redeem(uint256,address,address) external; + + function totalAssets() external returns uint256 envfree; + function userAssets(address) external returns uint256 envfree; + function convertToShares(uint256) external returns uint256 envfree; + function convertToAssets(uint256) external returns uint256 envfree; + function previewDeposit(uint256) external returns uint256 envfree; + function previewMint(uint256) external returns uint256 envfree; + function previewWithdraw(uint256) external returns uint256 envfree; + function previewRedeem(uint256) external returns uint256 envfree; + + function maxDeposit(address) external returns uint256 envfree; + function maxMint(address) external returns uint256 envfree; + function maxWithdraw(address) external returns uint256 envfree; + function maxRedeem(address) external returns uint256 envfree; + + function permit(address,address,uint256,uint256,uint8,bytes32,bytes32) external; + function DOMAIN_SEPARATOR() external returns bytes32; + + //// #ERC20 methods + function _.balanceOf(address) external => DISPATCHER(true); + function _.transfer(address,uint256) external => DISPATCHER(true); + function _.transferFrom(address,address,uint256) external => DISPATCHER(true); + + function ERC20a.balanceOf(address) external returns uint256 envfree; + function ERC20a.transferFrom(address,address,uint256) external returns bool; +} + + + +//////////////////////////////////////////////////////////////////////////////// +//// # asset To shares mathematical properties ///// +//////////////////////////////////////////////////////////////////////////////// + +rule conversionOfZero { + uint256 convertZeroShares = convertToAssets(0); + uint256 convertZeroAssets = convertToShares(0); + + assert convertZeroShares == 0, + "converting zero shares must return zero assets"; + assert convertZeroAssets == 0, + "converting zero assets must return zero shares"; +} + +rule convertToAssetsWeakAdditivity() { + uint256 sharesA; uint256 sharesB; + require sharesA + sharesB < max_uint128 + && convertToAssets(sharesA) + convertToAssets(sharesB) < to_mathint(max_uint256) + && convertToAssets(require_uint256(sharesA + sharesB)) < max_uint256; + assert convertToAssets(sharesA) + convertToAssets(sharesB) <= to_mathint(convertToAssets(require_uint256(sharesA + sharesB))), + "converting sharesA and sharesB to assets then summing them must yield a smaller or equal result to summing them then converting"; +} + +rule convertToSharesWeakAdditivity() { + uint256 assetsA; uint256 assetsB; + require assetsA + assetsB < max_uint128 + && convertToAssets(assetsA) + convertToAssets(assetsB) < to_mathint(max_uint256) + && convertToAssets(require_uint256(assetsA + assetsB)) < max_uint256; + assert convertToAssets(assetsA) + convertToAssets(assetsB) <= to_mathint(convertToAssets(require_uint256(assetsA + assetsB))), + "converting assetsA and assetsB to shares then summing them must yield a smaller or equal result to summing them then converting"; +} + +rule conversionWeakMonotonicity { + uint256 smallerShares; uint256 largerShares; + uint256 smallerAssets; uint256 largerAssets; + + assert smallerShares < largerShares => convertToAssets(smallerShares) <= convertToAssets(largerShares), + "converting more shares must yield equal or greater assets"; + assert smallerAssets < largerAssets => convertToShares(smallerAssets) <= convertToShares(largerAssets), + "converting more assets must yield equal or greater shares"; +} + +rule conversionWeakIntegrity() { + uint256 sharesOrAssets; + assert convertToShares(convertToAssets(sharesOrAssets)) <= sharesOrAssets, + "converting shares to assets then back to shares must return shares less than or equal to the original amount"; + assert convertToAssets(convertToShares(sharesOrAssets)) <= sharesOrAssets, + "converting assets to shares then back to assets must return assets less than or equal to the original amount"; +} + +rule convertToCorrectness(uint256 amount, uint256 shares) +{ + assert amount >= convertToAssets(convertToShares(amount)); + assert shares >= convertToShares(convertToAssets(shares)); +} + + +//////////////////////////////////////////////////////////////////////////////// +//// # Unit Test ///// +//////////////////////////////////////////////////////////////////////////////// + +rule depositMonotonicity() { + env e; storage start = lastStorage; + + uint256 smallerAssets; uint256 largerAssets; + address receiver; + require currentContract != e.msg.sender && currentContract != receiver; + + safeAssumptions(e, e.msg.sender, receiver); + + deposit(e, smallerAssets, receiver); + uint256 smallerShares = balanceOf(receiver) ; + + deposit(e, largerAssets, receiver) at start; + uint256 largerShares = balanceOf(receiver) ; + + assert smallerAssets < largerAssets => smallerShares <= largerShares, + "when supply tokens outnumber asset tokens, a larger deposit of assets must produce an equal or greater number of shares"; +} + + +rule zeroDepositZeroShares(uint assets, address receiver) +{ + env e; + + uint shares = deposit(e,assets, receiver); + + assert shares == 0 <=> assets == 0; +} + +//////////////////////////////////////////////////////////////////////////////// +//// # Valid State ///// +//////////////////////////////////////////////////////////////////////////////// + +invariant assetsMoreThanSupply() + totalAssets() >= totalSupply() + { + preserved with (env e) { + require e.msg.sender != currentContract; + address any; + safeAssumptions(e, any , e.msg.sender); + } + } + +invariant noAssetsIfNoSupply() + ( userAssets(currentContract) == 0 => totalSupply() == 0 ) && + ( totalAssets() == 0 => ( totalSupply() == 0 )) + + { + preserved with (env e) { + address any; + safeAssumptions(e, any, e.msg.sender); + } + } + +invariant noSupplyIfNoAssets() + noSupplyIfNoAssetsDef() // see defition in "helpers and miscellaneous" section + { + preserved with (env e) { + safeAssumptions(e, _, e.msg.sender); + } + } + + + +ghost mathint sumOfBalances { + init_state axiom sumOfBalances == 0; +} + +hook Sstore currentContract.vaultStorage.users[KEY address addy].data Vault.PackedUserSlot newValue (Vault.PackedUserSlot oldValue) { + sumOfBalances = sumOfBalances + newValue - oldValue; +} + +hook Sload Vault.PackedUserSlot val currentContract.vaultStorage.users[KEY address addy].data { + require sumOfBalances >= to_mathint(val); +} + +// hook Sstore balanceOf[KEY address addy] uint256 newValue (uint256 oldValue) { +// sumOfBalances = sumOfBalances + newValue - oldValue; +// } + + +// hook Sload uint256 val balanceOf[KEY address addy] { +// require sumOfBalances >= to_mathint(val); +// } + +invariant totalSupplyIsSumOfBalances() + to_mathint(totalSupply()) == sumOfBalances; + + + +//////////////////////////////////////////////////////////////////////////////// +//// # State Transition ///// +//////////////////////////////////////////////////////////////////////////////// + + +rule totalsMonotonicity() { + method f; env e; calldataarg args; + require e.msg.sender != currentContract; + uint256 totalSupplyBefore = totalSupply(); + uint256 totalAssetsBefore = totalAssets(); + address receiver; + safeAssumptions(e, receiver, e.msg.sender); + callReceiverFunctions(f, e, receiver); + + uint256 totalSupplyAfter = totalSupply(); + uint256 totalAssetsAfter = totalAssets(); + + // possibly assert totalSupply and totalAssets must not change in opposite directions + assert totalSupplyBefore < totalSupplyAfter <=> totalAssetsBefore < totalAssetsAfter, + "if totalSupply changes by a larger amount, the corresponding change in totalAssets must remain the same or grow"; + assert totalSupplyAfter == totalSupplyBefore => totalAssetsBefore == totalAssetsAfter, + "equal size changes to totalSupply must yield equal size changes to totalAssets"; +} + +rule underlyingCannotChange() { + address originalAsset = asset(); + + method f; env e; calldataarg args; + f(e, args); + + address newAsset = asset(); + + assert originalAsset == newAsset, + "the underlying asset of a contract must not change"; +} + +//////////////////////////////////////////////////////////////////////////////// +//// # High Level ///// +//////////////////////////////////////////////////////////////////////////////// + +//// # This rules timeout - we will show how to deal with timeouts +/* rule totalAssetsOfUser(method f, address user ) { + env e; + calldataarg args; + safeAssumptions(e, e.msg.sender, user); + require user != currentContract; + mathint before = userAssets(user) + maxWithdraw(user); + + // need to ignore cases where user is msg.sender but someone else the receiver + address receiver; + require e.msg.sender != user; + uint256 assets; uint256 shares; + callFunctionsWithReceiverAndOwner(e, f, assets, shares, receiver, e.msg.sender); + mathint after = userAssets(user) + maxWithdraw(user); + assert after >= before; +} +*/ + +rule dustFavorsTheHouse(uint assetsIn ) +{ + env e; + + require e.msg.sender != currentContract; + safeAssumptions(e,e.msg.sender,e.msg.sender); + uint256 totalSupplyBefore = totalSupply(); + + uint balanceBefore = ERC20a.balanceOf(currentContract); + + uint shares = deposit(e,assetsIn, e.msg.sender); + uint assetsOut = redeem(e,shares,e.msg.sender,e.msg.sender); + + uint balanceAfter = ERC20a.balanceOf(currentContract); + + assert balanceAfter >= balanceBefore; +} + +//////////////////////////////////////////////////////////////////////////////// +//// # Risk Analysis ///////// +//////////////////////////////////////////////////////////////////////////////// + + +invariant vaultSolvency() + totalAssets() >= totalSupply() && userAssets(currentContract) >= totalAssets() { + preserved with(env e){ + requireInvariant totalSupplyIsSumOfBalances(); + require e.msg.sender != currentContract; + require currentContract != asset(); + } + } + + + +rule redeemingAllValidity() { + address owner; + uint256 shares; require shares == balanceOf(owner); + + env e; safeAssumptions(e, _, owner); + redeem(e, shares, _, owner); + uint256 ownerBalanceAfter = balanceOf(owner); + assert ownerBalanceAfter == 0; +} + + +//////////////////////////////////////////////////////////////////////////////// +//// # stakeholder properties (Risk Analysis ) ////////// +//////////////////////////////////////////////////////////////////////////////// + +rule contributingProducesShares(method f) +filtered { + f -> f.selector == sig:deposit(uint256,address).selector + || f.selector == sig:mint(uint256,address).selector +} +{ + env e; uint256 assets; uint256 shares; + address contributor; require contributor == e.msg.sender; + address receiver; + require currentContract != contributor + && currentContract != receiver; + + require previewDeposit(assets) + balanceOf(receiver) <= max_uint256; // safe assumption because call to _mint will revert if totalSupply += amount overflows + require shares + balanceOf(receiver) <= max_uint256; // same as above + + safeAssumptions(e, contributor, receiver); + + uint256 contributorAssetsBefore = userAssets(contributor); + uint256 receiverSharesBefore = balanceOf(receiver); + + callContributionMethods(e, f, assets, shares, receiver); + + uint256 contributorAssetsAfter = userAssets(contributor); + uint256 receiverSharesAfter = balanceOf(receiver); + + assert contributorAssetsBefore > contributorAssetsAfter <=> receiverSharesBefore < receiverSharesAfter, + "a contributor's assets must decrease if and only if the receiver's shares increase"; +} + +rule onlyContributionMethodsReduceAssets(method f) { + address user; require user != currentContract; + uint256 userAssetsBefore = userAssets(user); + + env e; calldataarg args; + safeAssumptions(e, user, _); + + f(e, args); + + uint256 userAssetsAfter = userAssets(user); + + assert userAssetsBefore > userAssetsAfter => + (f.selector == sig:deposit(uint256,address).selector || + f.selector == sig:mint(uint256,address).selector), + "a user's assets must not go down except on calls to contribution methods"; +} + +rule reclaimingProducesAssets(method f) +filtered { + f -> f.selector == sig:withdraw(uint256,address,address).selector + || f.selector == sig:redeem(uint256,address,address).selector +} +{ + env e; uint256 assets; uint256 shares; + address receiver; address owner; + require currentContract != e.msg.sender + && currentContract != receiver + && currentContract != owner; + + safeAssumptions(e, receiver, owner); + + uint256 ownerSharesBefore = balanceOf(owner); + uint256 receiverAssetsBefore = userAssets(receiver); + + callReclaimingMethods(e, f, assets, shares, receiver, owner); + + uint256 ownerSharesAfter = balanceOf(owner); + uint256 receiverAssetsAfter = userAssets(receiver); + + assert ownerSharesBefore > ownerSharesAfter <=> receiverAssetsBefore < receiverAssetsAfter, + "an owner's shares must decrease if and only if the receiver's assets increase"; +} + + + +//////////////////////////////////////////////////////////////////////////////// +//// # helpers and miscellaneous ////////// +//////////////////////////////////////////////////////////////////////////////// + +definition noSupplyIfNoAssetsDef() returns bool = + ( userAssets(currentContract) == 0 => totalSupply() == 0 ) && + ( totalAssets() == 0 => ( totalSupply() == 0 )); + +// definition noSupplyIfNoAssetsStrongerDef() returns bool = // fails for ERC4626BalanceOfHarness as explained in the readme +// ( userAssets(currentContract) == 0 => totalSupply() == 0 ) && +// ( totalAssets() == 0 <=> ( totalSupply() == 0 )); + + +function safeAssumptions(env e, address receiver, address owner) { + require currentContract != asset(); // Although this is not disallowed, we assume the contract's underlying asset is not the contract itself + requireInvariant totalSupplyIsSumOfBalances(); + requireInvariant vaultSolvency(); + requireInvariant noAssetsIfNoSupply(); + requireInvariant noSupplyIfNoAssets(); + requireInvariant assetsMoreThanSupply(); + + //// # Note : we don't want to use singleBalanceBounded and singleBalanceBounded invariants + /* requireInvariant sumOfBalancePairsBounded(receiver, owner ); + requireInvariant singleBalanceBounded(receiver); + requireInvariant singleBalanceBounded(owner); + */ + ///// # but, it safe to assume that a single balance is less than sum of balances + require ( (receiver != owner => balanceOf(owner) + balanceOf(receiver) <= to_mathint(totalSupply())) && + balanceOf(receiver) <= totalSupply() && + balanceOf(owner) <= totalSupply()); +} + + +// A helper function to set the receiver +function callReceiverFunctions(method f, env e, address receiver) { + uint256 amount; + if (f.selector == sig:deposit(uint256,address).selector) { + deposit(e, amount, receiver); + } else if (f.selector == sig:mint(uint256,address).selector) { + mint(e, amount, receiver); + } else if (f.selector == sig:withdraw(uint256,address,address).selector) { + address owner; + withdraw(e, amount, receiver, owner); + } else if (f.selector == sig:redeem(uint256,address,address).selector) { + address owner; + redeem(e, amount, receiver, owner); + } else { + calldataarg args; + f(e, args); + } +} + + +function callContributionMethods(env e, method f, uint256 assets, uint256 shares, address receiver) { + if (f.selector == sig:deposit(uint256,address).selector) { + deposit(e, assets, receiver); + } + if (f.selector == sig:mint(uint256,address).selector) { + mint(e, shares, receiver); + } +} + +function callReclaimingMethods(env e, method f, uint256 assets, uint256 shares, address receiver, address owner) { + if (f.selector == sig:withdraw(uint256,address,address).selector) { + withdraw(e, assets, receiver, owner); + } + if (f.selector == sig:redeem(uint256,address,address).selector) { + redeem(e, shares, receiver, owner); + } +} + +function callFunctionsWithReceiverAndOwner(env e, method f, uint256 assets, uint256 shares, address receiver, address owner) { + if (f.selector == sig:withdraw(uint256,address,address).selector) { + withdraw(e, assets, receiver, owner); + } + if (f.selector == sig:redeem(uint256,address,address).selector) { + redeem(e, shares, receiver, owner); + } + if (f.selector == sig:deposit(uint256,address).selector) { + deposit(e, assets, receiver); + } + if (f.selector == sig:mint(uint256,address).selector) { + mint(e, shares, receiver); + } + if (f.selector == sig:transferFrom(address,address,uint256).selector) { + transferFrom(e, owner, receiver, shares); + } + else { + calldataarg args; + f(e, args); + } +} From d7d9c143386bb31691ca697c8b05a73fcc1021aa Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Apr 2024 16:50:08 +0100 Subject: [PATCH 053/152] Remove bad envfrees in ERC4626 spec --- certora/conf/VaultERC4626.conf | 21 ++++- certora/specs/VaultERC4626.spec | 155 +++++++++++++++++--------------- 2 files changed, 99 insertions(+), 77 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index 6125d0ab..955e2452 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -14,13 +14,28 @@ "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", "solc": "solc8.23", "rule_sanity": "basic", - "msg": "Vault benchmarking", + "msg": "Vault ERC4626", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + // currently the rules with sanity failures + // "rule": [ + // // "assetsMoreThanSupply", + // // "contributingProducesShares", + // "depositMonotonicity", + // // "dustFavorsTheHouse", + // // "noAssetsIfNoSupply", + // // "noSupplyIfNoAssets", + // // "onlyContributionMethodsReduceAssets", + // // "reclaimingProducesAssets", + // // "redeemingAllValidity", + // // "totalsMonotonicity", + // // "vaultSolvency" + // ], + // "coverage_info" : "advanced", "parametric_contracts": ["ERC4626Harness"], - "build_cache": true, + // "build_cache": true, "prover_version" : "master", // Performance tuning options below this line "solc_via_ir": true, @@ -29,7 +44,7 @@ "loop_iter": "2", // options borrowed from Liquidation "prover_args": [ - "-depth 10", + "-depth 0", "-smt_nonLinearArithmetic true", "-adaptiveSolverConfig false" ], diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 73c89103..82884cd4 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -24,8 +24,8 @@ methods { function decimals() external returns uint8 envfree; function asset() external returns address envfree; - function totalSupply() external returns uint256 envfree; - function balanceOf(address) external returns uint256 envfree; + // function totalSupply() external returns uint256 envfree; + // function balanceOf(address) external returns uint256 envfree; //NOT ENVFREE // Not implemented by EVault // function nonces(address) external returns uint256 envfree; @@ -35,19 +35,19 @@ methods { function withdraw(uint256,address,address) external; function redeem(uint256,address,address) external; - function totalAssets() external returns uint256 envfree; + // function totalAssets() external returns uint256 envfree; function userAssets(address) external returns uint256 envfree; - function convertToShares(uint256) external returns uint256 envfree; - function convertToAssets(uint256) external returns uint256 envfree; - function previewDeposit(uint256) external returns uint256 envfree; - function previewMint(uint256) external returns uint256 envfree; - function previewWithdraw(uint256) external returns uint256 envfree; - function previewRedeem(uint256) external returns uint256 envfree; - - function maxDeposit(address) external returns uint256 envfree; - function maxMint(address) external returns uint256 envfree; - function maxWithdraw(address) external returns uint256 envfree; - function maxRedeem(address) external returns uint256 envfree; + // function convertToShares(uint256) external returns uint256 envfree; + // function convertToAssets(uint256) external returns uint256 envfree; + // function previewDeposit(uint256) external returns uint256 envfree; + // function previewMint(uint256) external returns uint256 envfree; + // function previewWithdraw(uint256) external returns uint256 envfree; + // function previewRedeem(uint256) external returns uint256 envfree; + + // function maxDeposit(address) external returns uint256 envfree; + // function maxMint(address) external returns uint256 envfree; + // function maxWithdraw(address) external returns uint256 envfree; + // function maxRedeem(address) external returns uint256 envfree; function permit(address,address,uint256,uint256,uint8,bytes32,bytes32) external; function DOMAIN_SEPARATOR() external returns bytes32; @@ -57,7 +57,7 @@ methods { function _.transfer(address,uint256) external => DISPATCHER(true); function _.transferFrom(address,address,uint256) external => DISPATCHER(true); - function ERC20a.balanceOf(address) external returns uint256 envfree; + // function ERC20a.balanceOf(address) external returns uint256 envfree; // NOT ENVFREE function ERC20a.transferFrom(address,address,uint256) external returns bool; } @@ -68,8 +68,9 @@ methods { //////////////////////////////////////////////////////////////////////////////// rule conversionOfZero { - uint256 convertZeroShares = convertToAssets(0); - uint256 convertZeroAssets = convertToShares(0); + env e; + uint256 convertZeroShares = convertToAssets(e, 0); + uint256 convertZeroAssets = convertToShares(e, 0); assert convertZeroShares == 0, "converting zero shares must return zero assets"; @@ -78,45 +79,50 @@ rule conversionOfZero { } rule convertToAssetsWeakAdditivity() { + env e; uint256 sharesA; uint256 sharesB; require sharesA + sharesB < max_uint128 - && convertToAssets(sharesA) + convertToAssets(sharesB) < to_mathint(max_uint256) - && convertToAssets(require_uint256(sharesA + sharesB)) < max_uint256; - assert convertToAssets(sharesA) + convertToAssets(sharesB) <= to_mathint(convertToAssets(require_uint256(sharesA + sharesB))), + && convertToAssets(e, sharesA) + convertToAssets(e, sharesB) < to_mathint(max_uint256) + && convertToAssets(e, require_uint256(sharesA + sharesB)) < max_uint256; + assert convertToAssets(e, sharesA) + convertToAssets(e, sharesB) <= to_mathint(convertToAssets(e, require_uint256(sharesA + sharesB))), "converting sharesA and sharesB to assets then summing them must yield a smaller or equal result to summing them then converting"; } rule convertToSharesWeakAdditivity() { + env e; uint256 assetsA; uint256 assetsB; require assetsA + assetsB < max_uint128 - && convertToAssets(assetsA) + convertToAssets(assetsB) < to_mathint(max_uint256) - && convertToAssets(require_uint256(assetsA + assetsB)) < max_uint256; - assert convertToAssets(assetsA) + convertToAssets(assetsB) <= to_mathint(convertToAssets(require_uint256(assetsA + assetsB))), + && convertToAssets(e, assetsA) + convertToAssets(e, assetsB) < to_mathint(max_uint256) + && convertToAssets(e, require_uint256(assetsA + assetsB)) < max_uint256; + assert convertToAssets(e, assetsA) + convertToAssets(e, assetsB) <= to_mathint(convertToAssets(e, require_uint256(assetsA + assetsB))), "converting assetsA and assetsB to shares then summing them must yield a smaller or equal result to summing them then converting"; } rule conversionWeakMonotonicity { + env e; uint256 smallerShares; uint256 largerShares; uint256 smallerAssets; uint256 largerAssets; - assert smallerShares < largerShares => convertToAssets(smallerShares) <= convertToAssets(largerShares), + assert smallerShares < largerShares => convertToAssets(e, smallerShares) <= convertToAssets(e, largerShares), "converting more shares must yield equal or greater assets"; - assert smallerAssets < largerAssets => convertToShares(smallerAssets) <= convertToShares(largerAssets), + assert smallerAssets < largerAssets => convertToShares(e, smallerAssets) <= convertToShares(e, largerAssets), "converting more assets must yield equal or greater shares"; } rule conversionWeakIntegrity() { + env e; uint256 sharesOrAssets; - assert convertToShares(convertToAssets(sharesOrAssets)) <= sharesOrAssets, + assert convertToShares(e, convertToAssets(e, sharesOrAssets)) <= sharesOrAssets, "converting shares to assets then back to shares must return shares less than or equal to the original amount"; - assert convertToAssets(convertToShares(sharesOrAssets)) <= sharesOrAssets, + assert convertToAssets(e, convertToShares(e, sharesOrAssets)) <= sharesOrAssets, "converting assets to shares then back to assets must return assets less than or equal to the original amount"; } rule convertToCorrectness(uint256 amount, uint256 shares) { - assert amount >= convertToAssets(convertToShares(amount)); - assert shares >= convertToShares(convertToAssets(shares)); + env e; + assert amount >= convertToAssets(e, convertToShares(e, amount)); + assert shares >= convertToShares(e, convertToAssets(e, shares)); } @@ -134,10 +140,10 @@ rule depositMonotonicity() { safeAssumptions(e, e.msg.sender, receiver); deposit(e, smallerAssets, receiver); - uint256 smallerShares = balanceOf(receiver) ; + uint256 smallerShares = balanceOf(e, receiver) ; deposit(e, largerAssets, receiver) at start; - uint256 largerShares = balanceOf(receiver) ; + uint256 largerShares = balanceOf(e, receiver) ; assert smallerAssets < largerAssets => smallerShares <= largerShares, "when supply tokens outnumber asset tokens, a larger deposit of assets must produce an equal or greater number of shares"; @@ -157,31 +163,31 @@ rule zeroDepositZeroShares(uint assets, address receiver) //// # Valid State ///// //////////////////////////////////////////////////////////////////////////////// -invariant assetsMoreThanSupply() - totalAssets() >= totalSupply() +invariant assetsMoreThanSupply(env e) + totalAssets(e) >= totalSupply(e) { - preserved with (env e) { + preserved { require e.msg.sender != currentContract; address any; safeAssumptions(e, any , e.msg.sender); } } -invariant noAssetsIfNoSupply() - ( userAssets(currentContract) == 0 => totalSupply() == 0 ) && - ( totalAssets() == 0 => ( totalSupply() == 0 )) +invariant noAssetsIfNoSupply(env e) + ( userAssets(currentContract) == 0 => totalSupply(e) == 0 ) && + ( totalAssets(e) == 0 => ( totalSupply(e) == 0 )) { - preserved with (env e) { + preserved { address any; safeAssumptions(e, any, e.msg.sender); } } -invariant noSupplyIfNoAssets() - noSupplyIfNoAssetsDef() // see defition in "helpers and miscellaneous" section +invariant noSupplyIfNoAssets(env e) + noSupplyIfNoAssetsDef(e) // see defition in "helpers and miscellaneous" section { - preserved with (env e) { + preserved { safeAssumptions(e, _, e.msg.sender); } } @@ -209,8 +215,8 @@ hook Sload Vault.PackedUserSlot val currentContract.vaultStorage.users[KEY addre // require sumOfBalances >= to_mathint(val); // } -invariant totalSupplyIsSumOfBalances() - to_mathint(totalSupply()) == sumOfBalances; +invariant totalSupplyIsSumOfBalances(env e) + to_mathint(totalSupply(e)) == sumOfBalances; @@ -222,14 +228,14 @@ invariant totalSupplyIsSumOfBalances() rule totalsMonotonicity() { method f; env e; calldataarg args; require e.msg.sender != currentContract; - uint256 totalSupplyBefore = totalSupply(); - uint256 totalAssetsBefore = totalAssets(); + uint256 totalSupplyBefore = totalSupply(e); + uint256 totalAssetsBefore = totalAssets(e); address receiver; safeAssumptions(e, receiver, e.msg.sender); callReceiverFunctions(f, e, receiver); - uint256 totalSupplyAfter = totalSupply(); - uint256 totalAssetsAfter = totalAssets(); + uint256 totalSupplyAfter = totalSupply(e); + uint256 totalAssetsAfter = totalAssets(e); // possibly assert totalSupply and totalAssets must not change in opposite directions assert totalSupplyBefore < totalSupplyAfter <=> totalAssetsBefore < totalAssetsAfter, @@ -278,14 +284,14 @@ rule dustFavorsTheHouse(uint assetsIn ) require e.msg.sender != currentContract; safeAssumptions(e,e.msg.sender,e.msg.sender); - uint256 totalSupplyBefore = totalSupply(); + uint256 totalSupplyBefore = totalSupply(e); - uint balanceBefore = ERC20a.balanceOf(currentContract); + uint balanceBefore = ERC20a.balanceOf(e, currentContract); uint shares = deposit(e,assetsIn, e.msg.sender); uint assetsOut = redeem(e,shares,e.msg.sender,e.msg.sender); - uint balanceAfter = ERC20a.balanceOf(currentContract); + uint balanceAfter = ERC20a.balanceOf(e, currentContract); assert balanceAfter >= balanceBefore; } @@ -295,10 +301,10 @@ rule dustFavorsTheHouse(uint assetsIn ) //////////////////////////////////////////////////////////////////////////////// -invariant vaultSolvency() - totalAssets() >= totalSupply() && userAssets(currentContract) >= totalAssets() { - preserved with(env e){ - requireInvariant totalSupplyIsSumOfBalances(); +invariant vaultSolvency(env e) + totalAssets(e) >= totalSupply(e) && userAssets(currentContract) >= totalAssets(e) { + preserved { + requireInvariant totalSupplyIsSumOfBalances(e); require e.msg.sender != currentContract; require currentContract != asset(); } @@ -307,12 +313,13 @@ invariant vaultSolvency() rule redeemingAllValidity() { + env e; address owner; - uint256 shares; require shares == balanceOf(owner); + uint256 shares; require shares == balanceOf(e, owner); - env e; safeAssumptions(e, _, owner); + safeAssumptions(e, _, owner); redeem(e, shares, _, owner); - uint256 ownerBalanceAfter = balanceOf(owner); + uint256 ownerBalanceAfter = balanceOf(e, owner); assert ownerBalanceAfter == 0; } @@ -333,18 +340,18 @@ filtered { require currentContract != contributor && currentContract != receiver; - require previewDeposit(assets) + balanceOf(receiver) <= max_uint256; // safe assumption because call to _mint will revert if totalSupply += amount overflows - require shares + balanceOf(receiver) <= max_uint256; // same as above + require previewDeposit(e, assets) + balanceOf(e, receiver) <= max_uint256; // safe assumption because call to _mint will revert if totalSupply += amount overflows + require shares + balanceOf(e, receiver) <= max_uint256; // same as above safeAssumptions(e, contributor, receiver); uint256 contributorAssetsBefore = userAssets(contributor); - uint256 receiverSharesBefore = balanceOf(receiver); + uint256 receiverSharesBefore = balanceOf(e, receiver); callContributionMethods(e, f, assets, shares, receiver); uint256 contributorAssetsAfter = userAssets(contributor); - uint256 receiverSharesAfter = balanceOf(receiver); + uint256 receiverSharesAfter = balanceOf(e, receiver); assert contributorAssetsBefore > contributorAssetsAfter <=> receiverSharesBefore < receiverSharesAfter, "a contributor's assets must decrease if and only if the receiver's shares increase"; @@ -381,12 +388,12 @@ filtered { safeAssumptions(e, receiver, owner); - uint256 ownerSharesBefore = balanceOf(owner); + uint256 ownerSharesBefore = balanceOf(e, owner); uint256 receiverAssetsBefore = userAssets(receiver); callReclaimingMethods(e, f, assets, shares, receiver, owner); - uint256 ownerSharesAfter = balanceOf(owner); + uint256 ownerSharesAfter = balanceOf(e, owner); uint256 receiverAssetsAfter = userAssets(receiver); assert ownerSharesBefore > ownerSharesAfter <=> receiverAssetsBefore < receiverAssetsAfter, @@ -399,9 +406,9 @@ filtered { //// # helpers and miscellaneous ////////// //////////////////////////////////////////////////////////////////////////////// -definition noSupplyIfNoAssetsDef() returns bool = - ( userAssets(currentContract) == 0 => totalSupply() == 0 ) && - ( totalAssets() == 0 => ( totalSupply() == 0 )); +definition noSupplyIfNoAssetsDef(env e) returns bool = + ( userAssets(currentContract) == 0 => totalSupply(e) == 0 ) && + ( totalAssets(e) == 0 => ( totalSupply(e) == 0 )); // definition noSupplyIfNoAssetsStrongerDef() returns bool = // fails for ERC4626BalanceOfHarness as explained in the readme // ( userAssets(currentContract) == 0 => totalSupply() == 0 ) && @@ -410,11 +417,11 @@ definition noSupplyIfNoAssetsDef() returns bool = function safeAssumptions(env e, address receiver, address owner) { require currentContract != asset(); // Although this is not disallowed, we assume the contract's underlying asset is not the contract itself - requireInvariant totalSupplyIsSumOfBalances(); - requireInvariant vaultSolvency(); - requireInvariant noAssetsIfNoSupply(); - requireInvariant noSupplyIfNoAssets(); - requireInvariant assetsMoreThanSupply(); + requireInvariant totalSupplyIsSumOfBalances(e); + requireInvariant vaultSolvency(e); + requireInvariant noAssetsIfNoSupply(e); + requireInvariant noSupplyIfNoAssets(e); + requireInvariant assetsMoreThanSupply(e); //// # Note : we don't want to use singleBalanceBounded and singleBalanceBounded invariants /* requireInvariant sumOfBalancePairsBounded(receiver, owner ); @@ -422,9 +429,9 @@ function safeAssumptions(env e, address receiver, address owner) { requireInvariant singleBalanceBounded(owner); */ ///// # but, it safe to assume that a single balance is less than sum of balances - require ( (receiver != owner => balanceOf(owner) + balanceOf(receiver) <= to_mathint(totalSupply())) && - balanceOf(receiver) <= totalSupply() && - balanceOf(owner) <= totalSupply()); + require ( (receiver != owner => balanceOf(e, owner) + balanceOf(e, receiver) <= to_mathint(totalSupply(e))) && + balanceOf(e, receiver) <= totalSupply(e) && + balanceOf(e, owner) <= totalSupply(e)); } From 79249089acf77b3a8c5a620a534752d453a5c2c7 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 24 Apr 2024 11:24:39 +0100 Subject: [PATCH 054/152] Fix isOperationDisabled --- certora/harness/AbstractBaseHarness.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/certora/harness/AbstractBaseHarness.sol b/certora/harness/AbstractBaseHarness.sol index de445ea8..d06d709c 100644 --- a/certora/harness/AbstractBaseHarness.sol +++ b/certora/harness/AbstractBaseHarness.sol @@ -58,9 +58,8 @@ abstract contract AbstractBaseHarness is Base { // Operation disable checks //-------------------------------------------------------------------------- function isOperationDisabledExt(uint32 operation) public returns (bool) { - // This is based on the check in callHook. VaultCache memory vaultCache = updateVault(); - return vaultCache.hookedOps.isNotSet(operation); + return isOperationDisabled(vaultCache.hookedOps, operation); } function isDepositDisabled() public returns (bool) { From 73141e2a054027be8de809e8cfa0bbbc3ba3e2b4 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 25 Apr 2024 12:43:26 +0100 Subject: [PATCH 055/152] Update Vault no surprising reverts --- certora/conf/Cache.conf | 28 +++++++++++++++++++++++++ certora/harness/CacheHarness.sol | 27 ++++++++++++++++++++++++ certora/specs/Cache.spec | 35 ++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 certora/conf/Cache.conf create mode 100644 certora/harness/CacheHarness.sol create mode 100644 certora/specs/Cache.spec diff --git a/certora/conf/Cache.conf b/certora/conf/Cache.conf new file mode 100644 index 00000000..c8088f5d --- /dev/null +++ b/certora/conf/Cache.conf @@ -0,0 +1,28 @@ +{ + "files": [ + // "lib/ethereum-vault-connector/src/ExecutionContext.sol", + // "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "src/EVault/shared/Cache.sol", + "certora/harness/CacheHarness.sol" + ], + // "link": [ + // "CacheHarness:evc=EthereumVaultConnector", + // ], + "verify": "CacheHarness:certora/specs/Cache.spec", + "solc": "solc8.23", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_version": "master", + "server" : "production", + // "coverage_info" : "advanced", + "parametric_contracts": ["CacheHarness"], + "optimistic_loop": true, + "loop_iter": "2", + // "solc_via_ir": true, + // "solc_optimize": "10000", + "rule_sanity": "basic", + // "function_finder_mode" : "relaxed", + // "finder_friendly_optimizer" : false, +} \ No newline at end of file diff --git a/certora/harness/CacheHarness.sol b/certora/harness/CacheHarness.sol new file mode 100644 index 00000000..2c7d5ce5 --- /dev/null +++ b/certora/harness/CacheHarness.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import "../../src/EVault/shared/Cache.sol"; + +contract CacheHarness is Cache { + function updateVaultExt() external virtual returns (VaultCache memory vaultCache) { + updateVault(); + } + function initVaultCacheExt(VaultCache memory vaultCache) external view returns (bool dirty) { + return initVaultCache(vaultCache); + } + function getlastInterestAccumulatorUpdate() external view returns (uint256) { + return vaultStorage.lastInterestAccumulatorUpdate; + } + function getTotalBorrows() external view returns (Owed) { + return vaultStorage.totalBorrows; + } + function getInterestAcc() external view returns (uint256) { + return vaultStorage.interestAccumulator; + } + function getAccumulatedFees() external view returns (Shares) { + return vaultStorage.accumulatedFees; + } + +} \ No newline at end of file diff --git a/certora/specs/Cache.spec b/certora/specs/Cache.spec new file mode 100644 index 00000000..e49fb728 --- /dev/null +++ b/certora/specs/Cache.spec @@ -0,0 +1,35 @@ + +methods { + // It's not envfree. block time + // function updateVaultExt() external returns (Cache.VaultCache) envfree; +} + +rule updateVault_no_unexpected_reverts { + env e; + + // revert case run: + // https://prover.certora.com/output/65266/8d688972399441b6baaca896f085402a?anonymousKey=0e79c1406bd82a2ad2672404bb8b62d184cd537b + require e.msg.value == 0; + uint256 lastInterestAccUpd = getlastInterestAccumulatorUpdate(e); + + // assignment to deltaT + require assert_uint256(lastInterestAccUpd) < e.block.timestamp; + // https://prover.certora.com/output/65266/e834a7e7775443ffbe26577bfbc97f87?anonymousKey=98085ba3f887e9b0fd2b22683e73af45bc1a106b + + // assignment to newTotalBorrows, overflows + // Note: MAX_SANE_AMOUNT does not work as a bound for these: + // https://prover.certora.com/output/65266/e1aab12acdb5435d80e70e661299c504?anonymousKey=c6c63c10fa9ddb5c16b86cd2073643768d3d96e4 + require getTotalBorrows(e) < 1152921504606846975; //max_uint60 + require getInterestAcc(e) < 1152921504606846975; + // newTotalBorrows assigment, prevent divide by zero + require getInterestAcc(e) > 0; + + // typecast of newAccumulatedFees + // Also MAX_SANE_AMOUNT is not a sufficient bound for this + // (because the bounded var is from storage not the new accumulated fees) + // https://prover.certora.com/output/65266/8c53d45891374c4692ea7597de239ba1?anonymousKey=551bfa1d1460c56f30002f5de8aeab4bd49a0fcb + require getAccumulatedFees(e) < 1152921504606846975; + + updateVaultExt@withrevert(e); + assert !lastReverted; +} \ No newline at end of file From a54767bac751bcc8f080f09993aa6d0ad41a75c7 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 25 Apr 2024 12:50:16 +0100 Subject: [PATCH 056/152] Tweak comment in cache.spec --- certora/specs/Cache.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certora/specs/Cache.spec b/certora/specs/Cache.spec index e49fb728..2722be65 100644 --- a/certora/specs/Cache.spec +++ b/certora/specs/Cache.spec @@ -19,7 +19,7 @@ rule updateVault_no_unexpected_reverts { // assignment to newTotalBorrows, overflows // Note: MAX_SANE_AMOUNT does not work as a bound for these: // https://prover.certora.com/output/65266/e1aab12acdb5435d80e70e661299c504?anonymousKey=c6c63c10fa9ddb5c16b86cd2073643768d3d96e4 - require getTotalBorrows(e) < 1152921504606846975; //max_uint60 + require getTotalBorrows(e) < 1152921504606846975; //2**60-1 require getInterestAcc(e) < 1152921504606846975; // newTotalBorrows assigment, prevent divide by zero require getInterestAcc(e) > 0; From d2af5454cce97ca7d15ba75d4c45ff1d37a760d6 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 29 Apr 2024 11:53:14 +0100 Subject: [PATCH 057/152] some fixups after updating --- certora/conf/EVault/modules/BalanceForwarder.conf | 2 +- certora/conf/EVault/modules/Vault.conf | 1 + certora/harness/modules/LiquidationHarness.sol | 6 +++--- certora/specs/BalanceForwarder.spec | 4 ++++ certora/specs/Liquidation.spec | 4 ++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/certora/conf/EVault/modules/BalanceForwarder.conf b/certora/conf/EVault/modules/BalanceForwarder.conf index 0f48b4d3..551532de 100644 --- a/certora/conf/EVault/modules/BalanceForwarder.conf +++ b/certora/conf/EVault/modules/BalanceForwarder.conf @@ -5,7 +5,7 @@ "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", "src/EVault/modules/BalanceForwarder.sol", "certora/harness/BaseHarness.sol", - "certora/harness/BalanceForwarderHarness.sol", + "certora/harness/modules/BalanceForwarderHarness.sol", ], "link": [ "BalanceForwarderHarness:evc=EthereumVaultConnector", diff --git a/certora/conf/EVault/modules/Vault.conf b/certora/conf/EVault/modules/Vault.conf index 52b0fab7..72eb1efe 100644 --- a/certora/conf/EVault/modules/Vault.conf +++ b/certora/conf/EVault/modules/Vault.conf @@ -24,4 +24,5 @@ "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", + "prover_version": "master" } \ No newline at end of file diff --git a/certora/harness/modules/LiquidationHarness.sol b/certora/harness/modules/LiquidationHarness.sol index 00ab89c7..75b31a85 100644 --- a/certora/harness/modules/LiquidationHarness.sol +++ b/certora/harness/modules/LiquidationHarness.sol @@ -13,7 +13,7 @@ contract LiquidationHarness is AbstractBaseHarness, Liquidation { function calculateLiquidityExternal( address account ) public view returns (uint256 collateralValue, uint256 liabilityValue) { - return calculateLiquidity(loadVault(), account, getCollaterals(account), LTVType.LIQUIDATION); + return calculateLiquidity(loadVault(), account, getCollaterals(account), true); } function calculateLiquidationExt( @@ -38,11 +38,11 @@ contract LiquidationHarness is AbstractBaseHarness, Liquidation { return getCurrentOwed(vaultCache, violator).toAssetsUp(); } - function getCollateralValueExt(VaultCache memory vaultCache, address account, address collateral, LTVType ltvType) + function getCollateralValueExt(VaultCache memory vaultCache, address account, address collateral, bool liquidation) external view returns (uint256 value) { - return getCollateralValue(vaultCache, account, collateral, ltvType); + return getCollateralValue(vaultCache, account, collateral, liquidation); } } \ No newline at end of file diff --git a/certora/specs/BalanceForwarder.spec b/certora/specs/BalanceForwarder.spec index e5fe84bc..08ddb3f5 100644 --- a/certora/specs/BalanceForwarder.spec +++ b/certora/specs/BalanceForwarder.spec @@ -33,6 +33,8 @@ import "Base.spec"; // return ghost_balanceForwarderFlag[userStorage_data]; // } +//passing: +// https://prover.certora.com/output/65266/e2a397f3bb864a9eaf4eefdfd35529bc?anonymousKey=aa5dace26320fee72d3611b84d337413ac48c2da rule enableBalanceForwarder { address account; env e1; @@ -44,6 +46,8 @@ rule enableBalanceForwarder { assert balanceForwarderEnabled(e2, account); } +// passing: +// https://prover.certora.com/output/65266/e2a397f3bb864a9eaf4eefdfd35529bc?anonymousKey=aa5dace26320fee72d3611b84d337413ac48c2da rule disableBalanceForwarder { address account; env e1; diff --git a/certora/specs/Liquidation.spec b/certora/specs/Liquidation.spec index cd2e4acf..9e2ad823 100644 --- a/certora/specs/Liquidation.spec +++ b/certora/specs/Liquidation.spec @@ -115,9 +115,9 @@ rule getCollateralValue_borrowing_lower { require LTVConfigAssumptions(e, getLTVConfig(e, collateral)); - uint256 collateralValue_borrowing = getCollateralValueExt(e, vaultCache, account, collateral, Liquidation.LTVType.BORROWING); + uint256 collateralValue_borrowing = getCollateralValueExt(e, vaultCache, account, collateral, false); - uint256 collateralValue_liquidation = getCollateralValueExt(e, vaultCache, account, collateral, Liquidation.LTVType.LIQUIDATION); + uint256 collateralValue_liquidation = getCollateralValueExt(e, vaultCache, account, collateral, true); require collateralValue_liquidation > 0; require collateralValue_borrowing > 0; From 485bd37a065122a38ed7a24bc8bc005753e45acd Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 29 Apr 2024 15:55:19 +0100 Subject: [PATCH 058/152] Comments with runs. Fixing timeout for cache. Fixes in vault spec/conf post update --- certora/conf/Cache.conf | 9 +++++---- certora/conf/EVault/modules/Vault.conf | 7 ++++--- certora/specs/Cache.spec | 2 ++ certora/specs/Liquidation.spec | 2 ++ certora/specs/RiskManager.spec | 9 ++++++++- certora/specs/Vault.spec | 7 +++---- 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/certora/conf/Cache.conf b/certora/conf/Cache.conf index c8088f5d..eaa8fbbb 100644 --- a/certora/conf/Cache.conf +++ b/certora/conf/Cache.conf @@ -20,9 +20,10 @@ "parametric_contracts": ["CacheHarness"], "optimistic_loop": true, "loop_iter": "2", - // "solc_via_ir": true, - // "solc_optimize": "10000", + "solc_via_ir": true, + "solc_optimize": "10000", "rule_sanity": "basic", - // "function_finder_mode" : "relaxed", - // "finder_friendly_optimizer" : false, + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, + "nondet_difficult_funcs" : true } \ No newline at end of file diff --git a/certora/conf/EVault/modules/Vault.conf b/certora/conf/EVault/modules/Vault.conf index 72eb1efe..bd4b1504 100644 --- a/certora/conf/EVault/modules/Vault.conf +++ b/certora/conf/EVault/modules/Vault.conf @@ -10,19 +10,20 @@ "link" : [ "VaultHarness:evc=EthereumVaultConnector", ], - "verify": "VaultHarness:certora/specs/benchmarking/Benchmarking.spec", + "verify": "VaultHarness:certora/specs/Vault.spec", "solc": "solc8.23", "rule_sanity": "basic", - "msg": "Vault benchmarking", + "msg": "Vault", "packages": [ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], "parametric_contracts": ["VaultHarness"], + "prover_version": "master", + "build_cache" : true, // Performance tuning options below this line "solc_via_ir": true, "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", - "prover_version": "master" } \ No newline at end of file diff --git a/certora/specs/Cache.spec b/certora/specs/Cache.spec index 2722be65..e09335f2 100644 --- a/certora/specs/Cache.spec +++ b/certora/specs/Cache.spec @@ -4,6 +4,8 @@ methods { // function updateVaultExt() external returns (Cache.VaultCache) envfree; } +// passing +// run: https://prover.certora.com/output/65266/974f262c8ca84582909b12e83849003b/?anonymousKey=b8fc04fbb6a3a2aa0cca1151da309eaea9f64252 rule updateVault_no_unexpected_reverts { env e; diff --git a/certora/specs/Liquidation.spec b/certora/specs/Liquidation.spec index 9e2ad823..5b993166 100644 --- a/certora/specs/Liquidation.spec +++ b/certora/specs/Liquidation.spec @@ -26,6 +26,8 @@ checkLiquidation must revert if: - price oracle is not configured */ +// run: https://prover.certora.com/output/65266/d21dd88f07684b01930ff44d737378d7/?anonymousKey=660fbbe1c86127afc78c999a9ddd58c156ac7dad + import "Base.spec"; methods { function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; diff --git a/certora/specs/RiskManager.spec b/certora/specs/RiskManager.spec index 36bb58bb..ba3eca62 100644 --- a/certora/specs/RiskManager.spec +++ b/certora/specs/RiskManager.spec @@ -74,13 +74,15 @@ checkVaultStatus must revert if: import "Base.spec"; +// run: https://prover.certora.com/output/65266/4d1ba56cfd3c4aefbe2661e07fd5c95c/?anonymousKey=800abae52d40b2758c3f1f8c8a42ff82025533cd + methods { // envfree function vaultIsOnlyController(address account) external returns (bool) envfree; } -// timeout +// counterexamle. rule liquidations_equal_for_one { env e; calldataarg args; @@ -134,6 +136,8 @@ rule ltv_borrowing_lower { } +// Passing + rule accountLiquidityMustRevert { env e; calldataarg args; @@ -152,6 +156,7 @@ rule accountLiquidityMustRevert { } +// passing rule accountLiquidityFullMustRevert { env e; calldataarg args; @@ -169,6 +174,7 @@ rule accountLiquidityFullMustRevert { assert oracleConfigured; } +// passing rule checkAccountStatusMustRevert { env e; calldataarg args; @@ -180,6 +186,7 @@ rule checkAccountStatusMustRevert { assert checksInProgress; } +// passing rule checkVaultStatusMustRevert { env e; calldataarg args; diff --git a/certora/specs/Vault.spec b/certora/specs/Vault.spec index d3814196..bf385d7f 100644 --- a/certora/specs/Vault.spec +++ b/certora/specs/Vault.spec @@ -104,8 +104,8 @@ methods { function EVCClient.EVCRequireStatusChecks(address account) internal => CVLRequireStatusCheck(account); // Track if balance forwarder hook is called - function BalanceUtils.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal returns (bool) => - CVLCalledBalanceForwarder(account, newAccountBalance); + function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => + CVLCalledBalanceForwarder(account, newAccountBalance) expect void; // Workaround for lack of ability to summarize metadata function Cache.loadVault() internal returns (Vault.VaultCache memory) => CVLLoadVault(); @@ -152,9 +152,8 @@ rule status_checks_scheduled (method f) filtered { f -> } persistent ghost bool calledForwarder; -function CVLCalledBalanceForwarder(address account, uint256 newAccountBalance) returns bool { +function CVLCalledBalanceForwarder(address account, uint256 newAccountBalance) { calledForwarder = true; - return true; } // NOTE: these rules are not parametric because they need From 49dc147485e256320f441090efac1bf4f52f5769 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 29 Apr 2024 16:02:03 +0100 Subject: [PATCH 059/152] run link for vault --- certora/specs/Vault.spec | 3 +++ 1 file changed, 3 insertions(+) diff --git a/certora/specs/Vault.spec b/certora/specs/Vault.spec index bf385d7f..9d0cfbbc 100644 --- a/certora/specs/Vault.spec +++ b/certora/specs/Vault.spec @@ -99,6 +99,9 @@ This operation affects: - total shares balance - total balance of the underlying assets held by the vault */ + +// all passing +// run: https://prover.certora.com/output/65266/4e6a6aeb5af9454e87e8245498b0207d?anonymousKey=e924e53a6ff7a84beab51de18671463a166885b4 methods { // Track if a check was scheduled function EVCClient.EVCRequireStatusChecks(address account) internal => CVLRequireStatusCheck(account); From 8a9272f7b029108edfe43fa5a9c375b53c3bd8e3 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 23 Apr 2024 15:43:16 +0100 Subject: [PATCH 060/152] WIP on erc4626. Mainly adding helpers. Some edits to Base.spec --- certora/conf/VaultERC4626.conf | 32 ++++++++------- certora/harness/ERC4626Harness.sol | 13 ++++-- certora/helpers/DummyERC20A.sol | 5 +++ certora/helpers/DummyERC20B.sol | 5 +++ certora/helpers/DummyERC20Impl.sol | 65 ++++++++++++++++++++++++++++++ certora/specs/Base.spec | 8 ++-- certora/specs/Liquidation.spec | 1 + certora/specs/VaultERC4626.spec | 21 ++++++---- 8 files changed, 122 insertions(+), 28 deletions(-) create mode 100644 certora/helpers/DummyERC20A.sol create mode 100644 certora/helpers/DummyERC20B.sol create mode 100644 certora/helpers/DummyERC20Impl.sol diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index 955e2452..b5c3824f 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -1,15 +1,17 @@ { "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "lib/ethereum-vault-connector/src/ExecutionContext.sol", - // "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + // TODO do we need EVC for this spec? + // "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + // "lib/ethereum-vault-connector/src/ExecutionContext.sol", "src/EVault/modules/Vault.sol", - "src/EVault/modules/Token.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", "certora/harness/BaseHarness.sol", "certora/harness/ERC4626Harness.sol", ], "link" : [ - "ERC4626Harness:evc=EthereumVaultConnector", + // "ERC4626Harness:evc=EthereumVaultConnector", + "ERC4626Harness:underlying_asset=DummyERC20A" ], "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", "solc": "solc8.23", @@ -20,10 +22,10 @@ "forge-std=lib/forge-std/src" ], // currently the rules with sanity failures - // "rule": [ + "rule": [ // // "assetsMoreThanSupply", - // // "contributingProducesShares", - // "depositMonotonicity", + // "contributingProducesShares", + "depositMonotonicity", // // "dustFavorsTheHouse", // // "noAssetsIfNoSupply", // // "noSupplyIfNoAssets", @@ -32,7 +34,7 @@ // // "redeemingAllValidity", // // "totalsMonotonicity", // // "vaultSolvency" - // ], + ], // "coverage_info" : "advanced", "parametric_contracts": ["ERC4626Harness"], // "build_cache": true, @@ -43,10 +45,10 @@ "optimistic_loop": true, "loop_iter": "2", // options borrowed from Liquidation - "prover_args": [ - "-depth 0", - "-smt_nonLinearArithmetic true", - "-adaptiveSolverConfig false" - ], - "smt_timeout": "7000" + // "prover_args": [ + // "-depth 0", + // "-smt_nonLinearArithmetic true", + // "-adaptiveSolverConfig false" + // ], + // "smt_timeout": "7000" } \ No newline at end of file diff --git a/certora/harness/ERC4626Harness.sol b/certora/harness/ERC4626Harness.sol index 17cc6f51..5df240a8 100644 --- a/certora/harness/ERC4626Harness.sol +++ b/certora/harness/ERC4626Harness.sol @@ -9,11 +9,18 @@ import "../../src/EVault/modules/Token.sol"; contract ERC4626Harness is VaultModule, TokenModule, AbstractBaseHarness { constructor(Integrations memory integrations) Base(integrations) {} - // function totalAssets() public view override returns (uint256) { - // return asset.balanceOf(address(this)); - // } + // Linked against DummyERC20A in verification config + IERC20 underlying_asset; + + function totalAssets() public view override returns (uint256) { + return underlying_asset.balanceOf(address(this)); + } function userAssets(address user) public view returns (uint256) { // harnessed return IERC20(asset()).balanceOf(user); } + + function asset() public view override virtual returns (address) { + return address(underlying_asset); + } } diff --git a/certora/helpers/DummyERC20A.sol b/certora/helpers/DummyERC20A.sol new file mode 100644 index 00000000..188b9260 --- /dev/null +++ b/certora/helpers/DummyERC20A.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; +import "./DummyERC20Impl.sol"; + +contract DummyERC20A is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/helpers/DummyERC20B.sol b/certora/helpers/DummyERC20B.sol new file mode 100644 index 00000000..0f97f1ef --- /dev/null +++ b/certora/helpers/DummyERC20B.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; +import "./DummyERC20Impl.sol"; + +contract DummyERC20B is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/helpers/DummyERC20Impl.sol b/certora/helpers/DummyERC20Impl.sol new file mode 100644 index 00000000..afcb8bee --- /dev/null +++ b/certora/helpers/DummyERC20Impl.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; + +// with mint +contract DummyERC20Impl { + uint256 t; + mapping (address => uint256) b; + mapping (address => mapping (address => uint256)) a; + + // string private name; // change public to private + // string private symbol; // change public to private + uint public decimals; + + function name() public returns (string memory) { // added for testing + return ""; + } + + function symbol() public returns (string memory) { // added for testing + return ""; + } + + function myAddress() public returns (address) { + return address(this); + } + + function add(uint a, uint b) internal pure returns (uint256) { + uint c = a +b; + require (c >= a); + return c; + } + function sub(uint a, uint b) internal pure returns (uint256) { + require (a>=b); + return a-b; + } + + function totalSupply() external view returns (uint256) { + return t; + } + function balanceOf(address account) external view returns (uint256) { + return b[account]; + } + function transfer(address recipient, uint256 amount) external returns (bool) { + b[msg.sender] = sub(b[msg.sender], amount); + b[recipient] = add(b[recipient], amount); + return true; + } + function allowance(address owner, address spender) external view returns (uint256) { + return a[owner][spender]; + } + function approve(address spender, uint256 amount) external returns (bool) { + a[msg.sender][spender] = amount; + return true; + } + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool) { + b[sender] = sub(b[sender], amount); + b[recipient] = add(b[recipient], amount); + a[sender][msg.sender] = sub(a[sender][msg.sender], amount); + return true; + } +} \ No newline at end of file diff --git a/certora/specs/Base.spec b/certora/specs/Base.spec index 464f9204..08d627a3 100644 --- a/certora/specs/Base.spec +++ b/certora/specs/Base.spec @@ -1,5 +1,5 @@ -using ERC20 as erc20; -using EthereumVaultConnector as evc; +using DummyERC20A as erc20; +// using EthereumVaultConnector as evc; methods { // envfree @@ -65,6 +65,7 @@ function LTVConfigAssumptions(env e, BaseHarness.LTVConfig ltvConfig) returns bo require_uint32(timeRemaining) < ltvConfig.rampDuration; } +/* function actualCaller(env e) returns address { if(e.msg.sender == evc) { address onBehalf; @@ -74,4 +75,5 @@ function actualCaller(env e) returns address { } else { return e.msg.sender; } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/certora/specs/Liquidation.spec b/certora/specs/Liquidation.spec index 5b993166..1df13a79 100644 --- a/certora/specs/Liquidation.spec +++ b/certora/specs/Liquidation.spec @@ -128,6 +128,7 @@ rule getCollateralValue_borrowing_lower { } +// passing (though I believe this was only introduced for debugging) rule calculateLiquidation_setViolator { env e; Liquidation.VaultCache vaultCache; diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 82884cd4..a614d7e4 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -8,9 +8,9 @@ // reference from the spec to additional contracts used in the verification -// using DummyERC20A as ERC20a; -// using DummyERC20B as ERC20b; -using ERC4626Harness as ERC20a; +import "Base.spec"; +using DummyERC20A as ERC20a; +using DummyERC20B as ERC20b; /* Declaration of methods that are used in the rules. envfree indicate that @@ -18,7 +18,6 @@ using ERC4626Harness as ERC20a; Methods that are not declared here are assumed to be dependent on env. */ methods { - // ERC20 methods not in Vault. Possibly combine Vault and Token function name() external returns string envfree; function symbol() external returns string envfree; function decimals() external returns uint8 envfree; @@ -53,9 +52,10 @@ methods { function DOMAIN_SEPARATOR() external returns bytes32; //// #ERC20 methods - function _.balanceOf(address) external => DISPATCHER(true); - function _.transfer(address,uint256) external => DISPATCHER(true); - function _.transferFrom(address,address,uint256) external => DISPATCHER(true); + // These are done in Base + // function _.balanceOf(address) external => DISPATCHER(true); + // function _.transfer(address,uint256) external => DISPATCHER(true); + // function _.transferFrom(address,address,uint256) external => DISPATCHER(true); // function ERC20a.balanceOf(address) external returns uint256 envfree; // NOT ENVFREE function ERC20a.transferFrom(address,address,uint256) external returns bool; @@ -494,3 +494,10 @@ function callFunctionsWithReceiverAndOwner(env e, method f, uint256 assets, uint f(e, args); } } + +rule sanity (method f) { + env e; + calldataarg args; + f(e, args); + assert false; +} \ No newline at end of file From 1b3bf16958db23b0ac5cff38108c8fb6fb6d6235 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 23 Apr 2024 16:03:31 +0100 Subject: [PATCH 061/152] Rerun all rules to check if sanity issues fixed --- certora/conf/VaultERC4626.conf | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index b5c3824f..b5c126ff 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -22,19 +22,19 @@ "forge-std=lib/forge-std/src" ], // currently the rules with sanity failures - "rule": [ - // // "assetsMoreThanSupply", - // "contributingProducesShares", - "depositMonotonicity", - // // "dustFavorsTheHouse", - // // "noAssetsIfNoSupply", - // // "noSupplyIfNoAssets", - // // "onlyContributionMethodsReduceAssets", - // // "reclaimingProducesAssets", - // // "redeemingAllValidity", - // // "totalsMonotonicity", - // // "vaultSolvency" - ], + // "rule": [ + // // // "assetsMoreThanSupply", + // // "contributingProducesShares", + // "depositMonotonicity", + // // // "dustFavorsTheHouse", + // // // "noAssetsIfNoSupply", + // // // "noSupplyIfNoAssets", + // // // "onlyContributionMethodsReduceAssets", + // // // "reclaimingProducesAssets", + // // // "redeemingAllValidity", + // // // "totalsMonotonicity", + // // // "vaultSolvency" + // ], // "coverage_info" : "advanced", "parametric_contracts": ["ERC4626Harness"], // "build_cache": true, From cf3a928b1fc65bca50880914e256552c374097dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antti=20Hyv=C3=A4rinen?= Date: Thu, 25 Apr 2024 14:32:22 +0200 Subject: [PATCH 062/152] Antti: summarize rpow --- certora/conf/VaultERC4626.conf | 2 +- certora/specs/GhostPow.spec | 54 +++++++++++++++++++++++++++++++++ certora/specs/VaultERC4626.spec | 6 +++- 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 certora/specs/GhostPow.spec diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index b5c126ff..040ddac4 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -51,4 +51,4 @@ // "-adaptiveSolverConfig false" // ], // "smt_timeout": "7000" -} \ No newline at end of file +} diff --git a/certora/specs/GhostPow.spec b/certora/specs/GhostPow.spec new file mode 100644 index 00000000..e99fba97 --- /dev/null +++ b/certora/specs/GhostPow.spec @@ -0,0 +1,54 @@ +/// @doc Ghost power function that incorporates mathematical pure x^y axioms. +/// @warning Some of these axioms might be false, depending on the Solidity implementation +/// The user must bear in mind that equality-like axioms can be violated because of rounding errors. +ghost _ghostPow(uint256, uint256, uint256) returns mathint { + /// x^0 = 1 + axiom forall uint256 x. forall uint256 base. _ghostPow(x, 0, base) == to_mathint(base); + /// 0^x = 1 + axiom forall uint256 y. forall uint256 base. _ghostPow(0, y, base) == 0; + /// x^1 = x + axiom forall uint256 x. forall uint256 base. _ghostPow(x, base, base) == to_mathint(x); + /// 1^y = 1 + axiom forall uint256 y. forall uint256 base. _ghostPow(base, y, base) == to_mathint(base); + + /// I. x > 1 && y1 > y2 => x^y1 > x^y2 + /// II. x < 1 && y1 > y2 => x^y1 < x^y2 + axiom forall uint256 x. forall uint256 y1. forall uint256 y2. forall uint256 base. + x >= base && y1 > y2 => _ghostPow(x, y1, base) >= _ghostPow(x, y2, base); + axiom forall uint256 x. forall uint256 y1. forall uint256 y2. forall uint256 base. + x < base && y1 > y2 => (_ghostPow(x, y1, base) <= _ghostPow(x, y2, base) && _ghostPow(x,y2, base) <= to_mathint(base)); + axiom forall uint256 x. forall uint256 y. forall uint256 base. + x < base && y > base => (_ghostPow(x, y, base) <= to_mathint(x)); + axiom forall uint256 x. forall uint256 y. forall uint256 base. + x < base && y <= base => (_ghostPow(x, y, base) >= to_mathint(x)); + axiom forall uint256 x. forall uint256 y. forall uint256 base. + x >= base && y > base => (_ghostPow(x, y, base) >= to_mathint(x)); + axiom forall uint256 x. forall uint256 y. forall uint256 base. + x >= base && y <= base => (_ghostPow(x, y, base) <= to_mathint(x)); + /// x1 > x2 && y > 0 => x1^y > x2^y + axiom forall uint256 x1. forall uint256 x2. forall uint256 y. forall uint256 base. + x1 > x2 => _ghostPow(x1, y, base) >= _ghostPow(x2, y, base); + + /* Additional axioms - potentially unsafe + /// x^y * x^(1-y) == x -> 0.01% relative error + axiom forall uint256 x. forall uint256 y. forall uint256 z. + (0 <= y && y <= ONE18() && z + y == to_mathint(ONE18())) => + relativeErrorBound(_ghostPow(x, y) * _ghostPow(x, z), x * ONE18(), ONE18() / 10000); + + /// (x^y)^(1/y) == x -> 1% relative error + axiom forall uint256 x. forall uint256 y. forall uint256 z. + (0 <= y && y <= ONE18() && z * y == ONE18()*ONE18() ) => + relativeErrorBound(_ghostPow(_ghostPow(x, y), z), x, ONE18() / 100); + */ +} + +function CVLPow(uint256 x, uint256 y, uint256 base) returns (uint256, bool) { + if (y == 0) {return (base, false);} + if (x == 0) {return (0, false);} + mathint res = _ghostPow(x, y, base); + if (res > max_uint256) { + uint256 havoced; + return (havoced, true); + } + return (require_uint256(res), false); +} \ No newline at end of file diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index a614d7e4..d69e3b5c 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -9,6 +9,8 @@ // reference from the spec to additional contracts used in the verification import "Base.spec"; +import "./GhostPow.spec"; + using DummyERC20A as ERC20a; using DummyERC20B as ERC20b; @@ -59,6 +61,8 @@ methods { // function ERC20a.balanceOf(address) external returns uint256 envfree; // NOT ENVFREE function ERC20a.transferFrom(address,address,uint256) external returns bool; + + function RPow.rpow(uint256 x, uint256 y, uint256 base) internal returns (uint256, bool) => CVLPow(x, y, base); } @@ -500,4 +504,4 @@ rule sanity (method f) { calldataarg args; f(e, args); assert false; -} \ No newline at end of file +} From 6114c54e5b18712395f0644a6a177c16f77a1ccb Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 29 Apr 2024 10:08:37 +0100 Subject: [PATCH 063/152] depositMonotonicity working with assumption --- certora/conf/VaultERC4626.conf | 21 +++++++++++---------- certora/specs/GhostPow.spec | 2 +- certora/specs/VaultERC4626.spec | 2 ++ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index 040ddac4..888145d2 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -22,10 +22,10 @@ "forge-std=lib/forge-std/src" ], // currently the rules with sanity failures - // "rule": [ + "rule": [ // // // "assetsMoreThanSupply", // // "contributingProducesShares", - // "depositMonotonicity", + "depositMonotonicity", // // // "dustFavorsTheHouse", // // // "noAssetsIfNoSupply", // // // "noSupplyIfNoAssets", @@ -34,7 +34,7 @@ // // // "redeemingAllValidity", // // // "totalsMonotonicity", // // // "vaultSolvency" - // ], + ], // "coverage_info" : "advanced", "parametric_contracts": ["ERC4626Harness"], // "build_cache": true, @@ -44,11 +44,12 @@ "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", - // options borrowed from Liquidation - // "prover_args": [ - // "-depth 0", - // "-smt_nonLinearArithmetic true", - // "-adaptiveSolverConfig false" - // ], - // "smt_timeout": "7000" + "prover_args": [ + "-smt_nonLinearArithmetic true", + "-adaptiveSolverConfig false", + "-solvers [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]", + "-splitParallel true" + ], + "nondet_difficult_funcs": true, + "smt_timeout": "7000" } diff --git a/certora/specs/GhostPow.spec b/certora/specs/GhostPow.spec index e99fba97..3dbfd0db 100644 --- a/certora/specs/GhostPow.spec +++ b/certora/specs/GhostPow.spec @@ -4,7 +4,7 @@ ghost _ghostPow(uint256, uint256, uint256) returns mathint { /// x^0 = 1 axiom forall uint256 x. forall uint256 base. _ghostPow(x, 0, base) == to_mathint(base); - /// 0^x = 1 + /// 0^x = 0 axiom forall uint256 y. forall uint256 base. _ghostPow(0, y, base) == 0; /// x^1 = x axiom forall uint256 x. forall uint256 base. _ghostPow(x, base, base) == to_mathint(x); diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index d69e3b5c..b9220724 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -141,6 +141,8 @@ rule depositMonotonicity() { address receiver; require currentContract != e.msg.sender && currentContract != receiver; + require largerAssets < max_uint256; // amount = max_uint256 deposits the full balance and we get a CEX for that case. + safeAssumptions(e, e.msg.sender, receiver); deposit(e, smallerAssets, receiver); From 0d9a34d20cdbb027c017ba6e295d5c1ae224efaf Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 29 Apr 2024 11:23:07 +0100 Subject: [PATCH 064/152] Add run link for depositMonotonicity --- certora/specs/VaultERC4626.spec | 3 +++ 1 file changed, 3 insertions(+) diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index b9220724..0fe1cc04 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -134,7 +134,10 @@ rule convertToCorrectness(uint256 amount, uint256 shares) //// # Unit Test ///// //////////////////////////////////////////////////////////////////////////////// +// passing with conf as here: +// https://prover.certora.com/output/65266/3fd23869b2124c45aa47599c521a70e5?anonymousKey=4c63cefe6e66a12fc34d6c9c887c3481b67379f0 rule depositMonotonicity() { + env e; storage start = lastStorage; uint256 smallerAssets; uint256 largerAssets; From d70f776cc4a1927f3ca294e563661431e938698b Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 29 Apr 2024 13:09:30 +0100 Subject: [PATCH 065/152] Mainly summarize calls that havoc contract state --- certora/conf/VaultERC4626.conf | 10 +++++----- certora/specs/VaultERC4626.spec | 31 +++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index 888145d2..f101fb62 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -25,8 +25,8 @@ "rule": [ // // // "assetsMoreThanSupply", // // "contributingProducesShares", - "depositMonotonicity", - // // // "dustFavorsTheHouse", + // "depositMonotonicity", + "dustFavorsTheHouse", // // // "noAssetsIfNoSupply", // // // "noSupplyIfNoAssets", // // // "onlyContributionMethodsReduceAssets", @@ -37,7 +37,7 @@ ], // "coverage_info" : "advanced", "parametric_contracts": ["ERC4626Harness"], - // "build_cache": true, + "build_cache": true, "prover_version" : "master", // Performance tuning options below this line "solc_via_ir": true, @@ -50,6 +50,6 @@ "-solvers [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]", "-splitParallel true" ], - "nondet_difficult_funcs": true, - "smt_timeout": "7000" + // "nondet_difficult_funcs": true, + // "smt_timeout": "7000" } diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 0fe1cc04..278cf38d 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -60,12 +60,33 @@ methods { // function _.transferFrom(address,address,uint256) external => DISPATCHER(true); // function ERC20a.balanceOf(address) external returns uint256 envfree; // NOT ENVFREE - function ERC20a.transferFrom(address,address,uint256) external returns bool; + function ERC20a.transferFrom(address,address,uint256) external returns bool; // not envfree function RPow.rpow(uint256 x, uint256 y, uint256 base) internal returns (uint256, bool) => CVLPow(x, y, base); -} + // These are unresolved calls that havoc contract state. + // Most of these cause these havocs because of a low-level call + // operation and are irrelevant for the rules. + function _.invokeHookTarget(address caller) internal => NONDET; + // another unresolved call that havocs all contracts + function _.requireVaultStatusCheck() external => NONDET; + function _.requireAccountAndVaultStatusCheck(address account) external => NONDET; + // trySafeTransferFrom cannot be summarized as NONDET (due to return type + // that includes bytes memory). So it is summarized as + // DummyERC20a.transferFrom + function _.trySafeTransferFrom(address token, address from, address to, uint256 value) internal with (env e) => CVLTrySafeTransferFrom(e, from, to, value) expect (bool, bytes memory); + // safeTransferFrom can be made NONDET, but is summarized as transferFrom + // from DummyERC20a anyway. + function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLTrySafeTransferFrom(e, from, to, value) expect (bool, bytes memory); + function _.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal => NONDET; +} + +// Summarize trySafeTransferFrom as DummyERC20 transferFrom +function CVLTrySafeTransferFrom(env e, address from, address to, uint256 value) returns (bool, bytes) { + bytes ret; // Ideally bytes("") if there is a way to do this + return (ERC20a.transferFrom(e, from, to, value), ret); +} //////////////////////////////////////////////////////////////////////////////// //// # asset To shares mathematical properties ///// @@ -297,7 +318,13 @@ rule dustFavorsTheHouse(uint assetsIn ) uint balanceBefore = ERC20a.balanceOf(e, currentContract); + require balanceBefore > 0; + require totalSupplyBefore > 0; + require assetsIn > 0; + + require assetsIn < max_uint256; uint shares = deposit(e,assetsIn, e.msg.sender); + require shares < max_uint256; uint assetsOut = redeem(e,shares,e.msg.sender,e.msg.sender); uint balanceAfter = ERC20a.balanceOf(e, currentContract); From 3be4b25fe4c37ca00ba7beb50abb4d97b4cc3ce4 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 29 Apr 2024 15:30:24 +0100 Subject: [PATCH 066/152] dustFavorsTheHouse passing --- certora/specs/VaultERC4626.spec | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 278cf38d..ef1a40b0 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -316,19 +316,14 @@ rule dustFavorsTheHouse(uint assetsIn ) safeAssumptions(e,e.msg.sender,e.msg.sender); uint256 totalSupplyBefore = totalSupply(e); - uint balanceBefore = ERC20a.balanceOf(e, currentContract); + // uint balanceBefore = ERC20a.balanceOf(e, currentContract); + uint balanceBefore = currentContract.balanceOf(e, currentContract); - require balanceBefore > 0; - require totalSupplyBefore > 0; - require assetsIn > 0; - - require assetsIn < max_uint256; uint shares = deposit(e,assetsIn, e.msg.sender); - require shares < max_uint256; uint assetsOut = redeem(e,shares,e.msg.sender,e.msg.sender); - uint balanceAfter = ERC20a.balanceOf(e, currentContract); - + // uint balanceAfter = ERC20a.balanceOf(e, currentContract); + uint balanceAfter = currentContract.balanceOf(e, currentContract); assert balanceAfter >= balanceBefore; } From 83a82e5dffa64309e0d314d70ac5369e894dcf40 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 29 Apr 2024 16:55:46 +0100 Subject: [PATCH 067/152] Add more passing run links --- certora/conf/VaultERC4626.conf | 4 ++-- certora/specs/VaultERC4626.spec | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index f101fb62..dd217ffa 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -26,10 +26,10 @@ // // // "assetsMoreThanSupply", // // "contributingProducesShares", // "depositMonotonicity", - "dustFavorsTheHouse", + // "dustFavorsTheHouse", // // // "noAssetsIfNoSupply", // // // "noSupplyIfNoAssets", - // // // "onlyContributionMethodsReduceAssets", + "onlyContributionMethodsReduceAssets", // // // "reclaimingProducesAssets", // // // "redeemingAllValidity", // // // "totalsMonotonicity", diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index ef1a40b0..a1af5847 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -308,6 +308,8 @@ rule underlyingCannotChange() { } */ +// passing +// run: https://prover.certora.com/output/65266/1912c053cdf8485087f2c050146c64aa/?anonymousKey=a12e3d573258a4d8136a19b612448a50f80b9a21 rule dustFavorsTheHouse(uint assetsIn ) { env e; @@ -388,6 +390,8 @@ filtered { "a contributor's assets must decrease if and only if the receiver's shares increase"; } +// passing +// run: https://prover.certora.com/output/65266/28a47dd30c6747cbbc4495de59e5f965?anonymousKey=2e86f97ff0030d5489503334c71961bb5978f331 rule onlyContributionMethodsReduceAssets(method f) { address user; require user != currentContract; uint256 userAssetsBefore = userAssets(user); From 7032a4b0f3b2f49996d919f83872d2b15778bd9e Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 30 Apr 2024 11:33:21 +0100 Subject: [PATCH 068/152] Make base consistent with main certora branch --- certora/conf/VaultERC4626.conf | 2 +- certora/specs/Base.spec | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index dd217ffa..f1941eb6 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -1,7 +1,7 @@ { "files": [ // TODO do we need EVC for this spec? - // "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", // "lib/ethereum-vault-connector/src/ExecutionContext.sol", "src/EVault/modules/Vault.sol", "certora/helpers/DummyERC20A.sol", diff --git a/certora/specs/Base.spec b/certora/specs/Base.spec index 08d627a3..1291f26a 100644 --- a/certora/specs/Base.spec +++ b/certora/specs/Base.spec @@ -1,5 +1,5 @@ using DummyERC20A as erc20; -// using EthereumVaultConnector as evc; +using EthereumVaultConnector as evc; methods { // envfree @@ -65,7 +65,6 @@ function LTVConfigAssumptions(env e, BaseHarness.LTVConfig ltvConfig) returns bo require_uint32(timeRemaining) < ltvConfig.rampDuration; } -/* function actualCaller(env e) returns address { if(e.msg.sender == evc) { address onBehalf; @@ -75,5 +74,4 @@ function actualCaller(env e) returns address { } else { return e.msg.sender; } -} -*/ \ No newline at end of file +} \ No newline at end of file From 0318b657115b2ee39b39c5057ced468a6ce94a14 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 1 May 2024 10:57:56 +0100 Subject: [PATCH 069/152] Cache reverts passing run --- certora/conf/Cache.conf | 1 - certora/specs/Cache.spec | 15 +++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/certora/conf/Cache.conf b/certora/conf/Cache.conf index eaa8fbbb..2b0f08d7 100644 --- a/certora/conf/Cache.conf +++ b/certora/conf/Cache.conf @@ -25,5 +25,4 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, - "nondet_difficult_funcs" : true } \ No newline at end of file diff --git a/certora/specs/Cache.spec b/certora/specs/Cache.spec index e09335f2..f4e1b9f5 100644 --- a/certora/specs/Cache.spec +++ b/certora/specs/Cache.spec @@ -5,7 +5,7 @@ methods { } // passing -// run: https://prover.certora.com/output/65266/974f262c8ca84582909b12e83849003b/?anonymousKey=b8fc04fbb6a3a2aa0cca1151da309eaea9f64252 +// run: https://prover.certora.com/output/65266/e5dc6fb3648f45fdbe48597c69561bd1/?anonymousKey=12ed8515517a0998ef7af0ed86ecc7008537cec1 rule updateVault_no_unexpected_reverts { env e; @@ -21,8 +21,14 @@ rule updateVault_no_unexpected_reverts { // assignment to newTotalBorrows, overflows // Note: MAX_SANE_AMOUNT does not work as a bound for these: // https://prover.certora.com/output/65266/e1aab12acdb5435d80e70e661299c504?anonymousKey=c6c63c10fa9ddb5c16b86cd2073643768d3d96e4 - require getTotalBorrows(e) < 1152921504606846975; //2**60-1 - require getInterestAcc(e) < 1152921504606846975; + // require getTotalBorrows(e) < 1152921504606846975; //2**60-1 + require getTotalBorrows(e) < 1267650600228229401496703205375; //2**100-1 + // require getTotalBorrows(e) < 1208925819614629174706175; //2**80-1 + // require getTotalBorrows(e) < max_uint112; //2**80-1 + + // require getInterestAcc(e) < 1152921504606846975; + require getInterestAcc(e) < 1267650600228229401496703205375; + // require getInterestAcc(e) < max_uint112; // newTotalBorrows assigment, prevent divide by zero require getInterestAcc(e) > 0; @@ -30,7 +36,8 @@ rule updateVault_no_unexpected_reverts { // Also MAX_SANE_AMOUNT is not a sufficient bound for this // (because the bounded var is from storage not the new accumulated fees) // https://prover.certora.com/output/65266/8c53d45891374c4692ea7597de239ba1?anonymousKey=551bfa1d1460c56f30002f5de8aeab4bd49a0fcb - require getAccumulatedFees(e) < 1152921504606846975; + require getAccumulatedFees(e) < 1267650600228229401496703205375; + // require getAccumulatedFees(e) < max_uint112; updateVaultExt@withrevert(e); assert !lastReverted; From 9702108c0f794e09675b34a3e3f03d6e2745980d Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 1 May 2024 14:21:14 +0100 Subject: [PATCH 070/152] Mainly attempted summary of loadVault --- certora/conf/Cache.conf | 8 ++--- certora/conf/VaultERC4626.conf | 14 +++++---- certora/harness/CacheHarness.sol | 3 ++ certora/harness/ERC4626Harness.sol | 50 ++++++++++++++++++++++++++---- certora/specs/Cache.spec | 6 +++- certora/specs/VaultERC4626.spec | 28 ++++++++++------- 6 files changed, 81 insertions(+), 28 deletions(-) diff --git a/certora/conf/Cache.conf b/certora/conf/Cache.conf index 2b0f08d7..c8088f5d 100644 --- a/certora/conf/Cache.conf +++ b/certora/conf/Cache.conf @@ -20,9 +20,9 @@ "parametric_contracts": ["CacheHarness"], "optimistic_loop": true, "loop_iter": "2", - "solc_via_ir": true, - "solc_optimize": "10000", + // "solc_via_ir": true, + // "solc_optimize": "10000", "rule_sanity": "basic", - "function_finder_mode" : "relaxed", - "finder_friendly_optimizer" : false, + // "function_finder_mode" : "relaxed", + // "finder_friendly_optimizer" : false, } \ No newline at end of file diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index f1941eb6..0175f015 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -9,10 +9,10 @@ "certora/harness/BaseHarness.sol", "certora/harness/ERC4626Harness.sol", ], - "link" : [ - // "ERC4626Harness:evc=EthereumVaultConnector", - "ERC4626Harness:underlying_asset=DummyERC20A" - ], + // "link" : [ + // // "ERC4626Harness:evc=EthereumVaultConnector", + // "ERC4626Harness:underlying_asset=DummyERC20A" + // ], "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", "solc": "solc8.23", "rule_sanity": "basic", @@ -28,14 +28,16 @@ // "depositMonotonicity", // "dustFavorsTheHouse", // // // "noAssetsIfNoSupply", - // // // "noSupplyIfNoAssets", - "onlyContributionMethodsReduceAssets", + "noSupplyIfNoAssets", + // "onlyContributionMethodsReduceAssets", // // // "reclaimingProducesAssets", // // // "redeemingAllValidity", // // // "totalsMonotonicity", // // // "vaultSolvency" ], // "coverage_info" : "advanced", + "method": "redeem(uint256,address,address)", + // "method": "withdraw(uint256,address,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, "prover_version" : "master", diff --git a/certora/harness/CacheHarness.sol b/certora/harness/CacheHarness.sol index 2c7d5ce5..1676ba0c 100644 --- a/certora/harness/CacheHarness.sol +++ b/certora/harness/CacheHarness.sol @@ -23,5 +23,8 @@ contract CacheHarness is Cache { function getAccumulatedFees() external view returns (Shares) { return vaultStorage.accumulatedFees; } + function getTotalShares() external view returns (Shares) { + return vaultStorage.totalShares; + } } \ No newline at end of file diff --git a/certora/harness/ERC4626Harness.sol b/certora/harness/ERC4626Harness.sol index 5df240a8..2effcaff 100644 --- a/certora/harness/ERC4626Harness.sol +++ b/certora/harness/ERC4626Harness.sol @@ -12,15 +12,53 @@ contract ERC4626Harness is VaultModule, TokenModule, AbstractBaseHarness { // Linked against DummyERC20A in verification config IERC20 underlying_asset; - function totalAssets() public view override returns (uint256) { - return underlying_asset.balanceOf(address(this)); - } + // function totalAssets() public view override returns (uint256) { + // return underlying_asset.balanceOf(address(this)); + // } function userAssets(address user) public view returns (uint256) { // harnessed - return IERC20(asset()).balanceOf(user); + return balanceOf(user); + // return IERC20(asset()).balanceOf(user); + // return vaultStorage.users[account].getBalance().toUint(); } - function asset() public view override virtual returns (address) { - return address(underlying_asset); + // function asset() public view override virtual returns (address) { + // return address(underlying_asset); + // } + + // VaultStorage Accessors: + function storage_lastInterestAccumulatorUpdate() public view returns (uint48) { + return vaultStorage.lastInterestAccumulatorUpdate; + } + function storage_cash() public view returns (Assets) { + return vaultStorage.cash; + } + function storage_supplyCap() public view returns (AmountCap) { + return vaultStorage.supplyCap; + } + function storage_borrowCap() public view returns (AmountCap) { + return vaultStorage.borrowCap; + } + // reentrancyLocked seems not direclty used in loadVault + function storage_hookedOps() public view returns (Flags) { + return vaultStorage.hookedOps; + } + function storage_snapshotInitialized() public view returns (bool) { + return vaultStorage.snapshotInitialized; + } + function storage_totalShares() public view returns (Shares) { + return vaultStorage.totalShares; + } + function storage_totalBorrows() public view returns (Owed) { + return vaultStorage.totalBorrows; + } + function storage_accumulatedFees() public view returns (Shares) { + return vaultStorage.accumulatedFees; + } + function storage_interestAccumulator() public view returns (uint256) { + return vaultStorage.interestAccumulator; + } + function storage_configFlags() public view returns (Flags) { + return vaultStorage.configFlags; } } diff --git a/certora/specs/Cache.spec b/certora/specs/Cache.spec index f4e1b9f5..2ca6597b 100644 --- a/certora/specs/Cache.spec +++ b/certora/specs/Cache.spec @@ -16,6 +16,8 @@ rule updateVault_no_unexpected_reverts { // assignment to deltaT require assert_uint256(lastInterestAccUpd) < e.block.timestamp; + + // https://prover.certora.com/output/65266/e834a7e7775443ffbe26577bfbc97f87?anonymousKey=98085ba3f887e9b0fd2b22683e73af45bc1a106b // assignment to newTotalBorrows, overflows @@ -36,8 +38,10 @@ rule updateVault_no_unexpected_reverts { // Also MAX_SANE_AMOUNT is not a sufficient bound for this // (because the bounded var is from storage not the new accumulated fees) // https://prover.certora.com/output/65266/8c53d45891374c4692ea7597de239ba1?anonymousKey=551bfa1d1460c56f30002f5de8aeab4bd49a0fcb - require getAccumulatedFees(e) < 1267650600228229401496703205375; + // require getAccumulatedFees(e) < 1267650600228229401496703205375; // require getAccumulatedFees(e) < max_uint112; + + // require getAccumulatedFees(e) < getTotalShares(e); updateVaultExt@withrevert(e); assert !lastReverted; diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index a1af5847..84dd961c 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -10,6 +10,7 @@ import "Base.spec"; import "./GhostPow.spec"; +import "./LoadVaultSummary.spec"; using DummyERC20A as ERC20a; using DummyERC20B as ERC20b; @@ -36,8 +37,9 @@ methods { function withdraw(uint256,address,address) external; function redeem(uint256,address,address) external; + // function totalAssets() external returns uint256 envfree; - function userAssets(address) external returns uint256 envfree; + // function userAssets(address) external returns uint256 envfree; // function convertToShares(uint256) external returns uint256 envfree; // function convertToAssets(uint256) external returns uint256 envfree; // function previewDeposit(uint256) external returns uint256 envfree; @@ -123,6 +125,8 @@ rule convertToSharesWeakAdditivity() { "converting assetsA and assetsB to shares then summing them must yield a smaller or equal result to summing them then converting"; } +// passing +// run: https://prover.certora.com/output/40748/614a8496d9784ba5873b9be6636d9f3e/?anonymousKey=a0622d3850471ef5d170484cbe7c5fec18646d61 rule conversionWeakMonotonicity { env e; uint256 smallerShares; uint256 largerShares; @@ -204,7 +208,7 @@ invariant assetsMoreThanSupply(env e) } invariant noAssetsIfNoSupply(env e) - ( userAssets(currentContract) == 0 => totalSupply(e) == 0 ) && + ( userAssets(e, currentContract) == 0 => totalSupply(e) == 0 ) && ( totalAssets(e) == 0 => ( totalSupply(e) == 0 )) { @@ -335,7 +339,7 @@ rule dustFavorsTheHouse(uint assetsIn ) invariant vaultSolvency(env e) - totalAssets(e) >= totalSupply(e) && userAssets(currentContract) >= totalAssets(e) { + totalAssets(e) >= totalSupply(e) && userAssets(e, currentContract) >= totalAssets(e) { preserved { requireInvariant totalSupplyIsSumOfBalances(e); require e.msg.sender != currentContract; @@ -378,12 +382,12 @@ filtered { safeAssumptions(e, contributor, receiver); - uint256 contributorAssetsBefore = userAssets(contributor); + uint256 contributorAssetsBefore = userAssets(e, contributor); uint256 receiverSharesBefore = balanceOf(e, receiver); callContributionMethods(e, f, assets, shares, receiver); - uint256 contributorAssetsAfter = userAssets(contributor); + uint256 contributorAssetsAfter = userAssets(e, contributor); uint256 receiverSharesAfter = balanceOf(e, receiver); assert contributorAssetsBefore > contributorAssetsAfter <=> receiverSharesBefore < receiverSharesAfter, @@ -393,15 +397,15 @@ filtered { // passing // run: https://prover.certora.com/output/65266/28a47dd30c6747cbbc4495de59e5f965?anonymousKey=2e86f97ff0030d5489503334c71961bb5978f331 rule onlyContributionMethodsReduceAssets(method f) { + env e; calldataarg args; address user; require user != currentContract; - uint256 userAssetsBefore = userAssets(user); + uint256 userAssetsBefore = userAssets(e, user); - env e; calldataarg args; safeAssumptions(e, user, _); f(e, args); - uint256 userAssetsAfter = userAssets(user); + uint256 userAssetsAfter = userAssets(e, user); assert userAssetsBefore > userAssetsAfter => (f.selector == sig:deposit(uint256,address).selector || @@ -409,6 +413,8 @@ rule onlyContributionMethodsReduceAssets(method f) { "a user's assets must not go down except on calls to contribution methods"; } +// passing +// run: https://prover.certora.com/output/65266/8ead2419e398420286adb1f636a35249/?anonymousKey=f135ef5ad92b9e187a5df3ebce5499f693eae015 rule reclaimingProducesAssets(method f) filtered { f -> f.selector == sig:withdraw(uint256,address,address).selector @@ -424,12 +430,12 @@ filtered { safeAssumptions(e, receiver, owner); uint256 ownerSharesBefore = balanceOf(e, owner); - uint256 receiverAssetsBefore = userAssets(receiver); + uint256 receiverAssetsBefore = userAssets(e, receiver); callReclaimingMethods(e, f, assets, shares, receiver, owner); uint256 ownerSharesAfter = balanceOf(e, owner); - uint256 receiverAssetsAfter = userAssets(receiver); + uint256 receiverAssetsAfter = userAssets(e, receiver); assert ownerSharesBefore > ownerSharesAfter <=> receiverAssetsBefore < receiverAssetsAfter, "an owner's shares must decrease if and only if the receiver's assets increase"; @@ -442,7 +448,7 @@ filtered { //////////////////////////////////////////////////////////////////////////////// definition noSupplyIfNoAssetsDef(env e) returns bool = - ( userAssets(currentContract) == 0 => totalSupply(e) == 0 ) && + ( userAssets(e, currentContract) == 0 => totalSupply(e) == 0 ) && ( totalAssets(e) == 0 => ( totalSupply(e) == 0 )); // definition noSupplyIfNoAssetsStrongerDef() returns bool = // fails for ERC4626BalanceOfHarness as explained in the readme From 15f4fa25e0da8a7c3cb8ed9a64c9786f48c29d58 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 2 May 2024 11:22:51 +0100 Subject: [PATCH 071/152] Vault summary for ERC4626 --- certora/conf/VaultERC4626.conf | 2 +- certora/specs/LoadVaultSummary.spec | 67 +++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 certora/specs/LoadVaultSummary.spec diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index 0175f015..0a2b3f88 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -36,7 +36,7 @@ // // // "vaultSolvency" ], // "coverage_info" : "advanced", - "method": "redeem(uint256,address,address)", + // "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, diff --git a/certora/specs/LoadVaultSummary.spec b/certora/specs/LoadVaultSummary.spec new file mode 100644 index 00000000..ced68cc6 --- /dev/null +++ b/certora/specs/LoadVaultSummary.spec @@ -0,0 +1,67 @@ +import "./Base.spec"; +methods { + function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) => CVLloadVault(); + function storage_lastInterestAccumulatorUpdate() external returns (uint48) envfree; + function storage_cash() external returns (BaseHarness.Assets) envfree; + function storage_supplyCap() external returns (BaseHarness.AmountCap) envfree; + function storage_borrowCap() external returns (BaseHarness.AmountCap) envfree; + function storage_hookedOps() external returns (BaseHarness.Flags) envfree; + function storage_snapshotInitialized() external returns (bool) envfree; + function storage_totalShares() external returns (BaseHarness.Shares) envfree; + function storage_totalBorrows() external returns (BaseHarness.Owed) envfree; + function storage_accumulatedFees() external returns (BaseHarness.Shares) envfree; + function storage_interestAccumulator() external returns (uint256) envfree; + function storage_configFlags() external returns (BaseHarness.Flags) envfree; +} + +function CVLloadVault() returns BaseHarness.VaultCache { + // for debugging performance + BaseHarness.VaultCache vaultCache; + + // These are used more than once + // not sure if our tool already de-duplicates these; + BaseHarness.Owed oldTotalBorrows = storage_totalBorrows(); + BaseHarness.Shares oldTotalShares = storage_totalShares(); + + require vaultCache.asset == erc20; + require vaultCache.oracle == oracleAddress; + require vaultCache.unitOfAccount == unitOfAccount; + + // try also directly setting to block.timestamp; + require vaultCache.lastInterestAccumulatorUpdate >= + storage_lastInterestAccumulatorUpdate(); + require vaultCache.cash == storage_cash(); + // check if this is true. Assigned from newTotalBorrows + require vaultCache.totalBorrows >= oldTotalBorrows; + require vaultCache.totalBorrows <= max_uint112; // MAX_SANE_AMOUNT + + /////// summarize totalShares + // simple summary + // require vaultCache.totalShares <= storage_totalShares(); + + // more accurate summary + mathint newTotalAssets = vaultCache.cash + vaultCache.totalBorrows; + uint16 feeScalar; //need to make higher than uint16 ? + uint256 feeAssets = require_uint256(feeScalar * (vaultCache.totalBorrows - oldTotalBorrows)); + if (require_uint256(newTotalAssets) > feeAssets) { // This is not directly checked in the code + require vaultCache.totalShares == require_uint112(oldTotalShares * newTotalAssets / + (newTotalAssets - feeAssets)); + } else { + require vaultCache.totalShares == oldTotalShares; + } + + require vaultCache.accumulatedFees == require_uint112(storage_accumulatedFees() + vaultCache.totalShares - oldTotalShares); + + require vaultCache.interestAccumulator >= storage_interestAccumulator(); + + require vaultCache.supplyCap == assert_uint256(storage_supplyCap()); + require vaultCache.borrowCap == assert_uint256(storage_borrowCap()); + require vaultCache.hookedOps == storage_hookedOps(); + require vaultCache.configFlags == storage_configFlags(); + + // Runtime + + bool snapshotInitialized; + + return vaultCache; +} \ No newline at end of file From c3bba7d4d680c81ff90fac0fce0132a27e8670ca Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 2 May 2024 13:40:38 +0100 Subject: [PATCH 072/152] LoadVault summary returns same values for same env --- certora/conf/VaultERC4626.conf | 2 +- certora/specs/LoadVaultSummary.spec | 55 ++++++++++++++++++----------- certora/specs/VaultERC4626.spec | 3 +- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index 0a2b3f88..0175f015 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -36,7 +36,7 @@ // // // "vaultSolvency" ], // "coverage_info" : "advanced", - // "method": "redeem(uint256,address,address)", + "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, diff --git a/certora/specs/LoadVaultSummary.spec b/certora/specs/LoadVaultSummary.spec index ced68cc6..bf3dae7a 100644 --- a/certora/specs/LoadVaultSummary.spec +++ b/certora/specs/LoadVaultSummary.spec @@ -1,6 +1,6 @@ import "./Base.spec"; methods { - function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) => CVLloadVault(); + function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLloadVault(e); function storage_lastInterestAccumulatorUpdate() external returns (uint48) envfree; function storage_cash() external returns (BaseHarness.Assets) envfree; function storage_supplyCap() external returns (BaseHarness.AmountCap) envfree; @@ -14,7 +14,20 @@ methods { function storage_configFlags() external returns (BaseHarness.Flags) envfree; } -function CVLloadVault() returns BaseHarness.VaultCache { +// need to make sure successive calls only return different values +// when this is actually possible in the real call... +// * calls with the same env will return all the same values +// the passage of time is not actually relevant to the spec because +// in all the rules, only one env is ever created per rule. +// The parts of the cache about interest are not relevant to the specs + +// parameter is meant to be block.timestamp +persistent ghost newInterestBorrows(uint256) returns uint256; +// this should be increasing over time, but I think we do +// not even need to model this. It can just be an uninterp function +// because in the ERC4626 spec there are no rules with multiple env. + +function CVLloadVault(env e) returns BaseHarness.VaultCache { // for debugging performance BaseHarness.VaultCache vaultCache; @@ -23,45 +36,45 @@ function CVLloadVault() returns BaseHarness.VaultCache { BaseHarness.Owed oldTotalBorrows = storage_totalBorrows(); BaseHarness.Shares oldTotalShares = storage_totalShares(); - require vaultCache.asset == erc20; - require vaultCache.oracle == oracleAddress; - require vaultCache.unitOfAccount == unitOfAccount; - - // try also directly setting to block.timestamp; + // havoc lastInterestAccummulatorUpdate (not relevant to rule or this model) require vaultCache.lastInterestAccumulatorUpdate >= storage_lastInterestAccumulatorUpdate(); require vaultCache.cash == storage_cash(); - // check if this is true. Assigned from newTotalBorrows - require vaultCache.totalBorrows >= oldTotalBorrows; + // total borrows may increase due to interest require vaultCache.totalBorrows <= max_uint112; // MAX_SANE_AMOUNT + uint256 interestBorrows = newInterestBorrows(e.block.timestamp); + require vaultCache.totalBorrows == require_uint144(oldTotalBorrows + interestBorrows); - /////// summarize totalShares - // simple summary - // require vaultCache.totalShares <= storage_totalShares(); - - // more accurate summary mathint newTotalAssets = vaultCache.cash + vaultCache.totalBorrows; - uint16 feeScalar; //need to make higher than uint16 ? - uint256 feeAssets = require_uint256(feeScalar * (vaultCache.totalBorrows - oldTotalBorrows)); - if (require_uint256(newTotalAssets) > feeAssets) { // This is not directly checked in the code + // uint16 feeScalar; + // uint256 feeAssets = require_uint256(feeScalar * (vaultCache.totalBorrows - oldTotalBorrows)); + // underapproximate interesteFee as 1 (1e4 in impl) + // this is a separate variable just for readability. + uint256 feeAssets = interestBorrows; + if (require_uint256(newTotalAssets) > feeAssets) { require vaultCache.totalShares == require_uint112(oldTotalShares * newTotalAssets / (newTotalAssets - feeAssets)); } else { require vaultCache.totalShares == oldTotalShares; } - require vaultCache.accumulatedFees == require_uint112(storage_accumulatedFees() + vaultCache.totalShares - oldTotalShares); + // not relevant to our ERC4626 rules, so havoced + uint112 accumulatedFeesHavoc; + require vaultCache.accumulatedFees == accumulatedFeesHavoc; + // require vaultCache.accumulatedFees == require_uint112(storage_accumulatedFees() + vaultCache.totalShares - oldTotalShares); + // havoc interestAccumulator (not relevant to rule or model) require vaultCache.interestAccumulator >= storage_interestAccumulator(); require vaultCache.supplyCap == assert_uint256(storage_supplyCap()); require vaultCache.borrowCap == assert_uint256(storage_borrowCap()); require vaultCache.hookedOps == storage_hookedOps(); require vaultCache.configFlags == storage_configFlags(); + require vaultCache.snapshotInitialized == storage_snapshotInitialized(); - // Runtime - - bool snapshotInitialized; + require vaultCache.asset == erc20; + require vaultCache.oracle == oracleAddress; + require vaultCache.unitOfAccount == unitOfAccount; return vaultCache; } \ No newline at end of file diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 84dd961c..99c59ab6 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -448,7 +448,8 @@ filtered { //////////////////////////////////////////////////////////////////////////////// definition noSupplyIfNoAssetsDef(env e) returns bool = - ( userAssets(e, currentContract) == 0 => totalSupply(e) == 0 ) && + // for this ERC4626 implementation balanceOf(Vault) is not the same as total assets + // ( userAssets(e, currentContract) == 0 => totalSupply(e) == 0 ) && ( totalAssets(e) == 0 => ( totalSupply(e) == 0 )); // definition noSupplyIfNoAssetsStrongerDef() returns bool = // fails for ERC4626BalanceOfHarness as explained in the readme From f2daf7f2a24efc82de3fca32a2e1291f59c15d0e Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 3 May 2024 12:02:02 +0100 Subject: [PATCH 073/152] Mainly check-in fixed harness --- certora/conf/VaultERC4626.conf | 22 +++++++++++++++------- certora/harness/ERC4626Harness.sol | 12 +----------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index 0175f015..59c4aca8 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -23,20 +23,21 @@ ], // currently the rules with sanity failures "rule": [ + // "conversionWeakMonotonicity" // // // "assetsMoreThanSupply", // // "contributingProducesShares", // "depositMonotonicity", // "dustFavorsTheHouse", // // // "noAssetsIfNoSupply", - "noSupplyIfNoAssets", - // "onlyContributionMethodsReduceAssets", - // // // "reclaimingProducesAssets", + // "noSupplyIfNoAssets", + "onlyContributionMethodsReduceAssets", + // "reclaimingProducesAssets", // // // "redeemingAllValidity", // // // "totalsMonotonicity", // // // "vaultSolvency" ], // "coverage_info" : "advanced", - "method": "redeem(uint256,address,address)", + // "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, @@ -46,12 +47,19 @@ "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", - "prover_args": [ + "prover_args": [ "-smt_nonLinearArithmetic true", "-adaptiveSolverConfig false", - "-solvers [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]", - "-splitParallel true" + "-solvers [cvc5:nonlin{randomSeed=1},cvc5:nonlin{randomSeed=2},cvc5:nonlin{randomSeed=3},cvc5:nonlin{randomSeed=4},cvc5:nonlin{randomSeed=5},cvc5:nonlin{randomSeed=6},cvc5:nonlin{randomSeed=7},cvc5:nonlin{randomSeed=8},cvc5:nonlin{randomSeed=9},cvc5:nonlin{randomSeed=10}]", + "-depth 0" ], + // old config + // "prover_args": [ + // "-smt_nonLinearArithmetic true", + // "-adaptiveSolverConfig false", + // "-solvers [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]", + // "-splitParallel true" + // ], // "nondet_difficult_funcs": true, // "smt_timeout": "7000" } diff --git a/certora/harness/ERC4626Harness.sol b/certora/harness/ERC4626Harness.sol index 2effcaff..be54dc09 100644 --- a/certora/harness/ERC4626Harness.sol +++ b/certora/harness/ERC4626Harness.sol @@ -12,20 +12,10 @@ contract ERC4626Harness is VaultModule, TokenModule, AbstractBaseHarness { // Linked against DummyERC20A in verification config IERC20 underlying_asset; - // function totalAssets() public view override returns (uint256) { - // return underlying_asset.balanceOf(address(this)); - // } - function userAssets(address user) public view returns (uint256) { // harnessed - return balanceOf(user); - // return IERC20(asset()).balanceOf(user); - // return vaultStorage.users[account].getBalance().toUint(); + return IERC20(asset()).balanceOf(user); } - // function asset() public view override virtual returns (address) { - // return address(underlying_asset); - // } - // VaultStorage Accessors: function storage_lastInterestAccumulatorUpdate() public view returns (uint48) { return vaultStorage.lastInterestAccumulatorUpdate; From 6cb53b970655f6b0ab509b122ad8a08de5f93cfc Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 7 May 2024 12:29:52 +0100 Subject: [PATCH 074/152] delete old EVaultERC4626 conf --- certora/conf/EVaultERC4626.conf | 29 ----------------------------- certora/conf/VaultERC4626.conf | 27 +++++++++++++-------------- 2 files changed, 13 insertions(+), 43 deletions(-) delete mode 100644 certora/conf/EVaultERC4626.conf diff --git a/certora/conf/EVaultERC4626.conf b/certora/conf/EVaultERC4626.conf deleted file mode 100644 index 8dacc5c0..00000000 --- a/certora/conf/EVaultERC4626.conf +++ /dev/null @@ -1,29 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "lib/ethereum-vault-connector/src/ExecutionContext.sol", - // "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", - "certora/harness/EVaultHarness.sol", - - ], - "link" : [ - "EVaultHarness:evc=EthereumVaultConnector", - ], - "verify": "EVaultHarness:certora/specs/EVaultERC4626.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "msg": "Vault benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "parametric_contracts": ["EVaultHarness"], - // Performance tuning options below this line - "solc_via_ir": true, - "solc_optimize": "10000", - "optimistic_loop": true, - "loop_iter": "2", - "build_cache": true, - "server": "production", - "prover_version": "master" -} \ No newline at end of file diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index 59c4aca8..4a7c814f 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -21,21 +21,20 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - // currently the rules with sanity failures - "rule": [ - // "conversionWeakMonotonicity" - // // // "assetsMoreThanSupply", + // "rule": [ + // // "conversionWeakMonotonicity", + // // "assetsMoreThanSupply", // // "contributingProducesShares", - // "depositMonotonicity", - // "dustFavorsTheHouse", - // // // "noAssetsIfNoSupply", - // "noSupplyIfNoAssets", - "onlyContributionMethodsReduceAssets", - // "reclaimingProducesAssets", - // // // "redeemingAllValidity", - // // // "totalsMonotonicity", - // // // "vaultSolvency" - ], + // // "depositMonotonicity", + // // "dustFavorsTheHouse", + // // "noAssetsIfNoSupply", + // // "noSupplyIfNoAssets", + // // "onlyContributionMethodsReduceAssets", + // // "reclaimingProducesAssets", + // // "redeemingAllValidity", + // // "totalsMonotonicity", + // // "vaultSolvency" + // ], // "coverage_info" : "advanced", // "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", From 33b73770e7374e937efbcd77cc44e0d9c7fdcaf6 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 9 May 2024 11:47:10 +0100 Subject: [PATCH 075/152] getCurrentOnBehalfOfAccount summary --- certora/conf/VaultERC4626.conf | 1 + certora/harness/ERC4626Harness.sol | 5 ++++- certora/specs/Base.spec | 12 ++++++++++++ certora/specs/LoadVaultSummary.spec | 2 ++ certora/specs/VaultERC4626.spec | 22 +++++++++++++++++++++- 5 files changed, 40 insertions(+), 2 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index 4a7c814f..f65cc648 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -38,6 +38,7 @@ // "coverage_info" : "advanced", // "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", + "method" : "deposit(uint256,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, "prover_version" : "master", diff --git a/certora/harness/ERC4626Harness.sol b/certora/harness/ERC4626Harness.sol index be54dc09..287895df 100644 --- a/certora/harness/ERC4626Harness.sol +++ b/certora/harness/ERC4626Harness.sol @@ -13,7 +13,10 @@ contract ERC4626Harness is VaultModule, TokenModule, AbstractBaseHarness { IERC20 underlying_asset; function userAssets(address user) public view returns (uint256) { // harnessed - return IERC20(asset()).balanceOf(user); + // The assets in the underlying asset contract (not in the vault) + return IERC20(asset()).balanceOf(user); + // The assets stored in the vault for a user. + // return vaultStorage.users[user].getBalance().toAssetsDown(loadVault()).toUint(); } // VaultStorage Accessors: diff --git a/certora/specs/Base.spec b/certora/specs/Base.spec index 1291f26a..874fdbaf 100644 --- a/certora/specs/Base.spec +++ b/certora/specs/Base.spec @@ -74,4 +74,16 @@ function actualCaller(env e) returns address { } else { return e.msg.sender; } +} + +function actualCallerCheckController(env e) returns address { + if(e.msg.sender == evc) { + address onBehalf; + bool unused; + // Similar to EVCAuthenticateDeferred when checkController is true. + onBehalf, unused = evc.getCurrentOnBehalfOfAccount(e, currentContract); + return onBehalf; + } else { + return e.msg.sender; + } } \ No newline at end of file diff --git a/certora/specs/LoadVaultSummary.spec b/certora/specs/LoadVaultSummary.spec index bf3dae7a..bce8f1f3 100644 --- a/certora/specs/LoadVaultSummary.spec +++ b/certora/specs/LoadVaultSummary.spec @@ -14,6 +14,8 @@ methods { function storage_configFlags() external returns (BaseHarness.Flags) envfree; } + + // need to make sure successive calls only return different values // when this is actually possible in the real call... // * calls with the same env will return all the same values diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 99c59ab6..ce2d856c 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -64,8 +64,12 @@ methods { // function ERC20a.balanceOf(address) external returns uint256 envfree; // NOT ENVFREE function ERC20a.transferFrom(address,address,uint256) external returns bool; // not envfree + function RPow.rpow(uint256 x, uint256 y, uint256 base) internal returns (uint256, bool) => CVLPow(x, y, base); + // See comment near CVLgetCurrentOnBehalfOfAccount definition. + function _.getCurrentOnBehalfOfAccount(address controller) external => CVLgetCurrentOnBehalfOfAccount(controller) expect (address, bool); + // These are unresolved calls that havoc contract state. // Most of these cause these havocs because of a low-level call // operation and are irrelevant for the rules. @@ -84,6 +88,16 @@ methods { } +// This is not in the scene for this config, so we just want it to be +// an uninterpreted function rather than NONDET so that +// we get the same value when this is called for different parts +ghost CVLgetCurrentOnBehalfOfAccountAddr(address) returns address; +ghost CVLgetCurrentOnBehalfOfAccountBool(address) returns bool; +function CVLgetCurrentOnBehalfOfAccount(address addr) returns (address, bool) { + return (CVLgetCurrentOnBehalfOfAccountAddr(addr), + CVLgetCurrentOnBehalfOfAccountBool(addr)); +} + // Summarize trySafeTransferFrom as DummyERC20 transferFrom function CVLTrySafeTransferFrom(env e, address from, address to, uint256 value) returns (bool, bytes) { bytes ret; // Ideally bytes("") if there is a way to do this @@ -372,7 +386,13 @@ filtered { } { env e; uint256 assets; uint256 shares; - address contributor; require contributor == e.msg.sender; + address contributor; + + // need to minimize these + require actualCaller(e) == contributor; + require contributor == CVLgetCurrentOnBehalfOfAccountAddr(0); + require actualCallerCheckController(e) == contributor; + address receiver; require currentContract != contributor && currentContract != receiver; From 2fb4c1d2d248113d2ae13d8157716254d1d67cc4 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 9 May 2024 12:37:15 +0100 Subject: [PATCH 076/152] tweak comments in loadvault, accumulatedFees gtz --- certora/conf/VaultERC4626.conf | 2 +- certora/specs/LoadVaultSummary.spec | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index f65cc648..da9576b6 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -38,7 +38,7 @@ // "coverage_info" : "advanced", // "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", - "method" : "deposit(uint256,address)", + // "method" : "deposit(uint256,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, "prover_version" : "master", diff --git a/certora/specs/LoadVaultSummary.spec b/certora/specs/LoadVaultSummary.spec index bce8f1f3..da15ce42 100644 --- a/certora/specs/LoadVaultSummary.spec +++ b/certora/specs/LoadVaultSummary.spec @@ -33,8 +33,6 @@ function CVLloadVault(env e) returns BaseHarness.VaultCache { // for debugging performance BaseHarness.VaultCache vaultCache; - // These are used more than once - // not sure if our tool already de-duplicates these; BaseHarness.Owed oldTotalBorrows = storage_totalBorrows(); BaseHarness.Shares oldTotalShares = storage_totalShares(); @@ -48,10 +46,13 @@ function CVLloadVault(env e) returns BaseHarness.VaultCache { require vaultCache.totalBorrows == require_uint144(oldTotalBorrows + interestBorrows); mathint newTotalAssets = vaultCache.cash + vaultCache.totalBorrows; + + // a more accurate but seemingly unnecessary model of feeAssests: // uint16 feeScalar; // uint256 feeAssets = require_uint256(feeScalar * (vaultCache.totalBorrows - oldTotalBorrows)); + // underapproximate interesteFee as 1 (1e4 in impl) - // this is a separate variable just for readability. + // feeAssets is a separate variable just for readability. uint256 feeAssets = interestBorrows; if (require_uint256(newTotalAssets) > feeAssets) { require vaultCache.totalShares == require_uint112(oldTotalShares * newTotalAssets / @@ -60,9 +61,12 @@ function CVLloadVault(env e) returns BaseHarness.VaultCache { require vaultCache.totalShares == oldTotalShares; } - // not relevant to our ERC4626 rules, so havoced - uint112 accumulatedFeesHavoc; - require vaultCache.accumulatedFees == accumulatedFeesHavoc; + // not relevant to our ERC4626 rules, so left uninitialized. + // (havocing is better in terms of safety, but the state space is larger) + // uint112 accumulatedFeesHavoc; + // require vaultCache.accumulatedFees == accumulatedFeesHavoc; + // havoc + require vaultCache.accumulatedFees > 0; // require vaultCache.accumulatedFees == require_uint112(storage_accumulatedFees() + vaultCache.totalShares - oldTotalShares); // havoc interestAccumulator (not relevant to rule or model) From 65208b64b6079981a4f798ea8673d05d0123433e Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 9 May 2024 13:11:17 +0100 Subject: [PATCH 077/152] Add passing run link --- certora/conf/VaultERC4626.conf | 1 + certora/specs/LoadVaultSummary.spec | 9 +++------ certora/specs/VaultERC4626.spec | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index da9576b6..139cfc6b 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -39,6 +39,7 @@ // "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", // "method" : "deposit(uint256,address)", + // "method" : "mint(uint256,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, "prover_version" : "master", diff --git a/certora/specs/LoadVaultSummary.spec b/certora/specs/LoadVaultSummary.spec index da15ce42..dbd183d5 100644 --- a/certora/specs/LoadVaultSummary.spec +++ b/certora/specs/LoadVaultSummary.spec @@ -61,12 +61,9 @@ function CVLloadVault(env e) returns BaseHarness.VaultCache { require vaultCache.totalShares == oldTotalShares; } - // not relevant to our ERC4626 rules, so left uninitialized. - // (havocing is better in terms of safety, but the state space is larger) - // uint112 accumulatedFeesHavoc; - // require vaultCache.accumulatedFees == accumulatedFeesHavoc; - // havoc - require vaultCache.accumulatedFees > 0; + // havoc vaultCache.accumulatedFees + uint112 accumulatedFeesHavoc; + require vaultCache.accumulatedFees == accumulatedFeesHavoc; // require vaultCache.accumulatedFees == require_uint112(storage_accumulatedFees() + vaultCache.totalShares - oldTotalShares); // havoc interestAccumulator (not relevant to rule or model) diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index ce2d856c..ee4d57b7 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -379,6 +379,7 @@ rule redeemingAllValidity() { //// # stakeholder properties (Risk Analysis ) ////////// //////////////////////////////////////////////////////////////////////////////// +// passing. run: https://prover.certora.com/output/65266/48a3074474f1475baf13fe3cb9602567/?anonymousKey=9111d29e8d8ed721825b12f083128af396e5e814 rule contributingProducesShares(method f) filtered { f -> f.selector == sig:deposit(uint256,address).selector From 545820d10fdaa84986a299a3a01840a59d6e124b Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 10 May 2024 10:38:49 +0100 Subject: [PATCH 078/152] initVault summary --- certora/conf/VaultERC4626.conf | 4 +- certora/specs/LoadVaultSummary.spec | 65 ++++++++++++++++++++++++++++- certora/specs/VaultERC4626.spec | 16 ++++++- 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index 139cfc6b..c90e674f 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -35,10 +35,10 @@ // // "totalsMonotonicity", // // "vaultSolvency" // ], - // "coverage_info" : "advanced", + "coverage_info" : "advanced", // "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", - // "method" : "deposit(uint256,address)", + "method" : "deposit(uint256,address)", // "method" : "mint(uint256,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, diff --git a/certora/specs/LoadVaultSummary.spec b/certora/specs/LoadVaultSummary.spec index dbd183d5..e9a69167 100644 --- a/certora/specs/LoadVaultSummary.spec +++ b/certora/specs/LoadVaultSummary.spec @@ -1,6 +1,8 @@ import "./Base.spec"; methods { - function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLloadVault(e); + // function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLloadVault(e); + function Cache.initVaultCache(BaseHarness.VaultCache memory vaultCache) internal returns (bool) with (env e) => CVLInitVaultCache(e, vaultCache); + function storage_lastInterestAccumulatorUpdate() external returns (uint48) envfree; function storage_cash() external returns (BaseHarness.Assets) envfree; function storage_supplyCap() external returns (BaseHarness.AmountCap) envfree; @@ -29,7 +31,66 @@ persistent ghost newInterestBorrows(uint256) returns uint256; // not even need to model this. It can just be an uninterp function // because in the ERC4626 spec there are no rules with multiple env. -function CVLloadVault(env e) returns BaseHarness.VaultCache { +function CVLInitVaultCache(env e, BaseHarness.VaultCache vaultCache) returns bool { + uint48 lastUpdate = storage_lastInterestAccumulatorUpdate(); + BaseHarness.Owed oldTotalBorrows = storage_totalBorrows(); + BaseHarness.Shares oldTotalShares = storage_totalShares(); + require vaultCache.cash == storage_cash(); + uint48 timestamp48 = require_uint48(e.block.timestamp); + bool updated = timestamp48 != lastUpdate; + if(updated) { + require vaultCache.lastInterestAccumulatorUpdate == timestamp48; + + // totalBorrows + uint256 interestBorrows = newInterestBorrows(e.block.timestamp); + require vaultCache.totalBorrows == require_uint144(oldTotalBorrows + interestBorrows); + + // totalShares + mathint newTotalAssets = vaultCache.cash + vaultCache.totalBorrows; + // underapproximate interesteFee as 1 (1e4 in impl) + // feeAssets is a separate variable just for readability. + uint256 feeAssets = interestBorrows; + require feeAssets < require_uint256(newTotalAssets); + if (feeAssets > 0) { + require vaultCache.totalShares == require_uint112(oldTotalShares * newTotalAssets / (newTotalAssets - feeAssets)); + } else { + require vaultCache.totalShares == oldTotalShares; + } + + // accumulatedFees + mathint accFees = storage_accumulatedFees() + + vaultCache.totalShares - oldTotalShares; + require vaultCache.accumulatedFees == require_uint112(accFees); + + // interestAccumulator + require vaultCache.interestAccumulator >= storage_interestAccumulator(); + + } else { + require vaultCache.lastInterestAccumulatorUpdate == lastUpdate; + require vaultCache.totalBorrows == oldTotalBorrows; + require vaultCache.totalShares == oldTotalShares; + require vaultCache.accumulatedFees == storage_accumulatedFees(); + require vaultCache.interestAccumulator == storage_interestAccumulator(); + } + + // unmodified values + require vaultCache.supplyCap == assert_uint256(storage_supplyCap()); + require vaultCache.borrowCap == assert_uint256(storage_borrowCap()); + require vaultCache.hookedOps == storage_hookedOps(); + require vaultCache.configFlags == storage_configFlags(); + require vaultCache.snapshotInitialized == storage_snapshotInitialized(); + + // either of these cause a vacuity failure ... + // require vaultCache.asset == erc20; + // require vaultCache.asset == asset(); + require vaultCache.oracle == oracleAddress; + require vaultCache.unitOfAccount == unitOfAccount; + + return updated; +} + + +function CVLloadVaultOld(env e) returns BaseHarness.VaultCache { // for debugging performance BaseHarness.VaultCache vaultCache; diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index ee4d57b7..3beef894 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -81,8 +81,8 @@ methods { // that includes bytes memory). So it is summarized as // DummyERC20a.transferFrom function _.trySafeTransferFrom(address token, address from, address to, uint256 value) internal with (env e) => CVLTrySafeTransferFrom(e, from, to, value) expect (bool, bytes memory); - // safeTransferFrom can be made NONDET, but is summarized as transferFrom - // from DummyERC20a anyway. + // safeTransferFrom is summarized as transferFrom + // from DummyERC20a to avoid dealing with the low-level `call` function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLTrySafeTransferFrom(e, from, to, value) expect (bool, bytes memory); function _.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal => NONDET; @@ -203,6 +203,11 @@ rule zeroDepositZeroShares(uint assets, address receiver) env e; uint shares = deposit(e,assets, receiver); + // In this Vault, max_uint256 as an argument will transfer all assets + // to the vault. This precondition rules out the case where + // the depositor calls deposit with a blance of 0 in the underlying + // asset and gives max_uint256 as the shares. + require assets < max_uint256; assert shares == 0 <=> assets == 0; } @@ -564,3 +569,10 @@ rule sanity (method f) { f(e, args); assert false; } + +// rule vaultCacheSanity (method f) { +// env e; +// BaseHarness.VaultCache vaultCache; +// CVLInitVaultCache(e, vaultCache); +// assert false; +// } \ No newline at end of file From 68c667fb11f96f9bafeeab73c6e308840430d024 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 10 May 2024 12:54:08 +0100 Subject: [PATCH 079/152] fixed sanity in totalSupplyIsSumOfBalances and it passes --- certora/conf/VaultERC4626.conf | 12 +++---- certora/harness/ERC4626Harness.sol | 8 ++--- certora/scripts/runERC4626RulesSeparately.py | 37 ++++++++++++++++++++ certora/specs/Base.spec | 2 +- certora/specs/LoadVaultSummary.spec | 10 +++--- certora/specs/VaultERC4626.spec | 2 ++ 6 files changed, 55 insertions(+), 16 deletions(-) create mode 100644 certora/scripts/runERC4626RulesSeparately.py diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index c90e674f..6c4b146b 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -23,22 +23,22 @@ ], // "rule": [ // // "conversionWeakMonotonicity", - // // "assetsMoreThanSupply", - // // "contributingProducesShares", + // // "assetsMoreThanSupply", + // // "contributingProducesShares", // // "depositMonotonicity", // // "dustFavorsTheHouse", - // // "noAssetsIfNoSupply", + // // "noAssetsIfNoSupply", // // "noSupplyIfNoAssets", // // "onlyContributionMethodsReduceAssets", - // // "reclaimingProducesAssets", + // // "reclaimingProducesAssets", // // "redeemingAllValidity", // // "totalsMonotonicity", // // "vaultSolvency" // ], - "coverage_info" : "advanced", + // "coverage_info" : "advanced", // "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", - "method" : "deposit(uint256,address)", + // "method" : "deposit(uint256,address)", // "method" : "mint(uint256,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, diff --git a/certora/harness/ERC4626Harness.sol b/certora/harness/ERC4626Harness.sol index 287895df..9cc5b0f3 100644 --- a/certora/harness/ERC4626Harness.sol +++ b/certora/harness/ERC4626Harness.sol @@ -26,11 +26,11 @@ contract ERC4626Harness is VaultModule, TokenModule, AbstractBaseHarness { function storage_cash() public view returns (Assets) { return vaultStorage.cash; } - function storage_supplyCap() public view returns (AmountCap) { - return vaultStorage.supplyCap; + function storage_supplyCap() public view returns (uint256) { + return vaultStorage.supplyCap.resolve(); } - function storage_borrowCap() public view returns (AmountCap) { - return vaultStorage.borrowCap; + function storage_borrowCap() public view returns (uint256) { + return vaultStorage.borrowCap.resolve(); } // reentrancyLocked seems not direclty used in loadVault function storage_hookedOps() public view returns (Flags) { diff --git a/certora/scripts/runERC4626RulesSeparately.py b/certora/scripts/runERC4626RulesSeparately.py new file mode 100644 index 00000000..5d79ec78 --- /dev/null +++ b/certora/scripts/runERC4626RulesSeparately.py @@ -0,0 +1,37 @@ +import argparse +import subprocess + +parser = argparse.ArgumentParser() +parser.add_argument('-M', '--batchMsg', metavar='M', type=str, nargs='?', + default='', + help='a message for all the jobs') + +rule_names = [ + "conversionOfZero", + "convertToAssetsWeakAdditivity", + "convertToSharesWeakAdditivity", + "conversionWeakMonotonicity", + "conversionWeakIntegrity", + "convertToCorrectness", + "depositMonotonicity", + "zeroDepositZeroShares", + "assetsMoreThanSupply", + "noAssetsIfNoSupply", + "noSupplyIfNoAssets", + "totalSupplyIsSumOfBalances", + "totalsMonotonicity", + "underlyingCannotChange", + "dustFavorsTheHouse", + "vaultSolvency", + "redeemingAllValidity", + "contributingProducesShares", + "onlyContributionMethodsReduceAssets", + "reclaimingProducesAssets" +] + +for name in rule_names: + args = parser.parse_args() + script = "certora/conf/VaultERC4626.conf" + command = f"certoraRun {script} --rule \"{name}\" --msg \"{name} : {args.batchMsg}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) \ No newline at end of file diff --git a/certora/specs/Base.spec b/certora/specs/Base.spec index 874fdbaf..247b72c9 100644 --- a/certora/specs/Base.spec +++ b/certora/specs/Base.spec @@ -25,7 +25,7 @@ methods { function _.symbol() external => DISPATCHER(true); function _.decimals() external => DISPATCHER(true); function _.totalSupply() external => DISPATCHER(true); - function _.balanceOf(address) external => DISPATCHER(true); + // function _.balanceOf(address) external => DISPATCHER(true); function _.allowance(address,address) external => DISPATCHER(true); function _.approve(address,uint256) external => DISPATCHER(true); function _.transfer(address,uint256) external => DISPATCHER(true); diff --git a/certora/specs/LoadVaultSummary.spec b/certora/specs/LoadVaultSummary.spec index e9a69167..51d54431 100644 --- a/certora/specs/LoadVaultSummary.spec +++ b/certora/specs/LoadVaultSummary.spec @@ -5,8 +5,8 @@ methods { function storage_lastInterestAccumulatorUpdate() external returns (uint48) envfree; function storage_cash() external returns (BaseHarness.Assets) envfree; - function storage_supplyCap() external returns (BaseHarness.AmountCap) envfree; - function storage_borrowCap() external returns (BaseHarness.AmountCap) envfree; + function storage_supplyCap() external returns (uint256) envfree; + function storage_borrowCap() external returns (uint256) envfree; function storage_hookedOps() external returns (BaseHarness.Flags) envfree; function storage_snapshotInitialized() external returns (bool) envfree; function storage_totalShares() external returns (BaseHarness.Shares) envfree; @@ -74,11 +74,11 @@ function CVLInitVaultCache(env e, BaseHarness.VaultCache vaultCache) returns boo } // unmodified values - require vaultCache.supplyCap == assert_uint256(storage_supplyCap()); - require vaultCache.borrowCap == assert_uint256(storage_borrowCap()); + // require vaultCache.supplyCap == storage_supplyCap(); + // require vaultCache.borrowCap == storage_borrowCap(); require vaultCache.hookedOps == storage_hookedOps(); require vaultCache.configFlags == storage_configFlags(); - require vaultCache.snapshotInitialized == storage_snapshotInitialized(); + // require vaultCache.snapshotInitialized == storage_snapshotInitialized(); // either of these cause a vacuity failure ... // require vaultCache.asset == erc20; diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 3beef894..15aa0b7f 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -85,6 +85,7 @@ methods { // from DummyERC20a to avoid dealing with the low-level `call` function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLTrySafeTransferFrom(e, from, to, value) expect (bool, bytes memory); function _.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal => NONDET; + function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; } @@ -268,6 +269,7 @@ hook Sload Vault.PackedUserSlot val currentContract.vaultStorage.users[KEY addre // require sumOfBalances >= to_mathint(val); // } +// passing: https://prover.certora.com/output/65266/c4edfe162c2a447795ebf8ceb1318e28?anonymousKey=3dd660707318d77990a4ad7b404dc1b290f78e75 invariant totalSupplyIsSumOfBalances(env e) to_mathint(totalSupply(e)) == sumOfBalances; From c30ca88e2eb3a44e2575039d9f6df1a026f95b75 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 13 May 2024 10:52:57 +0100 Subject: [PATCH 080/152] Add run links --- certora/specs/VaultERC4626.spec | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 15aa0b7f..dc8816dc 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -120,6 +120,8 @@ rule conversionOfZero { "converting zero assets must return zero shares"; } +// passing +// run: https://prover.certora.com/output/65266/e7e04c3291f843ba9fe0b81ea9a1f949/?anonymousKey=1828bc78fcb1ed87cf33d17878823becfad2ca23 rule convertToAssetsWeakAdditivity() { env e; uint256 sharesA; uint256 sharesB; @@ -130,6 +132,8 @@ rule convertToAssetsWeakAdditivity() { "converting sharesA and sharesB to assets then summing them must yield a smaller or equal result to summing them then converting"; } +// passing +// run: https://prover.certora.com/output/65266/3bd31b8e066543fc8097a0ffce93ee41/?anonymousKey=5b8d2876fecf3d8af7a550e203faa4d58bbedf5c rule convertToSharesWeakAdditivity() { env e; uint256 assetsA; uint256 assetsB; @@ -153,6 +157,8 @@ rule conversionWeakMonotonicity { "converting more assets must yield equal or greater shares"; } +// passing +// run: https://prover.certora.com/output/65266/302371dbde0246a28808b078c2164615/?anonymousKey=9759cd932017c8a142c5e1c4d6fa312b4ef94ae3 rule conversionWeakIntegrity() { env e; uint256 sharesOrAssets; @@ -198,7 +204,7 @@ rule depositMonotonicity() { "when supply tokens outnumber asset tokens, a larger deposit of assets must produce an equal or greater number of shares"; } - +//run: https://prover.certora.com/output/65266/8d021eab19f945cd86a3ef904b0aa6dc/?anonymousKey=bd4cc32f9af86278b0eceaae2316ea3e385c1cdf rule zeroDepositZeroShares(uint assets, address receiver) { env e; @@ -269,7 +275,7 @@ hook Sload Vault.PackedUserSlot val currentContract.vaultStorage.users[KEY addre // require sumOfBalances >= to_mathint(val); // } -// passing: https://prover.certora.com/output/65266/c4edfe162c2a447795ebf8ceb1318e28?anonymousKey=3dd660707318d77990a4ad7b404dc1b290f78e75 +// passing: https://prover.certora.com/output/65266/de3636d287d2473294463c07263fc11e/?anonymousKey=ac8f74e6c5c1298f0954a21fafd41cccf32b9ffb invariant totalSupplyIsSumOfBalances(env e) to_mathint(totalSupply(e)) == sumOfBalances; @@ -279,7 +285,7 @@ invariant totalSupplyIsSumOfBalances(env e) //// # State Transition ///// //////////////////////////////////////////////////////////////////////////////// - +//run: https://prover.certora.com/output/65266/3ef25c98a7e34422bcf177d853662b5f/?anonymousKey=ca43e967a607a404f34b39c70f6517e90dac0902 rule totalsMonotonicity() { method f; env e; calldataarg args; require e.msg.sender != currentContract; From db52af4965c10b539c24384969806e9f514f0ffa Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 16 May 2024 09:42:25 +0100 Subject: [PATCH 081/152] Mainly LoadVault summary again --- certora/conf/VaultERC4626.conf | 12 ++-- certora/harness/ERC4626Harness.sol | 19 ++++++ certora/specs/Base.spec | 19 +++++- certora/specs/LoadVaultSummary.spec | 95 ++++++++++++++++++++++++++++- certora/specs/VaultERC4626.spec | 21 ++++++- 5 files changed, 153 insertions(+), 13 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index 6c4b146b..ab523de9 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -48,12 +48,12 @@ "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", - "prover_args": [ - "-smt_nonLinearArithmetic true", - "-adaptiveSolverConfig false", - "-solvers [cvc5:nonlin{randomSeed=1},cvc5:nonlin{randomSeed=2},cvc5:nonlin{randomSeed=3},cvc5:nonlin{randomSeed=4},cvc5:nonlin{randomSeed=5},cvc5:nonlin{randomSeed=6},cvc5:nonlin{randomSeed=7},cvc5:nonlin{randomSeed=8},cvc5:nonlin{randomSeed=9},cvc5:nonlin{randomSeed=10}]", - "-depth 0" - ], + // "prover_args": [ + // "-smt_nonLinearArithmetic true", + // "-adaptiveSolverConfig false", + // "-solvers [cvc5:nonlin{randomSeed=1},cvc5:nonlin{randomSeed=2},cvc5:nonlin{randomSeed=3},cvc5:nonlin{randomSeed=4},cvc5:nonlin{randomSeed=5},cvc5:nonlin{randomSeed=6},cvc5:nonlin{randomSeed=7},cvc5:nonlin{randomSeed=8},cvc5:nonlin{randomSeed=9},cvc5:nonlin{randomSeed=10}]", + // "-depth 0" + // ], // old config // "prover_args": [ // "-smt_nonLinearArithmetic true", diff --git a/certora/harness/ERC4626Harness.sol b/certora/harness/ERC4626Harness.sol index 9cc5b0f3..fd8f82fa 100644 --- a/certora/harness/ERC4626Harness.sol +++ b/certora/harness/ERC4626Harness.sol @@ -19,6 +19,25 @@ contract ERC4626Harness is VaultModule, TokenModule, AbstractBaseHarness { // return vaultStorage.users[user].getBalance().toAssetsDown(loadVault()).toUint(); } + function updateVault() internal override returns (VaultCache memory vaultCache) { + // initVaultCache is difficult to summarize because we can't + // reason about the pass-by-value VaultCache at the start and + // end of the call as separate values. So this harness + // gives us a way to keep the loadVault summary when updateVault + // is called + vaultCache = loadVault(); + if(block.timestamp - vaultCache.lastInterestAccumulatorUpdate > 0) { + vaultStorage.lastInterestAccumulatorUpdate = vaultCache.lastInterestAccumulatorUpdate; + vaultStorage.accumulatedFees = vaultCache.accumulatedFees; + + vaultStorage.totalShares = vaultCache.totalShares; + vaultStorage.totalBorrows = vaultCache.totalBorrows; + + vaultStorage.interestAccumulator = vaultCache.interestAccumulator; + } + return vaultCache; + } + // VaultStorage Accessors: function storage_lastInterestAccumulatorUpdate() public view returns (uint48) { return vaultStorage.lastInterestAccumulatorUpdate; diff --git a/certora/specs/Base.spec b/certora/specs/Base.spec index 247b72c9..8ec607a7 100644 --- a/certora/specs/Base.spec +++ b/certora/specs/Base.spec @@ -19,13 +19,14 @@ methods { // ProxyUtils function ProxyUtils.metadata() internal returns (address, address, address)=> CVLProxyMetadata(); + function ProxyUtils.useViewCaller() internal returns (address) => CVLUseViewCaller(); // IERC20 function _.name() external => DISPATCHER(true); function _.symbol() external => DISPATCHER(true); function _.decimals() external => DISPATCHER(true); function _.totalSupply() external => DISPATCHER(true); - // function _.balanceOf(address) external => DISPATCHER(true); + function _.balanceOf(address) external => DISPATCHER(true); function _.allowance(address,address) external => DISPATCHER(true); function _.approve(address,uint256) external => DISPATCHER(true); function _.transfer(address,uint256) external => DISPATCHER(true); @@ -48,11 +49,23 @@ function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint ); } -ghost address oracleAddress; -ghost address unitOfAccount; +ghost address oracleAddress { + init_state axiom oracleAddress != 0; +} +ghost address unitOfAccount { + init_state axiom unitOfAccount != 0; +} function CVLProxyMetadata() returns (address, address, address) { + // Require addresses not zero? return (erc20, oracleAddress, unitOfAccount); } +persistent ghost address viewCallerGhost { + init_state axiom viewCallerGhost != 0; +} +function CVLUseViewCaller() returns address { + // require not zero? + return viewCallerGhost; +} function LTVConfigAssumptions(env e, BaseHarness.LTVConfig ltvConfig) returns bool { bool targetLTVLessOne = ltvConfig.targetLTV < 10000; diff --git a/certora/specs/LoadVaultSummary.spec b/certora/specs/LoadVaultSummary.spec index 51d54431..f4f181af 100644 --- a/certora/specs/LoadVaultSummary.spec +++ b/certora/specs/LoadVaultSummary.spec @@ -1,7 +1,9 @@ import "./Base.spec"; methods { - // function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLloadVault(e); - function Cache.initVaultCache(BaseHarness.VaultCache memory vaultCache) internal returns (bool) with (env e) => CVLInitVaultCache(e, vaultCache); + // function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLLoadVault(e); + function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLLoadVaultAssumeNoUpdate(e); + // function Cache.initVaultCache(BaseHarness.VaultCache memory vaultCache) internal returns (bool) with (env e) => CVLInitVaultCache(e, vaultCache); + // function Cache.initVaultCache(BaseHarness.VaultCache memory vaultCache) internal returns (bool) with (env e) => CVLInitVaultCacheSimpleCopy(e, vaultCache); function storage_lastInterestAccumulatorUpdate() external returns (uint48) envfree; function storage_cash() external returns (BaseHarness.Assets) envfree; @@ -38,6 +40,7 @@ function CVLInitVaultCache(env e, BaseHarness.VaultCache vaultCache) returns boo require vaultCache.cash == storage_cash(); uint48 timestamp48 = require_uint48(e.block.timestamp); bool updated = timestamp48 != lastUpdate; + require !updated; if(updated) { require vaultCache.lastInterestAccumulatorUpdate == timestamp48; @@ -89,6 +92,94 @@ function CVLInitVaultCache(env e, BaseHarness.VaultCache vaultCache) returns boo return updated; } +function CVLLoadVault(env e) returns BaseHarness.VaultCache { + BaseHarness.VaultCache vaultCache; + uint48 lastUpdate = storage_lastInterestAccumulatorUpdate(); + BaseHarness.Owed oldTotalBorrows = storage_totalBorrows(); + BaseHarness.Shares oldTotalShares = storage_totalShares(); + require vaultCache.cash == storage_cash(); + uint48 timestamp48 = require_uint48(e.block.timestamp); + bool updated = timestamp48 != lastUpdate; + if(updated) { + require vaultCache.lastInterestAccumulatorUpdate == timestamp48; + + // totalBorrows + uint256 interestBorrows = newInterestBorrows(e.block.timestamp); + require vaultCache.totalBorrows == require_uint144(oldTotalBorrows + interestBorrows); + + // totalShares + mathint newTotalAssets = vaultCache.cash + vaultCache.totalBorrows; + // underapproximate interesteFee as 1 (1e4 in impl) + // feeAssets is a separate variable just for readability. + uint256 feeAssets = interestBorrows; + require feeAssets < require_uint256(newTotalAssets); + if (feeAssets > 0) { + require vaultCache.totalShares == require_uint112(oldTotalShares * newTotalAssets / (newTotalAssets - feeAssets)); + } else { + require vaultCache.totalShares == oldTotalShares; + } + + // accumulatedFees + mathint accFees = storage_accumulatedFees() + + vaultCache.totalShares - oldTotalShares; + require vaultCache.accumulatedFees == require_uint112(accFees); + + // interestAccumulator + require vaultCache.interestAccumulator >= storage_interestAccumulator(); + + } else { + require vaultCache.lastInterestAccumulatorUpdate == lastUpdate; + require vaultCache.totalBorrows == oldTotalBorrows; + require vaultCache.totalShares == oldTotalShares; + require vaultCache.accumulatedFees == storage_accumulatedFees(); + require vaultCache.interestAccumulator == storage_interestAccumulator(); + } + + // unmodified values + require vaultCache.supplyCap == storage_supplyCap(); + require vaultCache.borrowCap == storage_borrowCap(); + require vaultCache.hookedOps == storage_hookedOps(); + require vaultCache.configFlags == storage_configFlags(); + require vaultCache.snapshotInitialized == storage_snapshotInitialized(); + + require vaultCache.asset == erc20; + require vaultCache.asset == asset(); + require vaultCache.oracle == oracleAddress; + require vaultCache.unitOfAccount == unitOfAccount; + + return vaultCache; +} + +function CVLLoadVaultAssumeNoUpdate(env e) returns BaseHarness.VaultCache { + BaseHarness.VaultCache vaultCache; + uint48 lastUpdate = storage_lastInterestAccumulatorUpdate(); + BaseHarness.Owed oldTotalBorrows = storage_totalBorrows(); + BaseHarness.Shares oldTotalShares = storage_totalShares(); + require vaultCache.cash == storage_cash(); + uint48 timestamp48 = require_uint48(e.block.timestamp); + bool updated = timestamp48 != lastUpdate; + require !updated; + require vaultCache.lastInterestAccumulatorUpdate == lastUpdate; + require vaultCache.totalBorrows == oldTotalBorrows; + require vaultCache.totalShares == oldTotalShares; + require vaultCache.accumulatedFees == storage_accumulatedFees(); + require vaultCache.interestAccumulator == storage_interestAccumulator(); + + // unmodified values + require vaultCache.supplyCap == storage_supplyCap(); + require vaultCache.borrowCap == storage_borrowCap(); + require vaultCache.hookedOps == storage_hookedOps(); + require vaultCache.configFlags == storage_configFlags(); + require vaultCache.snapshotInitialized == storage_snapshotInitialized(); + + require vaultCache.asset == erc20; + require vaultCache.asset == asset(); + require vaultCache.oracle == oracleAddress; + require vaultCache.unitOfAccount == unitOfAccount; + + return vaultCache; +} + function CVLloadVaultOld(env e) returns BaseHarness.VaultCache { // for debugging performance diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index dc8816dc..5505a390 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -77,6 +77,7 @@ methods { // another unresolved call that havocs all contracts function _.requireVaultStatusCheck() external => NONDET; function _.requireAccountAndVaultStatusCheck(address account) external => NONDET; + function EthereumVaultConnector.getAccountOwner(address account) external returns address => CVLGetAccountOwner(account); // trySafeTransferFrom cannot be summarized as NONDET (due to return type // that includes bytes memory). So it is summarized as // DummyERC20a.transferFrom @@ -92,12 +93,15 @@ methods { // This is not in the scene for this config, so we just want it to be // an uninterpreted function rather than NONDET so that // we get the same value when this is called for different parts -ghost CVLgetCurrentOnBehalfOfAccountAddr(address) returns address; +ghost CVLgetCurrentOnBehalfOfAccountAddr(address) returns address { + axiom forall address x. CVLgetCurrentOnBehalfOfAccountAddr(x) != currentContract; +} ghost CVLgetCurrentOnBehalfOfAccountBool(address) returns bool; function CVLgetCurrentOnBehalfOfAccount(address addr) returns (address, bool) { return (CVLgetCurrentOnBehalfOfAccountAddr(addr), CVLgetCurrentOnBehalfOfAccountBool(addr)); } +persistent ghost CVLGetAccountOwner(address) returns address; // Summarize trySafeTransferFrom as DummyERC20 transferFrom function CVLTrySafeTransferFrom(env e, address from, address to, uint256 value) returns (bool, bytes) { @@ -105,6 +109,7 @@ function CVLTrySafeTransferFrom(env e, address from, address to, uint256 value) return (ERC20a.transferFrom(e, from, to, value), ret); } + //////////////////////////////////////////////////////////////////////////////// //// # asset To shares mathematical properties ///// //////////////////////////////////////////////////////////////////////////////// @@ -254,7 +259,7 @@ invariant noSupplyIfNoAssets(env e) -ghost mathint sumOfBalances { +persistent ghost mathint sumOfBalances { init_state axiom sumOfBalances == 0; } @@ -277,6 +282,7 @@ hook Sload Vault.PackedUserSlot val currentContract.vaultStorage.users[KEY addre // passing: https://prover.certora.com/output/65266/de3636d287d2473294463c07263fc11e/?anonymousKey=ac8f74e6c5c1298f0954a21fafd41cccf32b9ffb invariant totalSupplyIsSumOfBalances(env e) + // to_mathint(totalSupply(e)) == sumOfBalances + accumulatedFees(e); to_mathint(totalSupply(e)) == sumOfBalances; @@ -369,7 +375,10 @@ invariant vaultSolvency(env e) totalAssets(e) >= totalSupply(e) && userAssets(e, currentContract) >= totalAssets(e) { preserved { requireInvariant totalSupplyIsSumOfBalances(e); + require e.msg.sender == evc; require e.msg.sender != currentContract; + require actualCaller(e) != currentContract; + require actualCallerCheckController(e) != currentContract; require currentContract != asset(); } } @@ -583,4 +592,12 @@ rule sanity (method f) { // BaseHarness.VaultCache vaultCache; // CVLInitVaultCache(e, vaultCache); // assert false; +// } + +// rule totalSupplySanity { +// env e; +// BaseHarness.VaultCache vaultCache; +// CVLInitVaultCache(e, vaultCache); +// totalSupply(e); +// assert false; // } \ No newline at end of file From b5ca4d169a4808f08e318fe129ea48938ed8418e Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 16 May 2024 18:13:39 +0100 Subject: [PATCH 082/152] delete InitVault summary --- certora/specs/LoadVaultSummary.spec | 113 ---------------------------- certora/specs/VaultERC4626.spec | 23 ++++-- 2 files changed, 15 insertions(+), 121 deletions(-) diff --git a/certora/specs/LoadVaultSummary.spec b/certora/specs/LoadVaultSummary.spec index f4f181af..7e2333c1 100644 --- a/certora/specs/LoadVaultSummary.spec +++ b/certora/specs/LoadVaultSummary.spec @@ -33,65 +33,6 @@ persistent ghost newInterestBorrows(uint256) returns uint256; // not even need to model this. It can just be an uninterp function // because in the ERC4626 spec there are no rules with multiple env. -function CVLInitVaultCache(env e, BaseHarness.VaultCache vaultCache) returns bool { - uint48 lastUpdate = storage_lastInterestAccumulatorUpdate(); - BaseHarness.Owed oldTotalBorrows = storage_totalBorrows(); - BaseHarness.Shares oldTotalShares = storage_totalShares(); - require vaultCache.cash == storage_cash(); - uint48 timestamp48 = require_uint48(e.block.timestamp); - bool updated = timestamp48 != lastUpdate; - require !updated; - if(updated) { - require vaultCache.lastInterestAccumulatorUpdate == timestamp48; - - // totalBorrows - uint256 interestBorrows = newInterestBorrows(e.block.timestamp); - require vaultCache.totalBorrows == require_uint144(oldTotalBorrows + interestBorrows); - - // totalShares - mathint newTotalAssets = vaultCache.cash + vaultCache.totalBorrows; - // underapproximate interesteFee as 1 (1e4 in impl) - // feeAssets is a separate variable just for readability. - uint256 feeAssets = interestBorrows; - require feeAssets < require_uint256(newTotalAssets); - if (feeAssets > 0) { - require vaultCache.totalShares == require_uint112(oldTotalShares * newTotalAssets / (newTotalAssets - feeAssets)); - } else { - require vaultCache.totalShares == oldTotalShares; - } - - // accumulatedFees - mathint accFees = storage_accumulatedFees() + - vaultCache.totalShares - oldTotalShares; - require vaultCache.accumulatedFees == require_uint112(accFees); - - // interestAccumulator - require vaultCache.interestAccumulator >= storage_interestAccumulator(); - - } else { - require vaultCache.lastInterestAccumulatorUpdate == lastUpdate; - require vaultCache.totalBorrows == oldTotalBorrows; - require vaultCache.totalShares == oldTotalShares; - require vaultCache.accumulatedFees == storage_accumulatedFees(); - require vaultCache.interestAccumulator == storage_interestAccumulator(); - } - - // unmodified values - // require vaultCache.supplyCap == storage_supplyCap(); - // require vaultCache.borrowCap == storage_borrowCap(); - require vaultCache.hookedOps == storage_hookedOps(); - require vaultCache.configFlags == storage_configFlags(); - // require vaultCache.snapshotInitialized == storage_snapshotInitialized(); - - // either of these cause a vacuity failure ... - // require vaultCache.asset == erc20; - // require vaultCache.asset == asset(); - require vaultCache.oracle == oracleAddress; - require vaultCache.unitOfAccount == unitOfAccount; - - return updated; -} - function CVLLoadVault(env e) returns BaseHarness.VaultCache { BaseHarness.VaultCache vaultCache; uint48 lastUpdate = storage_lastInterestAccumulatorUpdate(); @@ -177,59 +118,5 @@ function CVLLoadVaultAssumeNoUpdate(env e) returns BaseHarness.VaultCache { require vaultCache.oracle == oracleAddress; require vaultCache.unitOfAccount == unitOfAccount; - return vaultCache; -} - - -function CVLloadVaultOld(env e) returns BaseHarness.VaultCache { - // for debugging performance - BaseHarness.VaultCache vaultCache; - - BaseHarness.Owed oldTotalBorrows = storage_totalBorrows(); - BaseHarness.Shares oldTotalShares = storage_totalShares(); - - // havoc lastInterestAccummulatorUpdate (not relevant to rule or this model) - require vaultCache.lastInterestAccumulatorUpdate >= - storage_lastInterestAccumulatorUpdate(); - require vaultCache.cash == storage_cash(); - // total borrows may increase due to interest - require vaultCache.totalBorrows <= max_uint112; // MAX_SANE_AMOUNT - uint256 interestBorrows = newInterestBorrows(e.block.timestamp); - require vaultCache.totalBorrows == require_uint144(oldTotalBorrows + interestBorrows); - - mathint newTotalAssets = vaultCache.cash + vaultCache.totalBorrows; - - // a more accurate but seemingly unnecessary model of feeAssests: - // uint16 feeScalar; - // uint256 feeAssets = require_uint256(feeScalar * (vaultCache.totalBorrows - oldTotalBorrows)); - - // underapproximate interesteFee as 1 (1e4 in impl) - // feeAssets is a separate variable just for readability. - uint256 feeAssets = interestBorrows; - if (require_uint256(newTotalAssets) > feeAssets) { - require vaultCache.totalShares == require_uint112(oldTotalShares * newTotalAssets / - (newTotalAssets - feeAssets)); - } else { - require vaultCache.totalShares == oldTotalShares; - } - - // havoc vaultCache.accumulatedFees - uint112 accumulatedFeesHavoc; - require vaultCache.accumulatedFees == accumulatedFeesHavoc; - // require vaultCache.accumulatedFees == require_uint112(storage_accumulatedFees() + vaultCache.totalShares - oldTotalShares); - - // havoc interestAccumulator (not relevant to rule or model) - require vaultCache.interestAccumulator >= storage_interestAccumulator(); - - require vaultCache.supplyCap == assert_uint256(storage_supplyCap()); - require vaultCache.borrowCap == assert_uint256(storage_borrowCap()); - require vaultCache.hookedOps == storage_hookedOps(); - require vaultCache.configFlags == storage_configFlags(); - require vaultCache.snapshotInitialized == storage_snapshotInitialized(); - - require vaultCache.asset == erc20; - require vaultCache.oracle == oracleAddress; - require vaultCache.unitOfAccount == unitOfAccount; - return vaultCache; } \ No newline at end of file diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 5505a390..c2659a7d 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -93,12 +93,16 @@ methods { // This is not in the scene for this config, so we just want it to be // an uninterpreted function rather than NONDET so that // we get the same value when this is called for different parts -ghost CVLgetCurrentOnBehalfOfAccountAddr(address) returns address { - axiom forall address x. CVLgetCurrentOnBehalfOfAccountAddr(x) != currentContract; -} +// ghost CVLgetCurrentOnBehalfOfAccountAddr(address) returns address { +// axiom forall address x. CVLgetCurrentOnBehalfOfAccountAddr(x) != currentContract; +// } +ghost address GhostOnBehalfOfAccount { + axiom GhostOnBehalfOfAccount != currentContract; + axiom GhostOnBehalfOfAccount != 0; +} ghost CVLgetCurrentOnBehalfOfAccountBool(address) returns bool; function CVLgetCurrentOnBehalfOfAccount(address addr) returns (address, bool) { - return (CVLgetCurrentOnBehalfOfAccountAddr(addr), + return (GhostOnBehalfOfAccount, CVLgetCurrentOnBehalfOfAccountBool(addr)); } persistent ghost CVLGetAccountOwner(address) returns address; @@ -233,8 +237,11 @@ invariant assetsMoreThanSupply(env e) { preserved { require e.msg.sender != currentContract; + require actualCaller(e) != currentContract; + require actualCallerCheckController(e) != currentContract; address any; - safeAssumptions(e, any , e.msg.sender); + safeAssumptions(e, any , actualCaller(e)); + safeAssumptions(e, any , actualCallerCheckController(e)); } } @@ -245,7 +252,8 @@ invariant noAssetsIfNoSupply(env e) { preserved { address any; - safeAssumptions(e, any, e.msg.sender); + safeAssumptions(e, any, actualCaller(e)); + safeAssumptions(e, any, actualCallerCheckController(e)); } } @@ -375,7 +383,6 @@ invariant vaultSolvency(env e) totalAssets(e) >= totalSupply(e) && userAssets(e, currentContract) >= totalAssets(e) { preserved { requireInvariant totalSupplyIsSumOfBalances(e); - require e.msg.sender == evc; require e.msg.sender != currentContract; require actualCaller(e) != currentContract; require actualCallerCheckController(e) != currentContract; @@ -413,7 +420,7 @@ filtered { // need to minimize these require actualCaller(e) == contributor; - require contributor == CVLgetCurrentOnBehalfOfAccountAddr(0); + require contributor == GhostOnBehalfOfAccount; require actualCallerCheckController(e) == contributor; address receiver; From d5849bba26b638deb2a903544be11a5918b3e84d Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 20 May 2024 13:18:29 +0100 Subject: [PATCH 083/152] Save status before update --- certora/harness/ERC4626Harness.sol | 3 +++ certora/specs/LoadVaultSummary.spec | 2 +- certora/specs/VaultERC4626.spec | 12 +++++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/certora/harness/ERC4626Harness.sol b/certora/harness/ERC4626Harness.sol index fd8f82fa..662881d2 100644 --- a/certora/harness/ERC4626Harness.sol +++ b/certora/harness/ERC4626Harness.sol @@ -37,6 +37,9 @@ contract ERC4626Harness is VaultModule, TokenModule, AbstractBaseHarness { } return vaultCache; } + function cache_cash() public view returns (Assets) { + return loadVault().cash; + } // VaultStorage Accessors: function storage_lastInterestAccumulatorUpdate() public view returns (uint48) { diff --git a/certora/specs/LoadVaultSummary.spec b/certora/specs/LoadVaultSummary.spec index 7e2333c1..bf3cef90 100644 --- a/certora/specs/LoadVaultSummary.spec +++ b/certora/specs/LoadVaultSummary.spec @@ -1,7 +1,7 @@ import "./Base.spec"; methods { // function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLLoadVault(e); - function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLLoadVaultAssumeNoUpdate(e); + // function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLLoadVaultAssumeNoUpdate(e); // function Cache.initVaultCache(BaseHarness.VaultCache memory vaultCache) internal returns (bool) with (env e) => CVLInitVaultCache(e, vaultCache); // function Cache.initVaultCache(BaseHarness.VaultCache memory vaultCache) internal returns (bool) with (env e) => CVLInitVaultCacheSimpleCopy(e, vaultCache); diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index c2659a7d..f7e6cd9b 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -87,9 +87,19 @@ methods { function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLTrySafeTransferFrom(e, from, to, value) expect (bool, bytes memory); function _.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal => NONDET; function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; + // Type Conversions + function _.toShares(uint256 amount) internal => CVLToShares(amount) expect (BaseHarness.Shares); + function _.toAssets(uint256 amount) internal => CVLToAssets(amount) expect (BaseHarness.Assets); } +function CVLToShares(uint256 amount) returns BaseHarness.Shares { + return require_uint112(amount); +} +function CVLToAssets(uint256 amount) returns BaseHarness.Assets { + return require_uint112(amount); +} + // This is not in the scene for this config, so we just want it to be // an uninterpreted function rather than NONDET so that // we get the same value when this is called for different parts @@ -380,7 +390,7 @@ rule dustFavorsTheHouse(uint assetsIn ) invariant vaultSolvency(env e) - totalAssets(e) >= totalSupply(e) && userAssets(e, currentContract) >= totalAssets(e) { + totalAssets(e) >= totalSupply(e) && userAssets(e, currentContract) >= require_uint256(cache_cash(e)) { preserved { requireInvariant totalSupplyIsSumOfBalances(e); require e.msg.sender != currentContract; From 99e9e37a688684d6e40a0752a1010258374efe34 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 29 May 2024 13:21:31 +0100 Subject: [PATCH 084/152] Save supplyLessThanUnderlyingAsset at least once in git history --- certora/conf/Cache.conf | 8 ++--- certora/conf/VaultERC4626.conf | 41 ++++++++++++---------- certora/harness/AbstractBaseHarness.sol | 46 +++++++++++++++++++++++++ certora/harness/ERC4626Harness.sol | 43 +++++------------------ certora/harness/EVaultHarness.sol | 5 ++- certora/specs/Base.spec | 6 +++- certora/specs/LoadVaultSummary.spec | 6 ++-- certora/specs/VaultERC4626.spec | 19 ++++++++-- 8 files changed, 109 insertions(+), 65 deletions(-) diff --git a/certora/conf/Cache.conf b/certora/conf/Cache.conf index c8088f5d..2b0f08d7 100644 --- a/certora/conf/Cache.conf +++ b/certora/conf/Cache.conf @@ -20,9 +20,9 @@ "parametric_contracts": ["CacheHarness"], "optimistic_loop": true, "loop_iter": "2", - // "solc_via_ir": true, - // "solc_optimize": "10000", + "solc_via_ir": true, + "solc_optimize": "10000", "rule_sanity": "basic", - // "function_finder_mode" : "relaxed", - // "finder_friendly_optimizer" : false, + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, } \ No newline at end of file diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index ab523de9..f216a3ba 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -22,18 +22,19 @@ "forge-std=lib/forge-std/src" ], // "rule": [ - // // "conversionWeakMonotonicity", - // // "assetsMoreThanSupply", - // // "contributingProducesShares", + // // "conversionWeakMonotonicity", + // // "assetsMoreThanSupply", + // // "contributingProducesShares", // // "depositMonotonicity", - // // "dustFavorsTheHouse", - // // "noAssetsIfNoSupply", - // // "noSupplyIfNoAssets", - // // "onlyContributionMethodsReduceAssets", - // // "reclaimingProducesAssets", - // // "redeemingAllValidity", - // // "totalsMonotonicity", - // // "vaultSolvency" + // // "dustFavorsTheHouse", + // // "noAssetsIfNoSupply", + // // "noSupplyIfNoAssets", + // // "supplyLessThanUnderlyingAsset", + // // "onlyContributionMethodsReduceAssets", + // // "reclaimingProducesAssets", + // // "redeemingAllValidity", + // // "totalsMonotonicity", + // // "vaultSolvency" // ], // "coverage_info" : "advanced", // "method": "redeem(uint256,address,address)", @@ -42,18 +43,19 @@ // "method" : "mint(uint256,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, - "prover_version" : "master", + "prover_version" : "eric/CERT-6172", // Performance tuning options below this line "solc_via_ir": true, "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", - // "prover_args": [ - // "-smt_nonLinearArithmetic true", - // "-adaptiveSolverConfig false", - // "-solvers [cvc5:nonlin{randomSeed=1},cvc5:nonlin{randomSeed=2},cvc5:nonlin{randomSeed=3},cvc5:nonlin{randomSeed=4},cvc5:nonlin{randomSeed=5},cvc5:nonlin{randomSeed=6},cvc5:nonlin{randomSeed=7},cvc5:nonlin{randomSeed=8},cvc5:nonlin{randomSeed=9},cvc5:nonlin{randomSeed=10}]", - // "-depth 0" - // ], + "prover_args": [ + "-maxConcurrentTransforms INLINED_HOOKS:8", + "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 " + ] + // "prover_args": [ + // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 -s [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" + // ] , // old config // "prover_args": [ // "-smt_nonLinearArithmetic true", @@ -62,5 +64,6 @@ // "-splitParallel true" // ], // "nondet_difficult_funcs": true, - // "smt_timeout": "7000" + // "smt_timeout": "7200" } + diff --git a/certora/harness/AbstractBaseHarness.sol b/certora/harness/AbstractBaseHarness.sol index d06d709c..7b6ec48f 100644 --- a/certora/harness/AbstractBaseHarness.sol +++ b/certora/harness/AbstractBaseHarness.sol @@ -29,6 +29,14 @@ abstract contract AbstractBaseHarness is Base { return vaultStorage.users[account].getBalanceAndBalanceForwarder(); } + function checkAccountMagicValue() public view returns (bytes4) { + return IEVCVault.checkAccountStatus.selector; + } + + function getUserInterestAccumulator(address account) public view returns (uint256) { + return vaultStorage.users[account].interestAccumulator; + } + //-------------------------------------------------------------------------- // Controllers @@ -82,5 +90,43 @@ abstract contract AbstractBaseHarness is Base { return isOperationDisabledExt(OP_SKIM); } + //-------------------------------------------------------------------------- + // VaultStorage Accessors: + //-------------------------------------------------------------------------- + function storage_lastInterestAccumulatorUpdate() public view returns (uint48) { + return vaultStorage.lastInterestAccumulatorUpdate; + } + function storage_cash() public view returns (Assets) { + return vaultStorage.cash; + } + function storage_supplyCap() public view returns (uint256) { + return vaultStorage.supplyCap.resolve(); + } + function storage_borrowCap() public view returns (uint256) { + return vaultStorage.borrowCap.resolve(); + } + // reentrancyLocked seems not direclty used in loadVault + function storage_hookedOps() public view returns (Flags) { + return vaultStorage.hookedOps; + } + function storage_snapshotInitialized() public view returns (bool) { + return vaultStorage.snapshotInitialized; + } + function storage_totalShares() public view returns (Shares) { + return vaultStorage.totalShares; + } + function storage_totalBorrows() public view returns (Owed) { + return vaultStorage.totalBorrows; + } + function storage_accumulatedFees() public view returns (Shares) { + return vaultStorage.accumulatedFees; + } + function storage_interestAccumulator() public view returns (uint256) { + return vaultStorage.interestAccumulator; + } + function storage_configFlags() public view returns (Flags) { + return vaultStorage.configFlags; + } + } \ No newline at end of file diff --git a/certora/harness/ERC4626Harness.sol b/certora/harness/ERC4626Harness.sol index 662881d2..cc559052 100644 --- a/certora/harness/ERC4626Harness.sol +++ b/certora/harness/ERC4626Harness.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.0; import "../../certora/harness/AbstractBaseHarness.sol"; import "../../src/EVault/modules/Vault.sol"; import "../../src/EVault/modules/Token.sol"; +// import "../../src/EVault/shared/types/Types.sol"; contract ERC4626Harness is VaultModule, TokenModule, AbstractBaseHarness { constructor(Integrations memory integrations) Base(integrations) {} @@ -37,43 +38,15 @@ contract ERC4626Harness is VaultModule, TokenModule, AbstractBaseHarness { } return vaultCache; } + + function toSharesExt(uint256 amount) external view returns (uint256) { + require(amount < MAX_SANE_AMOUNT, "Assets are really uint112"); + VaultCache memory vaultCache = loadVault(); + return Assets.wrap(uint112(amount)).toSharesDownUint256(vaultCache); + } + function cache_cash() public view returns (Assets) { return loadVault().cash; } - // VaultStorage Accessors: - function storage_lastInterestAccumulatorUpdate() public view returns (uint48) { - return vaultStorage.lastInterestAccumulatorUpdate; - } - function storage_cash() public view returns (Assets) { - return vaultStorage.cash; - } - function storage_supplyCap() public view returns (uint256) { - return vaultStorage.supplyCap.resolve(); - } - function storage_borrowCap() public view returns (uint256) { - return vaultStorage.borrowCap.resolve(); - } - // reentrancyLocked seems not direclty used in loadVault - function storage_hookedOps() public view returns (Flags) { - return vaultStorage.hookedOps; - } - function storage_snapshotInitialized() public view returns (bool) { - return vaultStorage.snapshotInitialized; - } - function storage_totalShares() public view returns (Shares) { - return vaultStorage.totalShares; - } - function storage_totalBorrows() public view returns (Owed) { - return vaultStorage.totalBorrows; - } - function storage_accumulatedFees() public view returns (Shares) { - return vaultStorage.accumulatedFees; - } - function storage_interestAccumulator() public view returns (uint256) { - return vaultStorage.interestAccumulator; - } - function storage_configFlags() public view returns (Flags) { - return vaultStorage.configFlags; - } } diff --git a/certora/harness/EVaultHarness.sol b/certora/harness/EVaultHarness.sol index 2d23d1ab..041185f2 100644 --- a/certora/harness/EVaultHarness.sol +++ b/certora/harness/EVaultHarness.sol @@ -13,6 +13,8 @@ import {BalanceForwarderModule} from "../../src/EVault/modules/BalanceForwarder. import {GovernanceModule} from "../../src/EVault/modules/Governance.sol"; import {RiskManagerModule} from "../../src/EVault/modules/RiskManager.sol"; +import "../../certora/harness/AbstractBaseHarness.sol"; + contract EVaultHarness is Base, InitializeModule, @@ -22,7 +24,8 @@ contract EVaultHarness is LiquidationModule, RiskManagerModule, BalanceForwarderModule, - GovernanceModule + GovernanceModule, + AbstractBaseHarness { constructor( diff --git a/certora/specs/Base.spec b/certora/specs/Base.spec index 8ec607a7..3da3687c 100644 --- a/certora/specs/Base.spec +++ b/certora/specs/Base.spec @@ -40,6 +40,9 @@ ghost CVLGetQuote(uint256, address, address) returns uint256 { // LTVConfig.mul) axiom forall uint256 x. forall address y. forall address z. CVLGetQuote(x, y, z) < 1725436586697640946858688965569256363112777243042596638790631055949823; + // monotonicity of amount + axiom forall uint256 x1. forall uint256 x2. forall address y. forall address z. + x1 > x2 => CVLGetQuote(x1, y, z) > CVLGetQuote(x2, y, z); } function CVLGetQuotes(uint256 amount, address base, address quote) returns (uint256, uint256) { @@ -56,7 +59,8 @@ ghost address unitOfAccount { init_state axiom unitOfAccount != 0; } function CVLProxyMetadata() returns (address, address, address) { - // Require addresses not zero? + require oracleAddress != 0; + require unitOfAccount != 0; return (erc20, oracleAddress, unitOfAccount); } persistent ghost address viewCallerGhost { diff --git a/certora/specs/LoadVaultSummary.spec b/certora/specs/LoadVaultSummary.spec index bf3cef90..336ec58e 100644 --- a/certora/specs/LoadVaultSummary.spec +++ b/certora/specs/LoadVaultSummary.spec @@ -1,7 +1,7 @@ import "./Base.spec"; methods { // function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLLoadVault(e); - // function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLLoadVaultAssumeNoUpdate(e); + function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLLoadVaultAssumeNoUpdate(e); // function Cache.initVaultCache(BaseHarness.VaultCache memory vaultCache) internal returns (bool) with (env e) => CVLInitVaultCache(e, vaultCache); // function Cache.initVaultCache(BaseHarness.VaultCache memory vaultCache) internal returns (bool) with (env e) => CVLInitVaultCacheSimpleCopy(e, vaultCache); @@ -84,7 +84,6 @@ function CVLLoadVault(env e) returns BaseHarness.VaultCache { require vaultCache.snapshotInitialized == storage_snapshotInitialized(); require vaultCache.asset == erc20; - require vaultCache.asset == asset(); require vaultCache.oracle == oracleAddress; require vaultCache.unitOfAccount == unitOfAccount; @@ -114,9 +113,10 @@ function CVLLoadVaultAssumeNoUpdate(env e) returns BaseHarness.VaultCache { require vaultCache.snapshotInitialized == storage_snapshotInitialized(); require vaultCache.asset == erc20; - require vaultCache.asset == asset(); require vaultCache.oracle == oracleAddress; require vaultCache.unitOfAccount == unitOfAccount; + require oracleAddress != 0; + require unitOfAccount != 0; return vaultCache; } \ No newline at end of file diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index f7e6cd9b..75a46817 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -90,6 +90,9 @@ methods { // Type Conversions function _.toShares(uint256 amount) internal => CVLToShares(amount) expect (BaseHarness.Shares); function _.toAssets(uint256 amount) internal => CVLToAssets(amount) expect (BaseHarness.Assets); + // This is NONDET to help avoid timeouts. It should be safe + // to NONDET since it is a private view function. + function _.resolve(Vault.AmountCap self) internal => NONDET; } @@ -255,13 +258,25 @@ invariant assetsMoreThanSupply(env e) } } +invariant supplyLessThanUnderlyingAsset(env e) + toSharesExt(e, userAssets(e, currentContract)) >= totalSupply(e) + { + preserved { + address any; + require userAssets(e, currentContract) < max_uint112; + safeAssumptions(e, any, actualCaller(e)); + safeAssumptions(e, any, actualCallerCheckController(e)); + } + } + invariant noAssetsIfNoSupply(env e) - ( userAssets(e, currentContract) == 0 => totalSupply(e) == 0 ) && + // ( userAssets(e, currentContract) == 0 => totalSupply(e) == 0 ) && ( totalAssets(e) == 0 => ( totalSupply(e) == 0 )) { preserved { - address any; + // requireInvariant supplyLessThanUnderlyingAsset(e); + address any; safeAssumptions(e, any, actualCaller(e)); safeAssumptions(e, any, actualCallerCheckController(e)); } From 7b89e4f36d22d47048d0d67a5c8335de8ab54000 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 29 May 2024 13:21:56 +0100 Subject: [PATCH 085/152] Remove supplyLessThanUnderlyingAsset (may not be true) --- certora/specs/VaultERC4626.spec | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 75a46817..c1338f0d 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -258,24 +258,11 @@ invariant assetsMoreThanSupply(env e) } } -invariant supplyLessThanUnderlyingAsset(env e) - toSharesExt(e, userAssets(e, currentContract)) >= totalSupply(e) - { - preserved { - address any; - require userAssets(e, currentContract) < max_uint112; - safeAssumptions(e, any, actualCaller(e)); - safeAssumptions(e, any, actualCallerCheckController(e)); - } - } - invariant noAssetsIfNoSupply(env e) - // ( userAssets(e, currentContract) == 0 => totalSupply(e) == 0 ) && ( totalAssets(e) == 0 => ( totalSupply(e) == 0 )) { preserved { - // requireInvariant supplyLessThanUnderlyingAsset(e); address any; safeAssumptions(e, any, actualCaller(e)); safeAssumptions(e, any, actualCallerCheckController(e)); From 0c0ea5bd7f25b871cb68b9cffdc5e6237fcad0e1 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 31 May 2024 13:19:47 +0100 Subject: [PATCH 086/152] Mainly health status invariant --- .../conf/VaultERC4626-UpdatingSummary.conf | 62 +++++++++++ .../BalanceForwarderHealthStatus.conf | 34 ++++++ .../healthStatus/BorrowingHealthStatus.conf | 36 +++++++ .../conf/healthStatus/EVaultHealthStatus.conf | 30 ++++++ .../healthStatus/GovernanceHealthStatus.conf | 33 ++++++ .../healthStatus/InitializeHealthStatus.conf | 33 ++++++ .../healthStatus/LiquidationHealthStatus.conf | 33 ++++++ .../conf/healthStatus/TokenHealthStatus.conf | 33 ++++++ .../conf/healthStatus/VaultHealthStatus.conf | 33 ++++++ certora/harness/AbstractBaseHarness.sol | 8 ++ certora/harness/EVCHarness.sol | 17 +++ .../BalanceForwarderHSHarness.sol | 14 +++ .../healthStatus/BorrowingHSHarness.sol | 14 +++ .../healthStatus/GovernanceHSHarness.sol | 14 +++ .../healthStatus/InitializeHSHarness.sol | 14 +++ .../healthStatus/LiquidationHSHarness.sol | 14 +++ .../harness/healthStatus/TokenHSHarness.sol | 14 +++ .../harness/healthStatus/VaultHSHarness.sol | 14 +++ certora/specs/Base.spec | 2 +- certora/specs/ERC4626-NoUpdate.spec | 0 certora/specs/ERC4626-UpdatingSummary.spec | 11 ++ certora/specs/HealthStatusInvariant.spec | 102 ++++++++++++++++++ certora/specs/VaultERC4626.spec | 2 - 23 files changed, 564 insertions(+), 3 deletions(-) create mode 100644 certora/conf/VaultERC4626-UpdatingSummary.conf create mode 100644 certora/conf/healthStatus/BalanceForwarderHealthStatus.conf create mode 100644 certora/conf/healthStatus/BorrowingHealthStatus.conf create mode 100644 certora/conf/healthStatus/EVaultHealthStatus.conf create mode 100644 certora/conf/healthStatus/GovernanceHealthStatus.conf create mode 100644 certora/conf/healthStatus/InitializeHealthStatus.conf create mode 100644 certora/conf/healthStatus/LiquidationHealthStatus.conf create mode 100644 certora/conf/healthStatus/TokenHealthStatus.conf create mode 100644 certora/conf/healthStatus/VaultHealthStatus.conf create mode 100644 certora/harness/EVCHarness.sol create mode 100644 certora/harness/healthStatus/BalanceForwarderHSHarness.sol create mode 100644 certora/harness/healthStatus/BorrowingHSHarness.sol create mode 100644 certora/harness/healthStatus/GovernanceHSHarness.sol create mode 100644 certora/harness/healthStatus/InitializeHSHarness.sol create mode 100644 certora/harness/healthStatus/LiquidationHSHarness.sol create mode 100644 certora/harness/healthStatus/TokenHSHarness.sol create mode 100644 certora/harness/healthStatus/VaultHSHarness.sol create mode 100644 certora/specs/ERC4626-NoUpdate.spec create mode 100644 certora/specs/ERC4626-UpdatingSummary.spec create mode 100644 certora/specs/HealthStatusInvariant.spec diff --git a/certora/conf/VaultERC4626-UpdatingSummary.conf b/certora/conf/VaultERC4626-UpdatingSummary.conf new file mode 100644 index 00000000..18cccfc5 --- /dev/null +++ b/certora/conf/VaultERC4626-UpdatingSummary.conf @@ -0,0 +1,62 @@ +{ + "files": [ + // TODO do we need EVC for this spec? + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + // "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + // "link" : [ + // // "ERC4626Harness:evc=EthereumVaultConnector", + // "ERC4626Harness:underlying_asset=DummyERC20A" + // ], + "verify": "ERC4626Harness:certora/specs/ERC4626-UpdatingSummary.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626 with Updating Summary", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule": [ + "reclaimingProducesAssets", + "onlyContributionMethodsReduceAssets", + "redeemingAllValidity", + "dustFavorsTheHouse", + "underlyingCannotChange", + "zeroDepositZeroShares", + "convertToCorrectness", + "conversionOfZero" + ], + // "coverage_info" : "advanced", + // "method": "redeem(uint256,address,address)", + // "method": "withdraw(uint256,address,address)", + // "method" : "deposit(uint256,address)", + // "method" : "mint(uint256,address)", + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + // "prover_args": [ + // "-smt_nonLinearArithmetic true", + // "-adaptiveSolverConfig false", + // "-solvers [cvc5:nonlin{randomSeed=1},cvc5:nonlin{randomSeed=2},cvc5:nonlin{randomSeed=3},cvc5:nonlin{randomSeed=4},cvc5:nonlin{randomSeed=5},cvc5:nonlin{randomSeed=6},cvc5:nonlin{randomSeed=7},cvc5:nonlin{randomSeed=8},cvc5:nonlin{randomSeed=9},cvc5:nonlin{randomSeed=10}]", + // "-depth 0" + // ], + // old config + // "prover_args": [ + // "-smt_nonLinearArithmetic true", + // "-adaptiveSolverConfig false", + // "-solvers [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]", + // "-splitParallel true" + // ], + // "nondet_difficult_funcs": true, + // "smt_timeout": "7000" +} diff --git a/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf b/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf new file mode 100644 index 00000000..22696192 --- /dev/null +++ b/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf @@ -0,0 +1,34 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "certora/helpers/DummyERC20A.sol", + "src/EVault/modules/BalanceForwarder.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/healthStatus/BalanceForwarderHSHarness.sol", + ], + "link": [ + "BalanceForwarderHSHarness:evc=EthereumVaultConnector", + ], + "verify": "BalanceForwarderHSHarness:certora/specs/HealthStatusInvariant.spec", + "solc": "solc8.23", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule" : [ + "accountsStayHealthy" + ], + // "method": "enableBalanceForwarder()", + "build_cache": true, + "prover_version": "master", + "server" : "production", + "parametric_contracts": ["BalanceForwarderHSHarness"], + // "prover_args": ["-smt_bitVectorTheory", "true"], + "optimistic_loop": true, + "loop_iter": "2", + // "solc_via_ir": true, + // "solc_optimize": "10000", + // "rule_sanity": "basic", + // "function_finder_mode" : "relaxed", + // "finder_friendly_optimizer" : false, +} \ No newline at end of file diff --git a/certora/conf/healthStatus/BorrowingHealthStatus.conf b/certora/conf/healthStatus/BorrowingHealthStatus.conf new file mode 100644 index 00000000..a19494ae --- /dev/null +++ b/certora/conf/healthStatus/BorrowingHealthStatus.conf @@ -0,0 +1,36 @@ +{ + "files": [ + // "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "certora/harness/EVCHarness.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "certora/helpers/DummyERC20A.sol", + "src/EVault/modules/Vault.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/healthStatus/BorrowingHSHarness.sol", + ], + "link": [ + "BorrowingHSHarness:evc=EVCHarness" + ], + "verify": "BorrowingHSHarness:certora/specs/HealthStatusInvariant.spec", + "solc": "solc8.23", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule" : [ + "accountsStayHealthy" + ], + // "method" : "borrow(uint256,address)", + "build_cache": true, + "prover_version": "master", + "server" : "production", + "parametric_contracts": ["BorrowingHSHarness"], + // "prover_args": ["-smt_bitVectorTheory", "true"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, +} \ No newline at end of file diff --git a/certora/conf/healthStatus/EVaultHealthStatus.conf b/certora/conf/healthStatus/EVaultHealthStatus.conf new file mode 100644 index 00000000..d68c4049 --- /dev/null +++ b/certora/conf/healthStatus/EVaultHealthStatus.conf @@ -0,0 +1,30 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "certora/helpers/DummyERC20A.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/EVaultHarness.sol", + ], + "link": [ + "EVaultHarness:evc=EthereumVaultConnector", + ], + "verify": "EVaultHarness:certora/specs/HealthStatusInvariant.spec", + "solc": "solc8.23", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "build_cache": true, + "prover_version": "master", + "server" : "production", + // "coverage_info" : "advanced", + "parametric_contracts": ["EVaultHarness"], + // "prover_args": ["-smt_bitVectorTheory", "true"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, +} \ No newline at end of file diff --git a/certora/conf/healthStatus/GovernanceHealthStatus.conf b/certora/conf/healthStatus/GovernanceHealthStatus.conf new file mode 100644 index 00000000..b432d384 --- /dev/null +++ b/certora/conf/healthStatus/GovernanceHealthStatus.conf @@ -0,0 +1,33 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "certora/helpers/DummyERC20A.sol", + "src/EVault/modules/Vault.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/healthStatus/GovernanceHSHarness.sol", + ], + "link": [ + "GovernanceHSHarness:evc=EthereumVaultConnector", + ], + "verify": "GovernanceHSHarness:certora/specs/HealthStatusInvariant.spec", + "solc": "solc8.23", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule" : [ + "accountsStayHealthy" + ], + "build_cache": true, + "prover_version": "master", + "server" : "production", + "parametric_contracts": ["GovernanceHSHarness"], + // "prover_args": ["-smt_bitVectorTheory", "true"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, +} \ No newline at end of file diff --git a/certora/conf/healthStatus/InitializeHealthStatus.conf b/certora/conf/healthStatus/InitializeHealthStatus.conf new file mode 100644 index 00000000..1e4db5e5 --- /dev/null +++ b/certora/conf/healthStatus/InitializeHealthStatus.conf @@ -0,0 +1,33 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "certora/helpers/DummyERC20A.sol", + "src/EVault/modules/Vault.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/healthStatus/InitializeHSHarness.sol", + ], + "link": [ + "InitializeHSHarness:evc=EthereumVaultConnector", + ], + "verify": "InitializeHSHarness:certora/specs/HealthStatusInvariant.spec", + "solc": "solc8.23", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule" : [ + "accountsStayHealthy" + ], + "build_cache": true, + "prover_version": "master", + "server" : "production", + "parametric_contracts": ["InitializeHSHarness"], + // "prover_args": ["-smt_bitVectorTheory", "true"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, +} \ No newline at end of file diff --git a/certora/conf/healthStatus/LiquidationHealthStatus.conf b/certora/conf/healthStatus/LiquidationHealthStatus.conf new file mode 100644 index 00000000..9f0678d0 --- /dev/null +++ b/certora/conf/healthStatus/LiquidationHealthStatus.conf @@ -0,0 +1,33 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "certora/helpers/DummyERC20A.sol", + "src/EVault/modules/Vault.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/healthStatus/LiquidationHSHarness.sol", + ], + "link": [ + "LiquidationHSHarness:evc=EthereumVaultConnector", + ], + "verify": "LiquidationHSHarness:certora/specs/HealthStatusInvariant.spec", + "solc": "solc8.23", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule" : [ + "accountsStayHealthy" + ], + "build_cache": true, + "prover_version": "master", + "server" : "production", + "parametric_contracts": ["LiquidationHSHarness"], + // "prover_args": ["-smt_bitVectorTheory", "true"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, +} \ No newline at end of file diff --git a/certora/conf/healthStatus/TokenHealthStatus.conf b/certora/conf/healthStatus/TokenHealthStatus.conf new file mode 100644 index 00000000..38934c09 --- /dev/null +++ b/certora/conf/healthStatus/TokenHealthStatus.conf @@ -0,0 +1,33 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "certora/helpers/DummyERC20A.sol", + "src/EVault/modules/Vault.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/healthStatus/TokenHSHarness.sol", + ], + "link": [ + "TokenHSHarness:evc=EthereumVaultConnector", + ], + "verify": "TokenHSHarness:certora/specs/HealthStatusInvariant.spec", + "solc": "solc8.23", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule" : [ + "accountsStayHealthy" + ], + "build_cache": true, + "prover_version": "master", + "server" : "production", + "parametric_contracts": ["TokenHSHarness"], + // "prover_args": ["-smt_bitVectorTheory", "true"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, +} \ No newline at end of file diff --git a/certora/conf/healthStatus/VaultHealthStatus.conf b/certora/conf/healthStatus/VaultHealthStatus.conf new file mode 100644 index 00000000..46de6cd0 --- /dev/null +++ b/certora/conf/healthStatus/VaultHealthStatus.conf @@ -0,0 +1,33 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "certora/helpers/DummyERC20A.sol", + "src/EVault/modules/Vault.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/healthStatus/VaultHSHarness.sol", + ], + "link": [ + "VaultHSHarness:evc=EthereumVaultConnector", + ], + "verify": "VaultHSHarness:certora/specs/HealthStatusInvariant.spec", + "solc": "solc8.23", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule" : [ + "accountsStayHealthy" + ], + "build_cache": true, + "prover_version": "master", + "server" : "production", + "parametric_contracts": ["VaultHSHarness"], + // "prover_args": ["-smt_bitVectorTheory", "true"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, +} \ No newline at end of file diff --git a/certora/harness/AbstractBaseHarness.sol b/certora/harness/AbstractBaseHarness.sol index 7b6ec48f..a75793c4 100644 --- a/certora/harness/AbstractBaseHarness.sol +++ b/certora/harness/AbstractBaseHarness.sol @@ -33,6 +33,14 @@ abstract contract AbstractBaseHarness is Base { return IEVCVault.checkAccountStatus.selector; } + function checkAccountMagicValueMemory() public view returns (bytes memory) { + return abi.encodeWithSelector(IEVCVault.checkAccountStatus.selector); + } + + function checkVaultMagicValueMemory() public view returns (bytes memory) { + return abi.encodeWithSelector(IEVCVault.checkVaultStatus.selector); + } + function getUserInterestAccumulator(address account) public view returns (uint256) { return vaultStorage.users[account].interestAccumulator; } diff --git a/certora/harness/EVCHarness.sol b/certora/harness/EVCHarness.sol new file mode 100644 index 00000000..ddddbb31 --- /dev/null +++ b/certora/harness/EVCHarness.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.19; + +import "../../lib/ethereum-vault-connector/src/EthereumVaultConnector.sol"; +contract EVCHarness is EthereumVaultConnector { + using ExecutionContext for EC; + using Set for SetStorage; + + // Trigger the (deferred) status checks in restoreExecutionContext + // explicitly. + function checkStatusAllExt() external { + checkStatusAll(SetType.Account); + // it's not needed + // checkStatusAll(SetType.Vault); + } +} \ No newline at end of file diff --git a/certora/harness/healthStatus/BalanceForwarderHSHarness.sol b/certora/harness/healthStatus/BalanceForwarderHSHarness.sol new file mode 100644 index 00000000..0dee46fc --- /dev/null +++ b/certora/harness/healthStatus/BalanceForwarderHSHarness.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import "../../../src/interfaces/IPriceOracle.sol"; +import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/RiskManager.sol"; +import "../../../src/EVault/modules/BalanceForwarder.sol"; + +// To prove the Health Status rule we need to include the RiskManager module +// which implemeants the status check +contract BalanceForwarderHSHarness is BalanceForwarderModule, RiskManagerModule, + AbstractBaseHarness { + constructor(Integrations memory integrations) Base(integrations) {} +} \ No newline at end of file diff --git a/certora/harness/healthStatus/BorrowingHSHarness.sol b/certora/harness/healthStatus/BorrowingHSHarness.sol new file mode 100644 index 00000000..a25a8705 --- /dev/null +++ b/certora/harness/healthStatus/BorrowingHSHarness.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import "../../../src/interfaces/IPriceOracle.sol"; +// import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/RiskManager.sol"; +import "../../../src/EVault/modules/Borrowing.sol"; + +// To prove the Health Status rule we need to include the RiskManager module +// which implemeants the status check +contract BorrowingHSHarness is BorrowingModule, RiskManagerModule, + AbstractBaseHarness { + constructor(Integrations memory integrations) Base(integrations) {} +} \ No newline at end of file diff --git a/certora/harness/healthStatus/GovernanceHSHarness.sol b/certora/harness/healthStatus/GovernanceHSHarness.sol new file mode 100644 index 00000000..4eeee085 --- /dev/null +++ b/certora/harness/healthStatus/GovernanceHSHarness.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import "../../../src/interfaces/IPriceOracle.sol"; +// import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/RiskManager.sol"; +import "../../../src/EVault/modules/Governance.sol"; + +// To prove the Health Status rule we need to include the RiskManager module +// which implemeants the status check +contract GovernanceHSHarness is GovernanceModule, RiskManagerModule, + AbstractBaseHarness { + constructor(Integrations memory integrations) Base(integrations) {} +} \ No newline at end of file diff --git a/certora/harness/healthStatus/InitializeHSHarness.sol b/certora/harness/healthStatus/InitializeHSHarness.sol new file mode 100644 index 00000000..68f94f45 --- /dev/null +++ b/certora/harness/healthStatus/InitializeHSHarness.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import "../../../src/interfaces/IPriceOracle.sol"; +// import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/RiskManager.sol"; +import "../../../src/EVault/modules/Initialize.sol"; + +// To prove the Health Status rule we need to include the RiskManager module +// which implemeants the status check +contract InitializeHSHarness is InitializeModule, RiskManagerModule, + AbstractBaseHarness { + constructor(Integrations memory integrations) Base(integrations) {} +} \ No newline at end of file diff --git a/certora/harness/healthStatus/LiquidationHSHarness.sol b/certora/harness/healthStatus/LiquidationHSHarness.sol new file mode 100644 index 00000000..797e9ebd --- /dev/null +++ b/certora/harness/healthStatus/LiquidationHSHarness.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import "../../../src/interfaces/IPriceOracle.sol"; +// import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/RiskManager.sol"; +import "../../../src/EVault/modules/Liquidation.sol"; + +// To prove the Health Status rule we need to include the RiskManager module +// which implemeants the status check +contract LiquidationHSHarness is LiquidationModule, RiskManagerModule, + AbstractBaseHarness { + constructor(Integrations memory integrations) Base(integrations) {} +} \ No newline at end of file diff --git a/certora/harness/healthStatus/TokenHSHarness.sol b/certora/harness/healthStatus/TokenHSHarness.sol new file mode 100644 index 00000000..31f6cd02 --- /dev/null +++ b/certora/harness/healthStatus/TokenHSHarness.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import "../../../src/interfaces/IPriceOracle.sol"; +// import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/RiskManager.sol"; +import "../../../src/EVault/modules/Token.sol"; + +// To prove the Health Status rule we need to include the RiskManager module +// which implemeants the status check +contract TokenHSHarness is TokenModule, RiskManagerModule, + AbstractBaseHarness { + constructor(Integrations memory integrations) Base(integrations) {} +} \ No newline at end of file diff --git a/certora/harness/healthStatus/VaultHSHarness.sol b/certora/harness/healthStatus/VaultHSHarness.sol new file mode 100644 index 00000000..143fa6c4 --- /dev/null +++ b/certora/harness/healthStatus/VaultHSHarness.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; +import "../../../src/interfaces/IPriceOracle.sol"; +// import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/RiskManager.sol"; +import "../../../src/EVault/modules/Vault.sol"; + +// To prove the Health Status rule we need to include the RiskManager module +// which implemeants the status check +contract VaultHSHarness is VaultModule, RiskManagerModule, + AbstractBaseHarness { + constructor(Integrations memory integrations) Base(integrations) {} +} \ No newline at end of file diff --git a/certora/specs/Base.spec b/certora/specs/Base.spec index 3da3687c..5b3fe993 100644 --- a/certora/specs/Base.spec +++ b/certora/specs/Base.spec @@ -1,5 +1,5 @@ using DummyERC20A as erc20; -using EthereumVaultConnector as evc; +using EVCHarness as evc; methods { // envfree diff --git a/certora/specs/ERC4626-NoUpdate.spec b/certora/specs/ERC4626-NoUpdate.spec new file mode 100644 index 00000000..e69de29b diff --git a/certora/specs/ERC4626-UpdatingSummary.spec b/certora/specs/ERC4626-UpdatingSummary.spec new file mode 100644 index 00000000..cd0d6899 --- /dev/null +++ b/certora/specs/ERC4626-UpdatingSummary.spec @@ -0,0 +1,11 @@ + +// This provides a configuration for the VaultERC4626 rules +// that will use the `CVLLoadVault` summary which includes a model +// for the update for fees. + +import "./LoadVaultSummary.spec"; +import "./VaultERC4626.spec"; + +methods { + function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLLoadVault(e); +} \ No newline at end of file diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec new file mode 100644 index 00000000..b8d24d81 --- /dev/null +++ b/certora/specs/HealthStatusInvariant.spec @@ -0,0 +1,102 @@ +import "Base.spec"; +import "LoadVaultSummary.spec"; +using DummyERC20A as ERC20a; + +methods { + function checkAccountMagicValue() external returns (bytes4) envfree; + // healthStatusCheck reverts unless this is true. We assume it's true + // approximate the real situation where these checks get triggered + // by the EVC before which this flag will be set. + function EVCHarness.areChecksInProgress() external returns bool => CVLAreChecksInProgress(); + // unresolved calls that havoc all contracts + function _.invokeHookTarget(address caller) internal => NONDET; + function _.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal => NONDET; + function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; + function _.requireVaultStatusCheck() external => DISPATCHER(true); + // function _.requireAccountAndVaultStatusCheck(address account) external => NONDET; + function _.requireAccountAndVaultStatusCheck(address) external => DISPATCHER(true); + function _.emitTransfer(address from, address to, uint256 value) external => NONDET; + function EVCHarness.disableController(address account) external => NONDET; + // function _.computeInterestRate(BaseHarness.VaultCache memory vaultCache) internal => NONDET; + function _.computeInterestRate(address vault, uint256 cash, uint256 borrows) external => NONDET; + function _.onFlashLoan(bytes data) external => NONDET; + function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLTrySafeTransferFrom(e, from, to, value) expect (bool, bytes memory); + // We can't handle the low-level call in + // EthereumVaultConnector.checkAccountStatusInternal + // and so reroute it to RiskManager's status check with this summary. + function EthereumVaultConnector.checkAccountStatusInternal(address account) internal returns (bool, bytes memory) with (env e) => + CVLCheckAccountStatusInternal(e, account); + function EthereumVaultConnector.checkVaultStatusInternal(address vault) internal returns (bool, bytes memory) with(env e) => + CVLCheckVaultStatusInternal(e); + // NONDET checkVaultStatus because I think it is not related to enforcing + // this and it is expensive for the tool. + function currentContract.checkVaultStatus() external returns (bytes4) => NONDET; +} + +// We summarize EthereumVaultConnector.checkAccountStatusInternal +// because we need +function CVLCheckAccountStatusInternalBool(env e, address account) returns bool { + address[] collaterals = evc.getCollaterals(e, account); + checkAccountStatus@withrevert(e, account, collaterals); + return !lastReverted; +} + +function CVLCheckAccountStatusInternal(env e, address account) returns (bool, bytes) { + return (CVLCheckAccountStatusInternalBool(e, account), + checkAccountMagicValueMemory(e)); +} + +function CVLCheckVaultStatusInternalBool(env e) returns bool { + checkVaultStatus@withrevert(e); + return !lastReverted; +} + +function CVLCheckVaultStatusInternal(env e) returns (bool, bytes) { + return (CVLCheckVaultStatusInternalBool(e), + checkVaultMagicValueMemory(e)); +} + +function CVLAreChecksInProgress() returns bool { + return true; +} + +// Summarize trySafeTransferFrom as DummyERC20 transferFrom +function CVLTrySafeTransferFrom(env e, address from, address to, uint256 value) returns (bool, bytes) { + bytes ret; // Ideally bytes("") if there is a way to do this + return (ERC20a.transferFrom(e, from, to, value), ret); +} + + +// Assuming the prices stay the same, a healthy account can never become +// unhealthy. Here, our assumption that the prices do not change is implicit +// in the fact that the summary for GetQuote is an uninterpreted function -- +// the prover will model it as a function so it will always return the same +// value when given the same arguments. +rule accountsStayHealthy (method f) { + env e; + calldataarg args; + address account; + address[] collaterals = evc.getCollaterals(e, account); + require collaterals.length == 2; // loop bound + require oracleAddress != 0; + + // Otherwise this can cause an unintersting divide by zero in OwedLib.getCurrentOwed (on the mulDiv) + require getUserInterestAccumulator(e, account) > 0; + require storage_interestAccumulator(e) == getUserInterestAccumulator(e, account); + // otherwise this can cause an uninteresting overflow in mulDiv + require storage_interestAccumulator(e) < max_uint112; + + checkAccountStatus@withrevert(e, account, collaterals); + bool healthyBefore = !lastReverted; + f(e, args); + // The only way to call a vault funciton is through EVC's call, batch, + // or permit. During all of these status checks are deferred and at the end + // these call restoreExecutionContext which triggers the deferred checks. + evc.checkStatusAllExt(e); + checkAccountStatus@withrevert(e, account, collaterals); + bool healthyAfter= !lastReverted; + assert healthyBefore => healthyAfter; +} + +// - prove separately that every call to vault is from evc (except maybe view) +// - prove on EVC: at the end of call/batch/permit we really do always call checkStatusAll diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index c1338f0d..4fd9727b 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -510,8 +510,6 @@ filtered { //////////////////////////////////////////////////////////////////////////////// definition noSupplyIfNoAssetsDef(env e) returns bool = - // for this ERC4626 implementation balanceOf(Vault) is not the same as total assets - // ( userAssets(e, currentContract) == 0 => totalSupply(e) == 0 ) && ( totalAssets(e) == 0 => ( totalSupply(e) == 0 )); // definition noSupplyIfNoAssetsStrongerDef() returns bool = // fails for ERC4626BalanceOfHarness as explained in the readme From 7d18fa099bc5277ddc8e89bf49b70ec8f64d3e85 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 3 Jun 2024 12:18:21 +0100 Subject: [PATCH 087/152] Mainly health status check changes --- certora/conf/VaultERC4626.conf | 21 +++++++----- .../healthStatus/BorrowingHealthStatus.conf | 5 ++- .../healthStatus/LiquidationHealthStatus.conf | 7 ++-- .../conf/healthStatus/VaultHealthStatus.conf | 7 ++-- certora/harness/AbstractBaseHarness.sol | 23 ++++++++++++- .../healthStatus/BorrowingHSHarness.sol | 21 ++++++++++++ certora/specs/HealthStatusInvariant.spec | 34 +++++++++++++++++-- src/EVault/modules/Vault.sol | 3 +- src/EVault/shared/types/Shares.sol | 8 +++++ 9 files changed, 108 insertions(+), 21 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index f216a3ba..e0043900 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -6,6 +6,7 @@ "src/EVault/modules/Vault.sol", "certora/helpers/DummyERC20A.sol", "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", "certora/harness/BaseHarness.sol", "certora/harness/ERC4626Harness.sol", ], @@ -21,9 +22,9 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - // "rule": [ + "rule": [ // // "conversionWeakMonotonicity", - // // "assetsMoreThanSupply", + "assetsMoreThanSupply", // // "contributingProducesShares", // // "depositMonotonicity", // // "dustFavorsTheHouse", @@ -35,24 +36,28 @@ // // "redeemingAllValidity", // // "totalsMonotonicity", // // "vaultSolvency" - // ], + ], // "coverage_info" : "advanced", - // "method": "redeem(uint256,address,address)", + "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", // "method" : "deposit(uint256,address)", // "method" : "mint(uint256,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, - "prover_version" : "eric/CERT-6172", + "prover_version" : "master", + // "prover_version" : "eric/CERT-6172", // Performance tuning options below this line "solc_via_ir": true, "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", "prover_args": [ - "-maxConcurrentTransforms INLINED_HOOKS:8", - "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 " - ] + "-adaptiveSolverConfig false -smt_nonLinearArithmetic false -depth 0" + ], + // "prover_args": [ + // "-maxConcurrentTransforms INLINED_HOOKS:8", + // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 " + // ] // "prover_args": [ // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 -s [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" // ] , diff --git a/certora/conf/healthStatus/BorrowingHealthStatus.conf b/certora/conf/healthStatus/BorrowingHealthStatus.conf index a19494ae..f1b9346a 100644 --- a/certora/conf/healthStatus/BorrowingHealthStatus.conf +++ b/certora/conf/healthStatus/BorrowingHealthStatus.conf @@ -1,6 +1,5 @@ { "files": [ - // "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "certora/harness/EVCHarness.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", @@ -18,9 +17,9 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy" + "accountsStayHealthy_strategy" ], - // "method" : "borrow(uint256,address)", + "method" : "borrow(uint256,address)", "build_cache": true, "prover_version": "master", "server" : "production", diff --git a/certora/conf/healthStatus/LiquidationHealthStatus.conf b/certora/conf/healthStatus/LiquidationHealthStatus.conf index 9f0678d0..a27b9d70 100644 --- a/certora/conf/healthStatus/LiquidationHealthStatus.conf +++ b/certora/conf/healthStatus/LiquidationHealthStatus.conf @@ -1,13 +1,14 @@ { "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "certora/harness/EVCHarness.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", "certora/harness/healthStatus/LiquidationHSHarness.sol", ], "link": [ - "LiquidationHSHarness:evc=EthereumVaultConnector", + "LiquidationHSHarness:evc=EVCHarness", ], "verify": "LiquidationHSHarness:certora/specs/HealthStatusInvariant.spec", "solc": "solc8.23", @@ -16,7 +17,7 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy" + "accountsStayHealthy_strategy" ], "build_cache": true, "prover_version": "master", diff --git a/certora/conf/healthStatus/VaultHealthStatus.conf b/certora/conf/healthStatus/VaultHealthStatus.conf index 46de6cd0..e0ade6f3 100644 --- a/certora/conf/healthStatus/VaultHealthStatus.conf +++ b/certora/conf/healthStatus/VaultHealthStatus.conf @@ -1,13 +1,14 @@ { "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "certora/harness/EVCHarness.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", "certora/harness/healthStatus/VaultHSHarness.sol", ], "link": [ - "VaultHSHarness:evc=EthereumVaultConnector", + "VaultHSHarness:evc=EVCHarness", ], "verify": "VaultHSHarness:certora/specs/HealthStatusInvariant.spec", "solc": "solc8.23", @@ -16,7 +17,7 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy" + "accountsStayHealthy_strategy" ], "build_cache": true, "prover_version": "master", diff --git a/certora/harness/AbstractBaseHarness.sol b/certora/harness/AbstractBaseHarness.sol index a75793c4..5dfdf2c6 100644 --- a/certora/harness/AbstractBaseHarness.sol +++ b/certora/harness/AbstractBaseHarness.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.0; import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; import "../../src/EVault/shared/Base.sol"; +// Needed for checkLiquidityReturning: +import {LiquidityUtils} from "../../src/EVault/shared/LiquidityUtils.sol"; // This exists so that Base.LTVConfig and other type declarations // are available in CVL and can be used across specs for different modules. @@ -11,7 +13,7 @@ import "../../src/EVault/shared/Base.sol"; // so that we can refer to Base.LTVConfig as a type in shared CVL functions // while also making function definitions sharable among harnesses via // AbstractBase. AbstractBaseHarness includes the shared function definitions. -abstract contract AbstractBaseHarness is Base { +abstract contract AbstractBaseHarness is Base, LiquidityUtils { function getLTVConfig(address collateral) external view returns (LTVConfig memory) { return vaultStorage.ltvLookup[collateral]; @@ -45,6 +47,25 @@ abstract contract AbstractBaseHarness is Base { return vaultStorage.users[account].interestAccumulator; } + // This mirrors LiquidityUtils.checkLiquidity except that it returns + // a bool rather than reverting. + function checkLiquidityReturning(address account, address[] memory collaterals) public returns (bool) { + VaultCache memory vaultCache = loadVault(); + + Owed owed = vaultStorage.users[account].getOwed(); + if (owed.isZero()) return true; + + uint256 liabilityValue = getLiabilityValue(vaultCache, account, owed, false); + + uint256 collateralValue; + for (uint256 i; i < collaterals.length; ++i) { + collateralValue += getCollateralValue(vaultCache, account, collaterals[i], false); + if (collateralValue > liabilityValue) return true; + } + + return false; + } + //-------------------------------------------------------------------------- // Controllers diff --git a/certora/harness/healthStatus/BorrowingHSHarness.sol b/certora/harness/healthStatus/BorrowingHSHarness.sol index a25a8705..10f01e50 100644 --- a/certora/harness/healthStatus/BorrowingHSHarness.sol +++ b/certora/harness/healthStatus/BorrowingHSHarness.sol @@ -11,4 +11,25 @@ import "../../../src/EVault/modules/Borrowing.sol"; contract BorrowingHSHarness is BorrowingModule, RiskManagerModule, AbstractBaseHarness { constructor(Integrations memory integrations) Base(integrations) {} + + // This mirrors LiquidityUtils.checkLiquidity except that it returns + // a bool rather than reverting. + // Try importing LiquidityUtils into BaseHarness or AbstractBaseHarness + // and moving this function there. + // function checkLiquidityReturning(address account, address[] memory collaterals) public returns (bool) { + // VaultCache memory vaultCache = loadVault(); + // + // Owed owed = vaultStorage.users[account].getOwed(); + // if (owed.isZero()) return true; + + // uint256 liabilityValue = getLiabilityValue(vaultCache, account, owed, false); + + // uint256 collateralValue; + // for (uint256 i; i < collaterals.length; ++i) { + // collateralValue += getCollateralValue(vaultCache, account, collaterals[i], false); + // if (collateralValue > liabilityValue) return true; + // } + + // return false; + // } } \ No newline at end of file diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index b8d24d81..85a91dab 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -30,7 +30,7 @@ methods { CVLCheckVaultStatusInternal(e); // NONDET checkVaultStatus because I think it is not related to enforcing // this and it is expensive for the tool. - function currentContract.checkVaultStatus() external returns (bytes4) => NONDET; + // function currentContract.checkVaultStatus() external returns (bytes4) => NONDET; } // We summarize EthereumVaultConnector.checkAccountStatusInternal @@ -98,5 +98,35 @@ rule accountsStayHealthy (method f) { assert healthyBefore => healthyAfter; } +rule accountsStayHealthy_strategy (method f) { + env e; + calldataarg args; + address account; + address[] collaterals = evc.getCollaterals(e, account); + require collaterals.length == 2; // loop bound + require oracleAddress != 0; + // Vault cannot be a user of itself + require account != currentContract; + // Vault should not be used as a collateral + require collaterals[0] != currentContract; + require collaterals[1] != currentContract; + // not sure the following 4 are really needed + require account != erc20; + require account != oracleAddress; + require account != evc; + require account != unitOfAccount; + + bool healthyBefore = checkLiquidityReturning(e, account, collaterals); + f(e, args); + // The only way to call a vault funciton is through EVC's call, batch, + // or permit. During all of these status checks are deferred and at the end + // these call restoreExecutionContext which triggers the deferred checks. + evc.checkStatusAllExt(e); + bool healthyAfter = checkLiquidityReturning(e, account, collaterals); + assert healthyBefore => healthyAfter; +} + // - prove separately that every call to vault is from evc (except maybe view) -// - prove on EVC: at the end of call/batch/permit we really do always call checkStatusAll +// - prove on EVC: at the end of call/batch/permit we really do always call +// checkStatusAll --> After looking at the tickets I think we did not prove +// this already. diff --git a/src/EVault/modules/Vault.sol b/src/EVault/modules/Vault.sol index 8cd3ea8a..38cbfda2 100644 --- a/src/EVault/modules/Vault.sol +++ b/src/EVault/modules/Vault.sol @@ -170,7 +170,8 @@ abstract contract VaultModule is IVault, AssetTransfers, BalanceUtils { Shares shares = amount == type(uint256).max ? vaultStorage.users[owner].getBalance() : amount.toShares(); if (shares.isZero()) return 0; - Assets assets = shares.toAssetsDown(vaultCache); + // Changed by Certora + Assets assets = shares.toAssetsDownSubShares(vaultCache); if (assets.isZero()) revert E_ZeroAssets(); finalizeWithdraw(vaultCache, assets, shares, account, receiver, owner); diff --git a/src/EVault/shared/types/Shares.sol b/src/EVault/shared/types/Shares.sol index 93731097..fd05afad 100644 --- a/src/EVault/shared/types/Shares.sol +++ b/src/EVault/shared/types/Shares.sol @@ -26,6 +26,14 @@ library SharesLib { } } + // Introduced by Certora + function toAssetsDownSubShares(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) { + (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache); + return TypesLib.toAssets(amount.toUint() * totalAssets / + (totalShares - amount.toUint())); + } + + function toAssetsUp(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) { (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache); unchecked { From cc9e6957dce61ab17be54ac7d64c480450b843fa Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 3 Jun 2024 13:25:23 +0100 Subject: [PATCH 088/152] healthStatus config updates --- .../BalanceForwarderHealthStatus.conf | 25 +++++++++++-------- .../healthStatus/BorrowingHealthStatus.conf | 9 +++++-- .../healthStatus/GovernanceHealthStatus.conf | 13 +++++++--- .../healthStatus/InitializeHealthStatus.conf | 14 ++++++++--- .../healthStatus/LiquidationHealthStatus.conf | 7 +++++- .../conf/healthStatus/TokenHealthStatus.conf | 14 ++++++++--- .../conf/healthStatus/VaultHealthStatus.conf | 9 +++++-- certora/specs/HealthStatusInvariant.spec | 11 ++++++++ 8 files changed, 76 insertions(+), 26 deletions(-) diff --git a/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf b/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf index 22696192..63376449 100644 --- a/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf +++ b/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf @@ -1,13 +1,14 @@ { "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "certora/harness/EVCHarness.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", "src/EVault/modules/BalanceForwarder.sol", "certora/harness/BaseHarness.sol", "certora/harness/healthStatus/BalanceForwarderHSHarness.sol", ], "link": [ - "BalanceForwarderHSHarness:evc=EthereumVaultConnector", + "BalanceForwarderHSHarness:evc=EVCHarness", ], "verify": "BalanceForwarderHSHarness:certora/specs/HealthStatusInvariant.spec", "solc": "solc8.23", @@ -16,19 +17,23 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy" + "accountsStayHealthy_strategy" ], - // "method": "enableBalanceForwarder()", "build_cache": true, "prover_version": "master", "server" : "production", "parametric_contracts": ["BalanceForwarderHSHarness"], - // "prover_args": ["-smt_bitVectorTheory", "true"], "optimistic_loop": true, "loop_iter": "2", - // "solc_via_ir": true, - // "solc_optimize": "10000", - // "rule_sanity": "basic", - // "function_finder_mode" : "relaxed", - // "finder_friendly_optimizer" : false, + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, + "prover_args": [ + "-splitParallel true", + "-deleteSMTFile false", + "-smt_easy_LIA true" + ], + "smt_timeout": "4800", } \ No newline at end of file diff --git a/certora/conf/healthStatus/BorrowingHealthStatus.conf b/certora/conf/healthStatus/BorrowingHealthStatus.conf index f1b9346a..4ecb9516 100644 --- a/certora/conf/healthStatus/BorrowingHealthStatus.conf +++ b/certora/conf/healthStatus/BorrowingHealthStatus.conf @@ -19,12 +19,11 @@ "rule" : [ "accountsStayHealthy_strategy" ], - "method" : "borrow(uint256,address)", + // "method" : "borrow(uint256,address)", "build_cache": true, "prover_version": "master", "server" : "production", "parametric_contracts": ["BorrowingHSHarness"], - // "prover_args": ["-smt_bitVectorTheory", "true"], "optimistic_loop": true, "loop_iter": "2", "solc_via_ir": true, @@ -32,4 +31,10 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, + "prover_args": [ + "-splitParallel true", + "-deleteSMTFile false", + "-smt_easy_LIA true" + ], + "smt_timeout": "4800", } \ No newline at end of file diff --git a/certora/conf/healthStatus/GovernanceHealthStatus.conf b/certora/conf/healthStatus/GovernanceHealthStatus.conf index b432d384..6d0d020c 100644 --- a/certora/conf/healthStatus/GovernanceHealthStatus.conf +++ b/certora/conf/healthStatus/GovernanceHealthStatus.conf @@ -1,13 +1,14 @@ { "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "certora/harness/EVCHarness.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", "certora/harness/healthStatus/GovernanceHSHarness.sol", ], "link": [ - "GovernanceHSHarness:evc=EthereumVaultConnector", + "GovernanceHSHarness:evc=EVCHarness", ], "verify": "GovernanceHSHarness:certora/specs/HealthStatusInvariant.spec", "solc": "solc8.23", @@ -16,7 +17,7 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy" + "accountsStayHealthy_strategy" ], "build_cache": true, "prover_version": "master", @@ -30,4 +31,10 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, + "prover_args": [ + "-splitParallel true", + "-deleteSMTFile false", + "-smt_easy_LIA true" + ], + "smt_timeout": "4800", } \ No newline at end of file diff --git a/certora/conf/healthStatus/InitializeHealthStatus.conf b/certora/conf/healthStatus/InitializeHealthStatus.conf index 1e4db5e5..410bee1c 100644 --- a/certora/conf/healthStatus/InitializeHealthStatus.conf +++ b/certora/conf/healthStatus/InitializeHealthStatus.conf @@ -1,13 +1,14 @@ { "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "certora/harness/EVCHarness.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", "certora/harness/healthStatus/InitializeHSHarness.sol", ], "link": [ - "InitializeHSHarness:evc=EthereumVaultConnector", + "InitializeHSHarness:evc=EVCHarness", ], "verify": "InitializeHSHarness:certora/specs/HealthStatusInvariant.spec", "solc": "solc8.23", @@ -16,13 +17,12 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy" + "accountsStayHealthy_strategy" ], "build_cache": true, "prover_version": "master", "server" : "production", "parametric_contracts": ["InitializeHSHarness"], - // "prover_args": ["-smt_bitVectorTheory", "true"], "optimistic_loop": true, "loop_iter": "2", "solc_via_ir": true, @@ -30,4 +30,10 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, + "prover_args": [ + "-splitParallel true", + "-deleteSMTFile false", + "-smt_easy_LIA true" + ], + "smt_timeout": "4800", } \ No newline at end of file diff --git a/certora/conf/healthStatus/LiquidationHealthStatus.conf b/certora/conf/healthStatus/LiquidationHealthStatus.conf index a27b9d70..7dae58ef 100644 --- a/certora/conf/healthStatus/LiquidationHealthStatus.conf +++ b/certora/conf/healthStatus/LiquidationHealthStatus.conf @@ -23,7 +23,6 @@ "prover_version": "master", "server" : "production", "parametric_contracts": ["LiquidationHSHarness"], - // "prover_args": ["-smt_bitVectorTheory", "true"], "optimistic_loop": true, "loop_iter": "2", "solc_via_ir": true, @@ -31,4 +30,10 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, + "prover_args": [ + "-splitParallel true", + "-deleteSMTFile false", + "-smt_easy_LIA true" + ], + "smt_timeout": "4800", } \ No newline at end of file diff --git a/certora/conf/healthStatus/TokenHealthStatus.conf b/certora/conf/healthStatus/TokenHealthStatus.conf index 38934c09..0bc5e1e2 100644 --- a/certora/conf/healthStatus/TokenHealthStatus.conf +++ b/certora/conf/healthStatus/TokenHealthStatus.conf @@ -1,13 +1,14 @@ { "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "certora/harness/EVCHarness.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", "certora/harness/healthStatus/TokenHSHarness.sol", ], "link": [ - "TokenHSHarness:evc=EthereumVaultConnector", + "TokenHSHarness:evc=EVCHarness", ], "verify": "TokenHSHarness:certora/specs/HealthStatusInvariant.spec", "solc": "solc8.23", @@ -16,13 +17,12 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy" + "accountsStayHealthy_strategy" ], "build_cache": true, "prover_version": "master", "server" : "production", "parametric_contracts": ["TokenHSHarness"], - // "prover_args": ["-smt_bitVectorTheory", "true"], "optimistic_loop": true, "loop_iter": "2", "solc_via_ir": true, @@ -30,4 +30,10 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, + "prover_args": [ + "-splitParallel true", + "-deleteSMTFile false", + "-smt_easy_LIA true" + ], + "smt_timeout": "4800", } \ No newline at end of file diff --git a/certora/conf/healthStatus/VaultHealthStatus.conf b/certora/conf/healthStatus/VaultHealthStatus.conf index e0ade6f3..703a41c4 100644 --- a/certora/conf/healthStatus/VaultHealthStatus.conf +++ b/certora/conf/healthStatus/VaultHealthStatus.conf @@ -17,13 +17,12 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy_strategy" + "accountsStayHealthy" ], "build_cache": true, "prover_version": "master", "server" : "production", "parametric_contracts": ["VaultHSHarness"], - // "prover_args": ["-smt_bitVectorTheory", "true"], "optimistic_loop": true, "loop_iter": "2", "solc_via_ir": true, @@ -31,4 +30,10 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, + "prover_args": [ + "-splitParallel true", + "-deleteSMTFile false", + "-smt_easy_LIA true" + ], + "smt_timeout": "4800", } \ No newline at end of file diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index 85a91dab..7853c840 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -80,6 +80,17 @@ rule accountsStayHealthy (method f) { require collaterals.length == 2; // loop bound require oracleAddress != 0; + // Vault cannot be a user of itself + require account != currentContract; + // Vault should not be used as a collateral + require collaterals[0] != currentContract; + require collaterals[1] != currentContract; + // not sure the following 4 are really needed + require account != erc20; + require account != oracleAddress; + require account != evc; + require account != unitOfAccount; + // Otherwise this can cause an unintersting divide by zero in OwedLib.getCurrentOwed (on the mulDiv) require getUserInterestAccumulator(e, account) > 0; require storage_interestAccumulator(e) == getUserInterestAccumulator(e, account); From 397c6923dc7d3ab44cd61549cb65e97ad17cbea0 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 5 Jun 2024 14:18:10 +0100 Subject: [PATCH 089/152] Mainly Health status check, last few ERC4626 rules --- certora/conf/VaultERC4626.conf | 2 +- .../healthStatus/BorrowingHealthStatus.conf | 8 +-- .../healthStatus/GovernanceHealthStatus.conf | 1 + .../healthStatus/InitializeHealthStatus.conf | 2 +- .../healthStatus/LiquidationHealthStatus.conf | 9 ++- .../conf/healthStatus/VaultHealthStatus.conf | 2 +- certora/harness/AbstractBaseHarness.sol | 3 + .../healthStatus/BorrowingHSHarness.sol | 21 ------ certora/specs/HealthStatusInvariant.spec | 64 +++++++++++++++---- certora/specs/VaultERC4626.spec | 6 +- src/EVault/modules/Vault.sol | 2 +- src/EVault/shared/types/Shares.sol | 10 +-- 12 files changed, 77 insertions(+), 53 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index e0043900..c0e5cd7f 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -52,7 +52,7 @@ "optimistic_loop": true, "loop_iter": "2", "prover_args": [ - "-adaptiveSolverConfig false -smt_nonLinearArithmetic false -depth 0" + "-adaptiveSolverConfig false -depth 0 -smt_nonLinearArithmetic true" ], // "prover_args": [ // "-maxConcurrentTransforms INLINED_HOOKS:8", diff --git a/certora/conf/healthStatus/BorrowingHealthStatus.conf b/certora/conf/healthStatus/BorrowingHealthStatus.conf index 4ecb9516..f69b0820 100644 --- a/certora/conf/healthStatus/BorrowingHealthStatus.conf +++ b/certora/conf/healthStatus/BorrowingHealthStatus.conf @@ -31,10 +31,8 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, - "prover_args": [ - "-splitParallel true", - "-deleteSMTFile false", - "-smt_easy_LIA true" + "prover_args": [ + " -smt_easy_LIA true" ], - "smt_timeout": "4800", + "smt_timeout": "7200", } \ No newline at end of file diff --git a/certora/conf/healthStatus/GovernanceHealthStatus.conf b/certora/conf/healthStatus/GovernanceHealthStatus.conf index 6d0d020c..dd1f972c 100644 --- a/certora/conf/healthStatus/GovernanceHealthStatus.conf +++ b/certora/conf/healthStatus/GovernanceHealthStatus.conf @@ -17,6 +17,7 @@ "forge-std=lib/forge-std/src" ], "rule" : [ + "accountsStayHealthy", "accountsStayHealthy_strategy" ], "build_cache": true, diff --git a/certora/conf/healthStatus/InitializeHealthStatus.conf b/certora/conf/healthStatus/InitializeHealthStatus.conf index 410bee1c..49d1cad5 100644 --- a/certora/conf/healthStatus/InitializeHealthStatus.conf +++ b/certora/conf/healthStatus/InitializeHealthStatus.conf @@ -17,7 +17,7 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy_strategy" + "accountsStayHealthy" ], "build_cache": true, "prover_version": "master", diff --git a/certora/conf/healthStatus/LiquidationHealthStatus.conf b/certora/conf/healthStatus/LiquidationHealthStatus.conf index 7dae58ef..45256e96 100644 --- a/certora/conf/healthStatus/LiquidationHealthStatus.conf +++ b/certora/conf/healthStatus/LiquidationHealthStatus.conf @@ -19,6 +19,7 @@ "rule" : [ "accountsStayHealthy_strategy" ], + "method":"liquidate(address,address,uint256,uint256)", "build_cache": true, "prover_version": "master", "server" : "production", @@ -30,10 +31,8 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, - "prover_args": [ - "-splitParallel true", - "-deleteSMTFile false", - "-smt_easy_LIA true" + "prover_args": [ + " -smt_easy_LIA true" ], - "smt_timeout": "4800", + "smt_timeout": "7200", } \ No newline at end of file diff --git a/certora/conf/healthStatus/VaultHealthStatus.conf b/certora/conf/healthStatus/VaultHealthStatus.conf index 703a41c4..c44e959c 100644 --- a/certora/conf/healthStatus/VaultHealthStatus.conf +++ b/certora/conf/healthStatus/VaultHealthStatus.conf @@ -17,7 +17,7 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy" + "accountsStayHealthy_strategy" ], "build_cache": true, "prover_version": "master", diff --git a/certora/harness/AbstractBaseHarness.sol b/certora/harness/AbstractBaseHarness.sol index 5dfdf2c6..c441d13e 100644 --- a/certora/harness/AbstractBaseHarness.sol +++ b/certora/harness/AbstractBaseHarness.sol @@ -49,6 +49,9 @@ abstract contract AbstractBaseHarness is Base, LiquidityUtils { // This mirrors LiquidityUtils.checkLiquidity except that it returns // a bool rather than reverting. + // NOTE: for now it is also different in that it will use the LTV + // value for liquidation rather than non-liquidation to be + // more conservative about collateral values function checkLiquidityReturning(address account, address[] memory collaterals) public returns (bool) { VaultCache memory vaultCache = loadVault(); diff --git a/certora/harness/healthStatus/BorrowingHSHarness.sol b/certora/harness/healthStatus/BorrowingHSHarness.sol index 10f01e50..a25a8705 100644 --- a/certora/harness/healthStatus/BorrowingHSHarness.sol +++ b/certora/harness/healthStatus/BorrowingHSHarness.sol @@ -11,25 +11,4 @@ import "../../../src/EVault/modules/Borrowing.sol"; contract BorrowingHSHarness is BorrowingModule, RiskManagerModule, AbstractBaseHarness { constructor(Integrations memory integrations) Base(integrations) {} - - // This mirrors LiquidityUtils.checkLiquidity except that it returns - // a bool rather than reverting. - // Try importing LiquidityUtils into BaseHarness or AbstractBaseHarness - // and moving this function there. - // function checkLiquidityReturning(address account, address[] memory collaterals) public returns (bool) { - // VaultCache memory vaultCache = loadVault(); - // - // Owed owed = vaultStorage.users[account].getOwed(); - // if (owed.isZero()) return true; - - // uint256 liabilityValue = getLiabilityValue(vaultCache, account, owed, false); - - // uint256 collateralValue; - // for (uint256 i; i < collaterals.length; ++i) { - // collateralValue += getCollateralValue(vaultCache, account, collaterals[i], false); - // if (collateralValue > liabilityValue) return true; - // } - - // return false; - // } } \ No newline at end of file diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index 7853c840..7079c3f1 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -9,18 +9,25 @@ methods { // by the EVC before which this flag will be set. function EVCHarness.areChecksInProgress() external returns bool => CVLAreChecksInProgress(); // unresolved calls that havoc all contracts + function _.isHookTarget() external => NONDET; function _.invokeHookTarget(address caller) internal => NONDET; function _.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal => NONDET; function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; - function _.requireVaultStatusCheck() external => DISPATCHER(true); - // function _.requireAccountAndVaultStatusCheck(address account) external => NONDET; - function _.requireAccountAndVaultStatusCheck(address) external => DISPATCHER(true); function _.emitTransfer(address from, address to, uint256 value) external => NONDET; function EVCHarness.disableController(address account) external => NONDET; - // function _.computeInterestRate(BaseHarness.VaultCache memory vaultCache) internal => NONDET; function _.computeInterestRate(address vault, uint256 cash, uint256 borrows) external => NONDET; function _.onFlashLoan(bytes data) external => NONDET; - function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLTrySafeTransferFrom(e, from, to, value) expect (bool, bytes memory); + // function _.computeInterestRate(BaseHarness.VaultCache memory vaultCache) internal => NONDET; + + // EVC + function _.requireVaultStatusCheck() external => DISPATCHER(true); + function _.requireAccountAndVaultStatusCheck(address) external => DISPATCHER(true); + + // Summaries + function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLSafeTransferFrom(e, from, to, value) expect void; + function _.enforceCollateralTransfer(address collateral, uint256 amount, + address from, address receiver) internal => + CVLEnforceCollateralTransfer(collateral, amount, from, receiver) expect void; // We can't handle the low-level call in // EthereumVaultConnector.checkAccountStatusInternal // and so reroute it to RiskManager's status check with this summary. @@ -28,13 +35,11 @@ methods { CVLCheckAccountStatusInternal(e, account); function EthereumVaultConnector.checkVaultStatusInternal(address vault) internal returns (bool, bytes memory) with(env e) => CVLCheckVaultStatusInternal(e); - // NONDET checkVaultStatus because I think it is not related to enforcing - // this and it is expensive for the tool. - // function currentContract.checkVaultStatus() external returns (bytes4) => NONDET; } // We summarize EthereumVaultConnector.checkAccountStatusInternal -// because we need +// because we need to direct the low-level call to RiskManager. +// checkAccountStatus and this linking doesn't happen automatically function CVLCheckAccountStatusInternalBool(env e, address account) returns bool { address[] collaterals = evc.getCollaterals(e, account); checkAccountStatus@withrevert(e, account, collaterals); @@ -66,13 +71,35 @@ function CVLTrySafeTransferFrom(env e, address from, address to, uint256 value) return (ERC20a.transferFrom(e, from, to, value), ret); } +function CVLSafeTransferFrom(env e, address from, address to, uint256 value) { + ERC20a.transferFrom(e, from, to, value); +} + +function CVLEnforceCollateralTransfer(address collateral, uint256 amount, address from, address receiver) { + // Ideally we would reroute this to a call of transfer + // on the specific collateral address, but I am not sure we + // have a way to do this. For now assume the collateral is ERC20a. + env e2; + require e2.msg.sender == from; + require collateral == ERC20a; + ERC20a.transfer(e2, receiver, amount); +} // Assuming the prices stay the same, a healthy account can never become // unhealthy. Here, our assumption that the prices do not change is implicit // in the fact that the summary for GetQuote is an uninterpreted function -- // the prover will model it as a function so it will always return the same // value when given the same arguments. -rule accountsStayHealthy (method f) { +rule accountsStayHealthy (method f) filtered { f -> + // Literal selectors are used to avoid compilation errors when + // only some of the modules are in the verification scene + // sig:GovernanceModule.clearLTV(address).selector + f.selector != 0x8255d029 && + // sig:GovernanceModule.setLTV(address,uint16,uint32).selector + f.selector != 0x8b308de9 && + // sig:InitializeModule.initialize(address).selector + f.selector != 0xc4d66de8 +}{ env e; calldataarg args; address account; @@ -90,6 +117,9 @@ rule accountsStayHealthy (method f) { require account != oracleAddress; require account != evc; require account != unitOfAccount; + + require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[0])); + require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[1])); // Otherwise this can cause an unintersting divide by zero in OwedLib.getCurrentOwed (on the mulDiv) require getUserInterestAccumulator(e, account) > 0; @@ -109,7 +139,16 @@ rule accountsStayHealthy (method f) { assert healthyBefore => healthyAfter; } -rule accountsStayHealthy_strategy (method f) { +rule accountsStayHealthy_strategy (method f) filtered { f -> + // Literal selectors are used to avoid compilation errors when + // only some of the modules are in the verification scene + // sig:GovernanceModule.clearLTV(address).selector + f.selector != 0x8255d029 && + // sig:GovernanceModule.setLTV(address,uint16,uint32).selector + f.selector != 0x8b308de9 && + // sig:InitializeModule.initialize(address).selector + f.selector != 0xc4d66de8 +}{ env e; calldataarg args; address account; @@ -127,6 +166,9 @@ rule accountsStayHealthy_strategy (method f) { require account != evc; require account != unitOfAccount; + require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[0])); + require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[1])); + bool healthyBefore = checkLiquidityReturning(e, account, collaterals); f(e, args); // The only way to call a vault funciton is through EVC's call, batch, diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 4fd9727b..aa3c1685 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -88,8 +88,8 @@ methods { function _.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal => NONDET; function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; // Type Conversions - function _.toShares(uint256 amount) internal => CVLToShares(amount) expect (BaseHarness.Shares); - function _.toAssets(uint256 amount) internal => CVLToAssets(amount) expect (BaseHarness.Assets); + // function _.toShares(uint256 amount) internal => CVLToShares(amount) expect (BaseHarness.Shares); + // function _.toAssets(uint256 amount) internal => CVLToAssets(amount) expect (BaseHarness.Assets); // This is NONDET to help avoid timeouts. It should be safe // to NONDET since it is a private view function. function _.resolve(Vault.AmountCap self) internal => NONDET; @@ -249,6 +249,8 @@ invariant assetsMoreThanSupply(env e) totalAssets(e) >= totalSupply(e) { preserved { + // require totalAssets(e) >= 1e6; + // require totalSupply(e) >= 1e6; require e.msg.sender != currentContract; require actualCaller(e) != currentContract; require actualCallerCheckController(e) != currentContract; diff --git a/src/EVault/modules/Vault.sol b/src/EVault/modules/Vault.sol index 38cbfda2..90b8a573 100644 --- a/src/EVault/modules/Vault.sol +++ b/src/EVault/modules/Vault.sol @@ -171,7 +171,7 @@ abstract contract VaultModule is IVault, AssetTransfers, BalanceUtils { if (shares.isZero()) return 0; // Changed by Certora - Assets assets = shares.toAssetsDownSubShares(vaultCache); + Assets assets = shares.toAssetsDown(vaultCache); if (assets.isZero()) revert E_ZeroAssets(); finalizeWithdraw(vaultCache, assets, shares, account, receiver, owner); diff --git a/src/EVault/shared/types/Shares.sol b/src/EVault/shared/types/Shares.sol index fd05afad..14f9ef50 100644 --- a/src/EVault/shared/types/Shares.sol +++ b/src/EVault/shared/types/Shares.sol @@ -27,11 +27,11 @@ library SharesLib { } // Introduced by Certora - function toAssetsDownSubShares(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) { - (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache); - return TypesLib.toAssets(amount.toUint() * totalAssets / - (totalShares - amount.toUint())); - } + // function toAssetsDownSubShares(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) { + // (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache); + // return TypesLib.toAssets(amount.toUint() * totalAssets / + // (totalShares - amount.toUint())); + // } function toAssetsUp(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) { From 454e266fde7dc5e0d1217420fc8c34e750507cfe Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 5 Jun 2024 16:13:31 +0100 Subject: [PATCH 090/152] Martin's spec performance improvement refactoring supply calls --- certora/conf/VaultERC4626.conf | 42 +++++++++++++------------ certora/specs/VaultERC4626.spec | 54 +++++++++++++++++++++------------ 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index c0e5cd7f..3b776f2b 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -22,23 +22,23 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "rule": [ - // // "conversionWeakMonotonicity", - "assetsMoreThanSupply", - // // "contributingProducesShares", - // // "depositMonotonicity", - // // "dustFavorsTheHouse", - // // "noAssetsIfNoSupply", - // // "noSupplyIfNoAssets", - // // "supplyLessThanUnderlyingAsset", - // // "onlyContributionMethodsReduceAssets", - // // "reclaimingProducesAssets", - // // "redeemingAllValidity", - // // "totalsMonotonicity", - // // "vaultSolvency" - ], + // "rule": [ + // // // "conversionWeakMonotonicity", + // "assetsMoreThanSupply", + // // // "contributingProducesShares", + // // // "depositMonotonicity", + // // // "dustFavorsTheHouse", + // // // "noAssetsIfNoSupply", + // // // "noSupplyIfNoAssets", + // // // "supplyLessThanUnderlyingAsset", + // // // "onlyContributionMethodsReduceAssets", + // // // "reclaimingProducesAssets", + // // // "redeemingAllValidity", + // // // "totalsMonotonicity", + // // // "vaultSolvency" + // ], // "coverage_info" : "advanced", - "method": "redeem(uint256,address,address)", + // "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", // "method" : "deposit(uint256,address)", // "method" : "mint(uint256,address)", @@ -51,9 +51,12 @@ "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", - "prover_args": [ - "-adaptiveSolverConfig false -depth 0 -smt_nonLinearArithmetic true" - ], + "prover_args": [ + "-smt_easy_LIA true" + ], + // "prover_args": [ + // "-adaptiveSolverConfig false -depth 0 -smt_nonLinearArithmetic true" + // ], // "prover_args": [ // "-maxConcurrentTransforms INLINED_HOOKS:8", // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 " @@ -61,7 +64,6 @@ // "prover_args": [ // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 -s [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" // ] , - // old config // "prover_args": [ // "-smt_nonLinearArithmetic true", // "-adaptiveSolverConfig false", diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index aa3c1685..154fe316 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -245,12 +245,10 @@ rule zeroDepositZeroShares(uint assets, address receiver) //// # Valid State ///// //////////////////////////////////////////////////////////////////////////////// -invariant assetsMoreThanSupply(env e) - totalAssets(e) >= totalSupply(e) +invariant assetsMoreThanSupply(env e, uint256 totalAssets) + totalAssets >= totalSupply(e) { preserved { - // require totalAssets(e) >= 1e6; - // require totalSupply(e) >= 1e6; require e.msg.sender != currentContract; require actualCaller(e) != currentContract; require actualCallerCheckController(e) != currentContract; @@ -260,11 +258,23 @@ invariant assetsMoreThanSupply(env e) } } -invariant noAssetsIfNoSupply(env e) - ( totalAssets(e) == 0 => ( totalSupply(e) == 0 )) +invariant supplyLessThanUnderlyingAsset(env e) + toSharesExt(e, userAssets(e, currentContract)) >= totalSupply(e) + { + preserved { + address any; + safeAssumptions(e, any, actualCaller(e)); + safeAssumptions(e, any, actualCallerCheckController(e)); + } + } + +invariant noAssetsIfNoSupply(env e, uint256 totalSupply, uint256 totalAssets) + ( userAssets(e, currentContract) == 0 => totalSupply == 0 ) && + ( totalAssets == 0 => ( totalSupply == 0 )) { preserved { + requireInvariant supplyLessThanUnderlyingAsset(e); address any; safeAssumptions(e, any, actualCaller(e)); safeAssumptions(e, any, actualCallerCheckController(e)); @@ -303,9 +313,9 @@ hook Sload Vault.PackedUserSlot val currentContract.vaultStorage.users[KEY addre // } // passing: https://prover.certora.com/output/65266/de3636d287d2473294463c07263fc11e/?anonymousKey=ac8f74e6c5c1298f0954a21fafd41cccf32b9ffb -invariant totalSupplyIsSumOfBalances(env e) - // to_mathint(totalSupply(e)) == sumOfBalances + accumulatedFees(e); - to_mathint(totalSupply(e)) == sumOfBalances; +invariant totalSupplyIsSumOfBalances(env e, uint256 totalSupply) + // to_mathint(totalSupply) == sumOfBalances + accumulatedFees(e); + to_mathint(totalSupply) == sumOfBalances; @@ -393,10 +403,10 @@ rule dustFavorsTheHouse(uint assetsIn ) //////////////////////////////////////////////////////////////////////////////// -invariant vaultSolvency(env e) - totalAssets(e) >= totalSupply(e) && userAssets(e, currentContract) >= require_uint256(cache_cash(e)) { +invariant vaultSolvency(env e, uint256 totalSupply, uint256 totalAssets) + totalAssets >= totalSupply && userAssets(e, currentContract) >= require_uint256(cache_cash(e)) { preserved { - requireInvariant totalSupplyIsSumOfBalances(e); + requireInvariant totalSupplyIsSumOfBalances(e, totalSupply); require e.msg.sender != currentContract; require actualCaller(e) != currentContract; require actualCallerCheckController(e) != currentContract; @@ -512,6 +522,8 @@ filtered { //////////////////////////////////////////////////////////////////////////////// definition noSupplyIfNoAssetsDef(env e) returns bool = + // for this ERC4626 implementation balanceOf(Vault) is not the same as total assets + // ( userAssets(e, currentContract) == 0 => totalSupply(e) == 0 ) && ( totalAssets(e) == 0 => ( totalSupply(e) == 0 )); // definition noSupplyIfNoAssetsStrongerDef() returns bool = // fails for ERC4626BalanceOfHarness as explained in the readme @@ -520,12 +532,14 @@ definition noSupplyIfNoAssetsDef(env e) returns bool = function safeAssumptions(env e, address receiver, address owner) { + uint256 totalSupply = totalSupply(e); + uint256 totalAssets = totalAssets(e); require currentContract != asset(); // Although this is not disallowed, we assume the contract's underlying asset is not the contract itself - requireInvariant totalSupplyIsSumOfBalances(e); - requireInvariant vaultSolvency(e); - requireInvariant noAssetsIfNoSupply(e); + requireInvariant totalSupplyIsSumOfBalances(e, totalSupply); + requireInvariant vaultSolvency(e, totalSupply, totalAssets); + requireInvariant noAssetsIfNoSupply(e, totalSupply, totalAssets); requireInvariant noSupplyIfNoAssets(e); - requireInvariant assetsMoreThanSupply(e); + requireInvariant assetsMoreThanSupply(e, totalAssets); //// # Note : we don't want to use singleBalanceBounded and singleBalanceBounded invariants /* requireInvariant sumOfBalancePairsBounded(receiver, owner ); @@ -533,9 +547,9 @@ function safeAssumptions(env e, address receiver, address owner) { requireInvariant singleBalanceBounded(owner); */ ///// # but, it safe to assume that a single balance is less than sum of balances - require ( (receiver != owner => balanceOf(e, owner) + balanceOf(e, receiver) <= to_mathint(totalSupply(e))) && - balanceOf(e, receiver) <= totalSupply(e) && - balanceOf(e, owner) <= totalSupply(e)); + require ( (receiver != owner => balanceOf(e, owner) + balanceOf(e, receiver) <= to_mathint(totalSupply)) && + balanceOf(e, receiver) <= totalSupply && + balanceOf(e, owner) <= totalSupply); } @@ -619,4 +633,4 @@ rule sanity (method f) { // CVLInitVaultCache(e, vaultCache); // totalSupply(e); // assert false; -// } \ No newline at end of file +// } From 4c035d233b13246daa262c9c608e9a7e99c6dbed Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 6 Jun 2024 09:25:54 +0100 Subject: [PATCH 091/152] Fix change in LTVConfig --- certora/conf/healthStatus/BorrowingHealthStatus.conf | 4 ++-- certora/harness/ERC4626Harness.sol | 2 +- certora/specs/Base.spec | 6 +++--- lib/ethereum-vault-connector | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/certora/conf/healthStatus/BorrowingHealthStatus.conf b/certora/conf/healthStatus/BorrowingHealthStatus.conf index f69b0820..3cfc6ed3 100644 --- a/certora/conf/healthStatus/BorrowingHealthStatus.conf +++ b/certora/conf/healthStatus/BorrowingHealthStatus.conf @@ -17,9 +17,9 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy_strategy" + "accountsStayHealthy" ], - // "method" : "borrow(uint256,address)", + "method" : "borrow(uint256,address)", "build_cache": true, "prover_version": "master", "server" : "production", diff --git a/certora/harness/ERC4626Harness.sol b/certora/harness/ERC4626Harness.sol index cc559052..70661da7 100644 --- a/certora/harness/ERC4626Harness.sol +++ b/certora/harness/ERC4626Harness.sol @@ -42,7 +42,7 @@ contract ERC4626Harness is VaultModule, TokenModule, AbstractBaseHarness { function toSharesExt(uint256 amount) external view returns (uint256) { require(amount < MAX_SANE_AMOUNT, "Assets are really uint112"); VaultCache memory vaultCache = loadVault(); - return Assets.wrap(uint112(amount)).toSharesDownUint256(vaultCache); + return Assets.wrap(uint112(amount)).toSharesDownUint(vaultCache); } function cache_cash() public view returns (Assets) { diff --git a/certora/specs/Base.spec b/certora/specs/Base.spec index 5b3fe993..6581ac81 100644 --- a/certora/specs/Base.spec +++ b/certora/specs/Base.spec @@ -72,9 +72,9 @@ function CVLUseViewCaller() returns address { } function LTVConfigAssumptions(env e, BaseHarness.LTVConfig ltvConfig) returns bool { - bool targetLTVLessOne = ltvConfig.targetLTV < 10000; - bool originalLTVLessOne = ltvConfig.originalLTV < 10000; - bool target_less_original = ltvConfig.targetLTV < ltvConfig.originalLTV; + bool targetLTVLessOne = ltvConfig.liquidationLTV < 10000; + bool originalLTVLessOne = ltvConfig.initialLiquidationLTV < 10000; + bool target_less_original = ltvConfig.initialLiquidationLTV < ltvConfig.liquidationLTV; mathint timeRemaining = ltvConfig.targetTimestamp - e.block.timestamp; return targetLTVLessOne && originalLTVLessOne && diff --git a/lib/ethereum-vault-connector b/lib/ethereum-vault-connector index d24ce8c9..0229f62f 160000 --- a/lib/ethereum-vault-connector +++ b/lib/ethereum-vault-connector @@ -1 +1 @@ -Subproject commit d24ce8c92fffbd0c95948c1c843e3e23b27318ca +Subproject commit 0229f62f92856201e1f33bee9e59daf68938ba34 From f44b1417a33b5c373d010785879412d180835a18 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 7 Jun 2024 11:14:20 +0100 Subject: [PATCH 092/152] Revert "Martin's spec performance improvement" This reverts commit 68c0d900c3d19e8c16fcd6c13adb8ffcbd4bfefe. --- certora/conf/VaultERC4626.conf | 42 ++++++++++++------------- certora/specs/VaultERC4626.spec | 54 ++++++++++++--------------------- 2 files changed, 40 insertions(+), 56 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index 3b776f2b..c0e5cd7f 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -22,23 +22,23 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - // "rule": [ - // // // "conversionWeakMonotonicity", - // "assetsMoreThanSupply", - // // // "contributingProducesShares", - // // // "depositMonotonicity", - // // // "dustFavorsTheHouse", - // // // "noAssetsIfNoSupply", - // // // "noSupplyIfNoAssets", - // // // "supplyLessThanUnderlyingAsset", - // // // "onlyContributionMethodsReduceAssets", - // // // "reclaimingProducesAssets", - // // // "redeemingAllValidity", - // // // "totalsMonotonicity", - // // // "vaultSolvency" - // ], + "rule": [ + // // "conversionWeakMonotonicity", + "assetsMoreThanSupply", + // // "contributingProducesShares", + // // "depositMonotonicity", + // // "dustFavorsTheHouse", + // // "noAssetsIfNoSupply", + // // "noSupplyIfNoAssets", + // // "supplyLessThanUnderlyingAsset", + // // "onlyContributionMethodsReduceAssets", + // // "reclaimingProducesAssets", + // // "redeemingAllValidity", + // // "totalsMonotonicity", + // // "vaultSolvency" + ], // "coverage_info" : "advanced", - // "method": "redeem(uint256,address,address)", + "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", // "method" : "deposit(uint256,address)", // "method" : "mint(uint256,address)", @@ -51,12 +51,9 @@ "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", - "prover_args": [ - "-smt_easy_LIA true" - ], - // "prover_args": [ - // "-adaptiveSolverConfig false -depth 0 -smt_nonLinearArithmetic true" - // ], + "prover_args": [ + "-adaptiveSolverConfig false -depth 0 -smt_nonLinearArithmetic true" + ], // "prover_args": [ // "-maxConcurrentTransforms INLINED_HOOKS:8", // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 " @@ -64,6 +61,7 @@ // "prover_args": [ // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 -s [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" // ] , + // old config // "prover_args": [ // "-smt_nonLinearArithmetic true", // "-adaptiveSolverConfig false", diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 154fe316..aa3c1685 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -245,10 +245,12 @@ rule zeroDepositZeroShares(uint assets, address receiver) //// # Valid State ///// //////////////////////////////////////////////////////////////////////////////// -invariant assetsMoreThanSupply(env e, uint256 totalAssets) - totalAssets >= totalSupply(e) +invariant assetsMoreThanSupply(env e) + totalAssets(e) >= totalSupply(e) { preserved { + // require totalAssets(e) >= 1e6; + // require totalSupply(e) >= 1e6; require e.msg.sender != currentContract; require actualCaller(e) != currentContract; require actualCallerCheckController(e) != currentContract; @@ -258,23 +260,11 @@ invariant assetsMoreThanSupply(env e, uint256 totalAssets) } } -invariant supplyLessThanUnderlyingAsset(env e) - toSharesExt(e, userAssets(e, currentContract)) >= totalSupply(e) - { - preserved { - address any; - safeAssumptions(e, any, actualCaller(e)); - safeAssumptions(e, any, actualCallerCheckController(e)); - } - } - -invariant noAssetsIfNoSupply(env e, uint256 totalSupply, uint256 totalAssets) - ( userAssets(e, currentContract) == 0 => totalSupply == 0 ) && - ( totalAssets == 0 => ( totalSupply == 0 )) +invariant noAssetsIfNoSupply(env e) + ( totalAssets(e) == 0 => ( totalSupply(e) == 0 )) { preserved { - requireInvariant supplyLessThanUnderlyingAsset(e); address any; safeAssumptions(e, any, actualCaller(e)); safeAssumptions(e, any, actualCallerCheckController(e)); @@ -313,9 +303,9 @@ hook Sload Vault.PackedUserSlot val currentContract.vaultStorage.users[KEY addre // } // passing: https://prover.certora.com/output/65266/de3636d287d2473294463c07263fc11e/?anonymousKey=ac8f74e6c5c1298f0954a21fafd41cccf32b9ffb -invariant totalSupplyIsSumOfBalances(env e, uint256 totalSupply) - // to_mathint(totalSupply) == sumOfBalances + accumulatedFees(e); - to_mathint(totalSupply) == sumOfBalances; +invariant totalSupplyIsSumOfBalances(env e) + // to_mathint(totalSupply(e)) == sumOfBalances + accumulatedFees(e); + to_mathint(totalSupply(e)) == sumOfBalances; @@ -403,10 +393,10 @@ rule dustFavorsTheHouse(uint assetsIn ) //////////////////////////////////////////////////////////////////////////////// -invariant vaultSolvency(env e, uint256 totalSupply, uint256 totalAssets) - totalAssets >= totalSupply && userAssets(e, currentContract) >= require_uint256(cache_cash(e)) { +invariant vaultSolvency(env e) + totalAssets(e) >= totalSupply(e) && userAssets(e, currentContract) >= require_uint256(cache_cash(e)) { preserved { - requireInvariant totalSupplyIsSumOfBalances(e, totalSupply); + requireInvariant totalSupplyIsSumOfBalances(e); require e.msg.sender != currentContract; require actualCaller(e) != currentContract; require actualCallerCheckController(e) != currentContract; @@ -522,8 +512,6 @@ filtered { //////////////////////////////////////////////////////////////////////////////// definition noSupplyIfNoAssetsDef(env e) returns bool = - // for this ERC4626 implementation balanceOf(Vault) is not the same as total assets - // ( userAssets(e, currentContract) == 0 => totalSupply(e) == 0 ) && ( totalAssets(e) == 0 => ( totalSupply(e) == 0 )); // definition noSupplyIfNoAssetsStrongerDef() returns bool = // fails for ERC4626BalanceOfHarness as explained in the readme @@ -532,14 +520,12 @@ definition noSupplyIfNoAssetsDef(env e) returns bool = function safeAssumptions(env e, address receiver, address owner) { - uint256 totalSupply = totalSupply(e); - uint256 totalAssets = totalAssets(e); require currentContract != asset(); // Although this is not disallowed, we assume the contract's underlying asset is not the contract itself - requireInvariant totalSupplyIsSumOfBalances(e, totalSupply); - requireInvariant vaultSolvency(e, totalSupply, totalAssets); - requireInvariant noAssetsIfNoSupply(e, totalSupply, totalAssets); + requireInvariant totalSupplyIsSumOfBalances(e); + requireInvariant vaultSolvency(e); + requireInvariant noAssetsIfNoSupply(e); requireInvariant noSupplyIfNoAssets(e); - requireInvariant assetsMoreThanSupply(e, totalAssets); + requireInvariant assetsMoreThanSupply(e); //// # Note : we don't want to use singleBalanceBounded and singleBalanceBounded invariants /* requireInvariant sumOfBalancePairsBounded(receiver, owner ); @@ -547,9 +533,9 @@ function safeAssumptions(env e, address receiver, address owner) { requireInvariant singleBalanceBounded(owner); */ ///// # but, it safe to assume that a single balance is less than sum of balances - require ( (receiver != owner => balanceOf(e, owner) + balanceOf(e, receiver) <= to_mathint(totalSupply)) && - balanceOf(e, receiver) <= totalSupply && - balanceOf(e, owner) <= totalSupply); + require ( (receiver != owner => balanceOf(e, owner) + balanceOf(e, receiver) <= to_mathint(totalSupply(e))) && + balanceOf(e, receiver) <= totalSupply(e) && + balanceOf(e, owner) <= totalSupply(e)); } @@ -633,4 +619,4 @@ rule sanity (method f) { // CVLInitVaultCache(e, vaultCache); // totalSupply(e); // assert false; -// } +// } \ No newline at end of file From 4c8dac6f15e3c4ca6d4f294bd820469112efae4e Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 7 Jun 2024 11:11:30 +0100 Subject: [PATCH 093/152] Trim reqs in assetsMoreThanSupply, add conf options --- certora/conf/VaultERC4626.conf | 10 +++++++--- certora/specs/VaultERC4626.spec | 7 ------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index c0e5cd7f..dfa79a86 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -38,7 +38,7 @@ // // "vaultSolvency" ], // "coverage_info" : "advanced", - "method": "redeem(uint256,address,address)", + // "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", // "method" : "deposit(uint256,address)", // "method" : "mint(uint256,address)", @@ -51,10 +51,14 @@ "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", - "prover_args": [ - "-adaptiveSolverConfig false -depth 0 -smt_nonLinearArithmetic true" + // assetsMoreThanSupply + "prover_args" : [ + "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -splitParallel true -splitParallelInitialDepth 2 -depth 15 -s [z3:arith1,yices:def] -mediumTimeout 2 -splitParallelTimelimit 7200" ], // "prover_args": [ + // "-adaptiveSolverConfig false -depth 0 -smt_nonLinearArithmetic true" + // ], + // "prover_args": [ // "-maxConcurrentTransforms INLINED_HOOKS:8", // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 " // ] diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index aa3c1685..ce029fe5 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -249,14 +249,7 @@ invariant assetsMoreThanSupply(env e) totalAssets(e) >= totalSupply(e) { preserved { - // require totalAssets(e) >= 1e6; - // require totalSupply(e) >= 1e6; require e.msg.sender != currentContract; - require actualCaller(e) != currentContract; - require actualCallerCheckController(e) != currentContract; - address any; - safeAssumptions(e, any , actualCaller(e)); - safeAssumptions(e, any , actualCallerCheckController(e)); } } From 6735f88c5597313769985acb7952f4fb25752681 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 12 Jun 2024 09:42:34 +0100 Subject: [PATCH 094/152] Fixing some spurious counterexamples --- .../BalanceForwarderHealthStatus.conf | 1 + .../healthStatus/BorrowingHealthStatus.conf | 9 +- .../healthStatus/GovernanceHealthStatus.conf | 3 +- .../healthStatus/InitializeHealthStatus.conf | 1 + .../healthStatus/LiquidationHealthStatus.conf | 3 +- .../conf/healthStatus/TokenHealthStatus.conf | 1 + .../conf/healthStatus/VaultHealthStatus.conf | 2 + certora/harness/AbstractBaseHarness.sol | 3 - certora/specs/Base.spec | 8 +- certora/specs/HealthStatusInvariant.spec | 82 +++++++++++++------ 10 files changed, 78 insertions(+), 35 deletions(-) diff --git a/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf b/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf index 63376449..48d932a9 100644 --- a/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf +++ b/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf @@ -5,6 +5,7 @@ "certora/helpers/DummyERC20A.sol", "src/EVault/modules/BalanceForwarder.sol", "certora/harness/BaseHarness.sol", + "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/BalanceForwarderHSHarness.sol", ], "link": [ diff --git a/certora/conf/healthStatus/BorrowingHealthStatus.conf b/certora/conf/healthStatus/BorrowingHealthStatus.conf index 3cfc6ed3..4134ff18 100644 --- a/certora/conf/healthStatus/BorrowingHealthStatus.conf +++ b/certora/conf/healthStatus/BorrowingHealthStatus.conf @@ -5,6 +5,7 @@ "certora/helpers/DummyERC20A.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", + "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/BorrowingHSHarness.sol", ], "link": [ @@ -17,9 +18,13 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy" + // "accountsStayHealthy", + "accountsStayHealthy_strategy" ], - "method" : "borrow(uint256,address)", + // "method" : "borrow(uint256,address)", + // "method" : "pullDebt(uint256, address)", + // "method" : "repayWithShares(uint256,address)", + // "method" : "repay(uint256,address)", "build_cache": true, "prover_version": "master", "server" : "production", diff --git a/certora/conf/healthStatus/GovernanceHealthStatus.conf b/certora/conf/healthStatus/GovernanceHealthStatus.conf index dd1f972c..17194c9b 100644 --- a/certora/conf/healthStatus/GovernanceHealthStatus.conf +++ b/certora/conf/healthStatus/GovernanceHealthStatus.conf @@ -5,6 +5,7 @@ "certora/helpers/DummyERC20A.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", + "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/GovernanceHSHarness.sol", ], "link": [ @@ -17,7 +18,7 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy", + // "accountsStayHealthy", "accountsStayHealthy_strategy" ], "build_cache": true, diff --git a/certora/conf/healthStatus/InitializeHealthStatus.conf b/certora/conf/healthStatus/InitializeHealthStatus.conf index 49d1cad5..87db24c5 100644 --- a/certora/conf/healthStatus/InitializeHealthStatus.conf +++ b/certora/conf/healthStatus/InitializeHealthStatus.conf @@ -5,6 +5,7 @@ "certora/helpers/DummyERC20A.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", + "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/InitializeHSHarness.sol", ], "link": [ diff --git a/certora/conf/healthStatus/LiquidationHealthStatus.conf b/certora/conf/healthStatus/LiquidationHealthStatus.conf index 45256e96..de8d90b1 100644 --- a/certora/conf/healthStatus/LiquidationHealthStatus.conf +++ b/certora/conf/healthStatus/LiquidationHealthStatus.conf @@ -5,6 +5,7 @@ "certora/helpers/DummyERC20A.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", + "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/LiquidationHSHarness.sol", ], "link": [ @@ -17,7 +18,7 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy_strategy" + "accountsStayHealthy" ], "method":"liquidate(address,address,uint256,uint256)", "build_cache": true, diff --git a/certora/conf/healthStatus/TokenHealthStatus.conf b/certora/conf/healthStatus/TokenHealthStatus.conf index 0bc5e1e2..ec3a3c2d 100644 --- a/certora/conf/healthStatus/TokenHealthStatus.conf +++ b/certora/conf/healthStatus/TokenHealthStatus.conf @@ -5,6 +5,7 @@ "certora/helpers/DummyERC20A.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", + "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/TokenHSHarness.sol", ], "link": [ diff --git a/certora/conf/healthStatus/VaultHealthStatus.conf b/certora/conf/healthStatus/VaultHealthStatus.conf index c44e959c..cb41a892 100644 --- a/certora/conf/healthStatus/VaultHealthStatus.conf +++ b/certora/conf/healthStatus/VaultHealthStatus.conf @@ -4,7 +4,9 @@ "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", "src/EVault/modules/Vault.sol", + "src/EVault/modules/Token.sol", "certora/harness/BaseHarness.sol", + "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/VaultHSHarness.sol", ], "link": [ diff --git a/certora/harness/AbstractBaseHarness.sol b/certora/harness/AbstractBaseHarness.sol index c441d13e..5dfdf2c6 100644 --- a/certora/harness/AbstractBaseHarness.sol +++ b/certora/harness/AbstractBaseHarness.sol @@ -49,9 +49,6 @@ abstract contract AbstractBaseHarness is Base, LiquidityUtils { // This mirrors LiquidityUtils.checkLiquidity except that it returns // a bool rather than reverting. - // NOTE: for now it is also different in that it will use the LTV - // value for liquidation rather than non-liquidation to be - // more conservative about collateral values function checkLiquidityReturning(address account, address[] memory collaterals) public returns (bool) { VaultCache memory vaultCache = loadVault(); diff --git a/certora/specs/Base.spec b/certora/specs/Base.spec index 6581ac81..6b327e8d 100644 --- a/certora/specs/Base.spec +++ b/certora/specs/Base.spec @@ -74,12 +74,14 @@ function CVLUseViewCaller() returns address { function LTVConfigAssumptions(env e, BaseHarness.LTVConfig ltvConfig) returns bool { bool targetLTVLessOne = ltvConfig.liquidationLTV < 10000; bool originalLTVLessOne = ltvConfig.initialLiquidationLTV < 10000; - bool target_less_original = ltvConfig.initialLiquidationLTV < ltvConfig.liquidationLTV; + bool liquidationLTVHigher = ltvConfig.liquidationLTV > ltvConfig.borrowLTV; + bool initialLTVHigherTarget = ltvConfig.initialLiquidationLTV > ltvConfig.liquidationLTV; mathint timeRemaining = ltvConfig.targetTimestamp - e.block.timestamp; return targetLTVLessOne && originalLTVLessOne && - target_less_original && - require_uint32(timeRemaining) < ltvConfig.rampDuration; + liquidationLTVHigher && + initialLTVHigherTarget && + (require_uint32(timeRemaining) < ltvConfig.rampDuration); } function actualCaller(env e) returns address { diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index 7079c3f1..ac245048 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -1,6 +1,7 @@ import "Base.spec"; import "LoadVaultSummary.spec"; using DummyERC20A as ERC20a; +using TokenHarness as EToken; // Used to assume collaterals are ETokens methods { function checkAccountMagicValue() external returns (bytes4) envfree; @@ -24,10 +25,12 @@ methods { function _.requireAccountAndVaultStatusCheck(address) external => DISPATCHER(true); // Summaries - function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLSafeTransferFrom(e, from, to, value) expect void; + function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLSafeTransferFrom(e, token, from, to, value) expect void; function _.enforceCollateralTransfer(address collateral, uint256 amount, - address from, address receiver) internal => - CVLEnforceCollateralTransfer(collateral, amount, from, receiver) expect void; + address from, address receiver) internal with (env e) => + CVLEnforceCollateralTransfer(e, collateral, amount, from, receiver) expect void; + // To deal with changes between LTV values: + // function _.getLTV(address collateral, bool liquidation) internal => CVLGetLTV(collateral, liquidation) expect (BaseHarness.ConfigAmount); // We can't handle the low-level call in // EthereumVaultConnector.checkAccountStatusInternal // and so reroute it to RiskManager's status check with this summary. @@ -37,6 +40,13 @@ methods { CVLCheckVaultStatusInternal(e); } +// TODO ideally delete this if it is really not needed. +// persistent ghost uint16 ghost_ltv; +// function CVLGetLTV(address collateral, bool liquidation) returns uint16 { +// require ghost_ltv > 0; +// return ghost_ltv; +// } + // We summarize EthereumVaultConnector.checkAccountStatusInternal // because we need to direct the low-level call to RiskManager. // checkAccountStatus and this linking doesn't happen automatically @@ -65,24 +75,36 @@ function CVLAreChecksInProgress() returns bool { return true; } -// Summarize trySafeTransferFrom as DummyERC20 transferFrom -function CVLTrySafeTransferFrom(env e, address from, address to, uint256 value) returns (bool, bytes) { - bytes ret; // Ideally bytes("") if there is a way to do this - return (ERC20a.transferFrom(e, from, to, value), ret); -} - -function CVLSafeTransferFrom(env e, address from, address to, uint256 value) { - ERC20a.transferFrom(e, from, to, value); +function CVLSafeTransferFrom(env e, address token, address from, address to, uint256 value) { + if (token == ERC20a) { + ERC20a.transferFrom(e, from, to, value); + } else if (token == EToken) { + EToken.transferFrom(e, from, to, value); + } } -function CVLEnforceCollateralTransfer(address collateral, uint256 amount, address from, address receiver) { - // Ideally we would reroute this to a call of transfer - // on the specific collateral address, but I am not sure we - // have a way to do this. For now assume the collateral is ERC20a. - env e2; - require e2.msg.sender == from; - require collateral == ERC20a; - ERC20a.transfer(e2, receiver, amount); +/* +* The prover struggles to reason about the low-level call operations involved +* in the real EVCClient.enforceControlCollateral function, so we need +* to emulate the real behavior here. Here's how it works in the real code: +* - EVCClient calls evc.controlCollateral passing a call to `transfer(receiver, amount)` along with the collateral and from addresses +* - In controlCollateral the from address is used to set the onBehalfOfAccount +* and some authentication is done +* - After this, callWithContextInternal invokes the transfer function +* on the collateral address +* - Collaterals in the EVK must all be Token.sol and token's transfer +* implementation calls initOperation which enqueues an account status +* check on the EVC for the onBehalfOfAccount it also gets from EVC. +* Because onBehalfOfAccount was set to the from address in callWithContextInternal this status check is for the from account +* To emulate this, we: +* - explicitly call EToken.transferFrom using the expected addresses +* - enqueue a status check on the evc for the "from" address +*/ +function CVLEnforceCollateralTransfer(env e, address collateral, uint256 amount, address from, address receiver) { + // (Ideally: Cast collateral address into EToken to allow for + // multiple addresses with the EToken contract) + evc.requireAccountStatusCheck(e, from); + EToken.transferFromInternalHarnessed(e, from, receiver, amount); } // Assuming the prices stay the same, a healthy account can never become @@ -95,10 +117,10 @@ rule accountsStayHealthy (method f) filtered { f -> // only some of the modules are in the verification scene // sig:GovernanceModule.clearLTV(address).selector f.selector != 0x8255d029 && - // sig:GovernanceModule.setLTV(address,uint16,uint32).selector - f.selector != 0x8b308de9 && + // sig:GovernanceModule.setLTV(address,uint16,uint16,uint32).selector + f.selector != 0x4bca3d5b && // sig:InitializeModule.initialize(address).selector - f.selector != 0xc4d66de8 + f.selector != 0xc4d66de8 }{ env e; calldataarg args; @@ -112,6 +134,9 @@ rule accountsStayHealthy (method f) filtered { f -> // Vault should not be used as a collateral require collaterals[0] != currentContract; require collaterals[1] != currentContract; + // Collaterals must be ETokens + require collaterals[0] == EToken; + require collaterals[1] == EToken; // not sure the following 4 are really needed require account != erc20; require account != oracleAddress; @@ -133,6 +158,8 @@ rule accountsStayHealthy (method f) filtered { f -> // The only way to call a vault funciton is through EVC's call, batch, // or permit. During all of these status checks are deferred and at the end // these call restoreExecutionContext which triggers the deferred checks. + // This excplicit call to checkStatusAll is a way to get a setup that + // approximates the real situation. evc.checkStatusAllExt(e); checkAccountStatus@withrevert(e, account, collaterals); bool healthyAfter= !lastReverted; @@ -143,9 +170,9 @@ rule accountsStayHealthy_strategy (method f) filtered { f -> // Literal selectors are used to avoid compilation errors when // only some of the modules are in the verification scene // sig:GovernanceModule.clearLTV(address).selector - f.selector != 0x8255d029 && - // sig:GovernanceModule.setLTV(address,uint16,uint32).selector - f.selector != 0x8b308de9 && + f.selector != 0x8255d029 && + // sig:GovernanceModule.setLTV(address,uint16,uint16,uint32).selector + f.selector != 0x4bca3d5b && // sig:InitializeModule.initialize(address).selector f.selector != 0xc4d66de8 }{ @@ -160,6 +187,9 @@ rule accountsStayHealthy_strategy (method f) filtered { f -> // Vault should not be used as a collateral require collaterals[0] != currentContract; require collaterals[1] != currentContract; + // Collaterals must be ETokens + require collaterals[0] == EToken; + require collaterals[1] == EToken; // not sure the following 4 are really needed require account != erc20; require account != oracleAddress; @@ -174,6 +204,8 @@ rule accountsStayHealthy_strategy (method f) filtered { f -> // The only way to call a vault funciton is through EVC's call, batch, // or permit. During all of these status checks are deferred and at the end // these call restoreExecutionContext which triggers the deferred checks. + // This excplicit call to checkStatusAll is a way to get a setup that + // approximates the real situation. evc.checkStatusAllExt(e); bool healthyAfter = checkLiquidityReturning(e, account, collaterals); assert healthyBefore => healthyAfter; From eed28909b1ab3c3086ec91cb5653f99dd8afbc22 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 12 Jun 2024 12:56:20 +0100 Subject: [PATCH 095/152] Add missing harness, add scripts --- certora/conf/ERC4626Rules/BaseERC4626.conf | 75 +++++++++++++++++++ certora/conf/VaultERC4626.conf | 47 ++++++------ .../healthStatus/BorrowingHealthStatus.conf | 14 ++-- .../conf/healthStatus/VaultHealthStatus.conf | 4 +- certora/harness/TokenHarness.sol | 25 +++++++ .../runERC4626RulesSeparatelySplitConfs.py | 37 +++++++++ certora/scripts/runHealthStatusAllModules.py | 28 +++++++ 7 files changed, 199 insertions(+), 31 deletions(-) create mode 100644 certora/conf/ERC4626Rules/BaseERC4626.conf create mode 100644 certora/harness/TokenHarness.sol create mode 100644 certora/scripts/runERC4626RulesSeparatelySplitConfs.py create mode 100644 certora/scripts/runHealthStatusAllModules.py diff --git a/certora/conf/ERC4626Rules/BaseERC4626.conf b/certora/conf/ERC4626Rules/BaseERC4626.conf new file mode 100644 index 00000000..aa9a0c9e --- /dev/null +++ b/certora/conf/ERC4626Rules/BaseERC4626.conf @@ -0,0 +1,75 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + // "rule": [ + // // // "conversionWeakMonotonicity", + // // "assetsMoreThanSupply", + // // // "contributingProducesShares", + // // // "depositMonotonicity", + // // // "dustFavorsTheHouse", + // // // "noAssetsIfNoSupply", + // // // "noSupplyIfNoAssets", + // // // "supplyLessThanUnderlyingAsset", + // // // "onlyContributionMethodsReduceAssets", + // // // "reclaimingProducesAssets", + // // // "redeemingAllValidity", + // // // "totalsMonotonicity", + // // // "vaultSolvency" + // ], + // "method": "redeem(uint256,address,address)", + // "method": "withdraw(uint256,address,address)", + // "method" : "deposit(uint256,address)", + // "method" : "mint(uint256,address)", + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + // "prover_args" : [ + // "-smt_nonLinearArithmetic true", + // "-adaptiveSolverConfig false", + // "-splitParallel true" + // ], + // assetsMoreThanSupply + // "prover_args" : [ + // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -splitParallel true -splitParallelInitialDepth 2 -depth 15 -s [z3:arith1,yices:def] -mediumTimeout 2 -splitParallelTimelimit 7200" + // ], + // "prover_args": [ + // "-adaptiveSolverConfig false -depth 0 -smt_nonLinearArithmetic true" + // ], + // "prover_args": [ + // "-maxConcurrentTransforms INLINED_HOOKS:8", + // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 " + // ] + // "prover_args": [ + // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 -s [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" + // ] , + // old config + // "prover_args": [ + // "-smt_nonLinearArithmetic true", + // "-adaptiveSolverConfig false", + // "-solvers [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]", + // "-splitParallel true" + // ], + // "nondet_difficult_funcs": true, + // "smt_timeout": "7200" +} + diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index dfa79a86..e9688e0f 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -1,8 +1,6 @@ { "files": [ - // TODO do we need EVC for this spec? "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - // "lib/ethereum-vault-connector/src/ExecutionContext.sol", "src/EVault/modules/Vault.sol", "certora/helpers/DummyERC20A.sol", "certora/helpers/DummyERC20B.sol", @@ -10,10 +8,6 @@ "certora/harness/BaseHarness.sol", "certora/harness/ERC4626Harness.sol", ], - // "link" : [ - // // "ERC4626Harness:evc=EthereumVaultConnector", - // "ERC4626Harness:underlying_asset=DummyERC20A" - // ], "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", "solc": "solc8.23", "rule_sanity": "basic", @@ -22,22 +16,21 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "rule": [ - // // "conversionWeakMonotonicity", - "assetsMoreThanSupply", - // // "contributingProducesShares", - // // "depositMonotonicity", - // // "dustFavorsTheHouse", - // // "noAssetsIfNoSupply", - // // "noSupplyIfNoAssets", - // // "supplyLessThanUnderlyingAsset", - // // "onlyContributionMethodsReduceAssets", - // // "reclaimingProducesAssets", - // // "redeemingAllValidity", - // // "totalsMonotonicity", - // // "vaultSolvency" - ], - // "coverage_info" : "advanced", + // "rule": [ + // // // "conversionWeakMonotonicity", + // // "assetsMoreThanSupply", + // // // "contributingProducesShares", + // // // "depositMonotonicity", + // // // "dustFavorsTheHouse", + // // // "noAssetsIfNoSupply", + // // // "noSupplyIfNoAssets", + // // // "supplyLessThanUnderlyingAsset", + // // // "onlyContributionMethodsReduceAssets", + // // // "reclaimingProducesAssets", + // // // "redeemingAllValidity", + // // // "totalsMonotonicity", + // // // "vaultSolvency" + // ], // "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", // "method" : "deposit(uint256,address)", @@ -45,16 +38,20 @@ "parametric_contracts": ["ERC4626Harness"], "build_cache": true, "prover_version" : "master", - // "prover_version" : "eric/CERT-6172", // Performance tuning options below this line "solc_via_ir": true, "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", - // assetsMoreThanSupply "prover_args" : [ - "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -splitParallel true -splitParallelInitialDepth 2 -depth 15 -s [z3:arith1,yices:def] -mediumTimeout 2 -splitParallelTimelimit 7200" + "-smt_nonLinearArithmetic true", + "-adaptiveSolverConfig false", + "-splitParallel true" ], + // assetsMoreThanSupply + // "prover_args" : [ + // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -splitParallel true -splitParallelInitialDepth 2 -depth 15 -s [z3:arith1,yices:def] -mediumTimeout 2 -splitParallelTimelimit 7200" + // ], // "prover_args": [ // "-adaptiveSolverConfig false -depth 0 -smt_nonLinearArithmetic true" // ], diff --git a/certora/conf/healthStatus/BorrowingHealthStatus.conf b/certora/conf/healthStatus/BorrowingHealthStatus.conf index 4134ff18..1acc6bff 100644 --- a/certora/conf/healthStatus/BorrowingHealthStatus.conf +++ b/certora/conf/healthStatus/BorrowingHealthStatus.conf @@ -18,11 +18,11 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - // "accountsStayHealthy", - "accountsStayHealthy_strategy" + "accountsStayHealthy", + // "accountsStayHealthy_strategy" ], // "method" : "borrow(uint256,address)", - // "method" : "pullDebt(uint256, address)", + "method" : "pullDebt(uint256,address)", // "method" : "repayWithShares(uint256,address)", // "method" : "repay(uint256,address)", "build_cache": true, @@ -36,8 +36,12 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, + // "prover_args": [ + // " -smt_easy_LIA true" + // ], "prover_args": [ - " -smt_easy_LIA true" + "-timeoutCracker true" ], - "smt_timeout": "7200", + // "smt_timeout": "7200", + "smt_timeout": "28800", } \ No newline at end of file diff --git a/certora/conf/healthStatus/VaultHealthStatus.conf b/certora/conf/healthStatus/VaultHealthStatus.conf index cb41a892..f4128ae2 100644 --- a/certora/conf/healthStatus/VaultHealthStatus.conf +++ b/certora/conf/healthStatus/VaultHealthStatus.conf @@ -19,8 +19,10 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy_strategy" + // "accountsStayHealthy_strategy", + "accountsStayHealthy" ], + "method" : "redeem(uint256,address,address)", "build_cache": true, "prover_version": "master", "server" : "production", diff --git a/certora/harness/TokenHarness.sol b/certora/harness/TokenHarness.sol new file mode 100644 index 00000000..71ee5f53 --- /dev/null +++ b/certora/harness/TokenHarness.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import "../../src/EVault/modules/Token.sol"; +import "../../src/EVault/shared/types/Types.sol"; + +contract TokenHarness is TokenModule { + // for amount.toShares() + using TypesLib for uint256; + + constructor(Integrations memory integrations) Base(integrations) {} + + function transferFromInternalHarnessed(address from, address to, uint256 amount) public returns (bool) { + // This is similar to the body of Token.transferFrom + // when it gets its arguments from Token.transfer. + // It is not harnessed directly since Token.transferFrom is private + // and we want to avoid munging. + // This is used for the enforceCollateralTransfer function + Shares shares = amount.toShares(); + if (from == to) revert E_SelfTransfer(); + decreaseAllowance(from, from, shares); + transferBalance(from, to, shares); + return true; + } +} \ No newline at end of file diff --git a/certora/scripts/runERC4626RulesSeparatelySplitConfs.py b/certora/scripts/runERC4626RulesSeparatelySplitConfs.py new file mode 100644 index 00000000..1b25da5f --- /dev/null +++ b/certora/scripts/runERC4626RulesSeparatelySplitConfs.py @@ -0,0 +1,37 @@ +import argparse +import subprocess + +parser = argparse.ArgumentParser() +parser.add_argument('-M', '--batchMsg', metavar='M', type=str, nargs='?', + default='', + help='a message for all the jobs') + +rule_confs = { + "conversionOfZero": "BaseERC4626.conf", + "convertToAssetsWeakAdditivity": "BaseERC4626.conf", + "convertToSharesWeakAdditivity": "BaseERC4626.conf" , + "conversionWeakMonotonicity": "BaseERC4626.conf", + "conversionWeakIntegrity": "BaseERC4626.conf", + "convertToCorrectness": "BaseERC4626.conf", + "depositMonotonicity": "BaseERC4626.conf", + "zeroDepositZeroShares": "BaseERC4626.conf", + "assetsMoreThanSupply": "BaseERC4626.conf", + "noAssetsIfNoSupply": "BaseERC4626.conf", + "noSupplyIfNoAssets": "BaseERC4626.conf", + "totalSupplyIsSumOfBalances": "BaseERC4626.conf", + "totalsMonotonicity": "BaseERC4626.conf", + "underlyingCannotChange": "BaseERC4626.conf", + "dustFavorsTheHouse": "BaseERC4626.conf", + "vaultSolvency": "BaseERC4626.conf", + "redeemingAllValidity": "BaseERC4626.conf", + "contributingProducesShares": "BaseERC4626.conf", + "onlyContributionMethodsReduceAssets": "BaseERC4626.conf", + "reclaimingProducesAssets": "BaseERC4626.conf" +} + +for name in rule_confs.keys(): + args = parser.parse_args() + script = f"certora/conf/ERC4626Rules/{rule_confs[name]}" + command = f"certoraRun {script} --rule \"{name}\" --msg \"{name} : {args.batchMsg}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) \ No newline at end of file diff --git a/certora/scripts/runHealthStatusAllModules.py b/certora/scripts/runHealthStatusAllModules.py new file mode 100644 index 00000000..fe2c759c --- /dev/null +++ b/certora/scripts/runHealthStatusAllModules.py @@ -0,0 +1,28 @@ +import argparse +import subprocess + +parser = argparse.ArgumentParser() +parser.add_argument('-M', '--batchMsg', metavar='M', type=str, nargs='?', + default='', + help='a message for all the jobs') + +hs_confs = [ + # "BalanceForwarder", + # "Borrowing", + "Governance", + # "Initialize", + # "Liquidation", + # "Token", + # "Vault" +] + +for conf in hs_confs: + args = parser.parse_args() + script = f"certora/conf/healthStatus/{conf}HealthStatus.conf" + commands = [ + f"certoraRun {script} --msg \"{conf} : {args.batchMsg}\" --rule \"accountsStayHealthy\"", + f"certoraRun {script} --msg \"{conf} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\"" + ] + for command in commands: + print(f"runing {command}") + subprocess.run(command, shell=True) \ No newline at end of file From bf52c6d3d67555530b8130ff6441126710c732f3 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 17 Jun 2024 12:53:34 +0100 Subject: [PATCH 096/152] Some cleanup --- certora/conf/ERC4626Rules/BaseERC4626.conf | 75 --- .../conf/VaultERC4626-UpdatingSummary.conf | 62 --- certora/specs/BalanceForwarder.spec | 19 - certora/specs/ERC4626-NoUpdate.spec | 0 certora/specs/ERC4626-UpdatingSummary.spec | 11 - certora/specs/EvaultERC4626.spec | 480 ------------------ 6 files changed, 647 deletions(-) delete mode 100644 certora/conf/ERC4626Rules/BaseERC4626.conf delete mode 100644 certora/conf/VaultERC4626-UpdatingSummary.conf delete mode 100644 certora/specs/ERC4626-NoUpdate.spec delete mode 100644 certora/specs/ERC4626-UpdatingSummary.spec delete mode 100644 certora/specs/EvaultERC4626.spec diff --git a/certora/conf/ERC4626Rules/BaseERC4626.conf b/certora/conf/ERC4626Rules/BaseERC4626.conf deleted file mode 100644 index aa9a0c9e..00000000 --- a/certora/conf/ERC4626Rules/BaseERC4626.conf +++ /dev/null @@ -1,75 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "src/EVault/modules/Vault.sol", - "certora/helpers/DummyERC20A.sol", - "certora/helpers/DummyERC20B.sol", - "certora/harness/EVCHarness.sol", - "certora/harness/BaseHarness.sol", - "certora/harness/ERC4626Harness.sol", - ], - "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "msg": "Vault ERC4626", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - // "rule": [ - // // // "conversionWeakMonotonicity", - // // "assetsMoreThanSupply", - // // // "contributingProducesShares", - // // // "depositMonotonicity", - // // // "dustFavorsTheHouse", - // // // "noAssetsIfNoSupply", - // // // "noSupplyIfNoAssets", - // // // "supplyLessThanUnderlyingAsset", - // // // "onlyContributionMethodsReduceAssets", - // // // "reclaimingProducesAssets", - // // // "redeemingAllValidity", - // // // "totalsMonotonicity", - // // // "vaultSolvency" - // ], - // "method": "redeem(uint256,address,address)", - // "method": "withdraw(uint256,address,address)", - // "method" : "deposit(uint256,address)", - // "method" : "mint(uint256,address)", - "parametric_contracts": ["ERC4626Harness"], - "build_cache": true, - "prover_version" : "master", - // Performance tuning options below this line - "solc_via_ir": true, - "solc_optimize": "10000", - "optimistic_loop": true, - "loop_iter": "2", - // "prover_args" : [ - // "-smt_nonLinearArithmetic true", - // "-adaptiveSolverConfig false", - // "-splitParallel true" - // ], - // assetsMoreThanSupply - // "prover_args" : [ - // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -splitParallel true -splitParallelInitialDepth 2 -depth 15 -s [z3:arith1,yices:def] -mediumTimeout 2 -splitParallelTimelimit 7200" - // ], - // "prover_args": [ - // "-adaptiveSolverConfig false -depth 0 -smt_nonLinearArithmetic true" - // ], - // "prover_args": [ - // "-maxConcurrentTransforms INLINED_HOOKS:8", - // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 " - // ] - // "prover_args": [ - // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 -s [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" - // ] , - // old config - // "prover_args": [ - // "-smt_nonLinearArithmetic true", - // "-adaptiveSolverConfig false", - // "-solvers [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]", - // "-splitParallel true" - // ], - // "nondet_difficult_funcs": true, - // "smt_timeout": "7200" -} - diff --git a/certora/conf/VaultERC4626-UpdatingSummary.conf b/certora/conf/VaultERC4626-UpdatingSummary.conf deleted file mode 100644 index 18cccfc5..00000000 --- a/certora/conf/VaultERC4626-UpdatingSummary.conf +++ /dev/null @@ -1,62 +0,0 @@ -{ - "files": [ - // TODO do we need EVC for this spec? - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - // "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "src/EVault/modules/Vault.sol", - "certora/helpers/DummyERC20A.sol", - "certora/helpers/DummyERC20B.sol", - "certora/harness/BaseHarness.sol", - "certora/harness/ERC4626Harness.sol", - ], - // "link" : [ - // // "ERC4626Harness:evc=EthereumVaultConnector", - // "ERC4626Harness:underlying_asset=DummyERC20A" - // ], - "verify": "ERC4626Harness:certora/specs/ERC4626-UpdatingSummary.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "msg": "Vault ERC4626 with Updating Summary", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "rule": [ - "reclaimingProducesAssets", - "onlyContributionMethodsReduceAssets", - "redeemingAllValidity", - "dustFavorsTheHouse", - "underlyingCannotChange", - "zeroDepositZeroShares", - "convertToCorrectness", - "conversionOfZero" - ], - // "coverage_info" : "advanced", - // "method": "redeem(uint256,address,address)", - // "method": "withdraw(uint256,address,address)", - // "method" : "deposit(uint256,address)", - // "method" : "mint(uint256,address)", - "parametric_contracts": ["ERC4626Harness"], - "build_cache": true, - "prover_version" : "master", - // Performance tuning options below this line - "solc_via_ir": true, - "solc_optimize": "10000", - "optimistic_loop": true, - "loop_iter": "2", - // "prover_args": [ - // "-smt_nonLinearArithmetic true", - // "-adaptiveSolverConfig false", - // "-solvers [cvc5:nonlin{randomSeed=1},cvc5:nonlin{randomSeed=2},cvc5:nonlin{randomSeed=3},cvc5:nonlin{randomSeed=4},cvc5:nonlin{randomSeed=5},cvc5:nonlin{randomSeed=6},cvc5:nonlin{randomSeed=7},cvc5:nonlin{randomSeed=8},cvc5:nonlin{randomSeed=9},cvc5:nonlin{randomSeed=10}]", - // "-depth 0" - // ], - // old config - // "prover_args": [ - // "-smt_nonLinearArithmetic true", - // "-adaptiveSolverConfig false", - // "-solvers [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]", - // "-splitParallel true" - // ], - // "nondet_difficult_funcs": true, - // "smt_timeout": "7000" -} diff --git a/certora/specs/BalanceForwarder.spec b/certora/specs/BalanceForwarder.spec index 08ddb3f5..d46f947e 100644 --- a/certora/specs/BalanceForwarder.spec +++ b/certora/specs/BalanceForwarder.spec @@ -13,25 +13,6 @@ hook should be invoked with 0 as the balance of the account. */ import "Base.spec"; -// using EthereumVaultConnector as evc; - -// function actualCaller(env e) returns address { -// if(e.msg.sender == evc) { -// address onBehalf; -// bool unused; -// onBehalf, unused = evc.getCurrentOnBehalfOfAccount(e, 0); -// return onBehalf; -// } else { -// return e.msg.sender; -// } -// } - - -// NOTE: Unused currently -// ghost mapping(uint256 => bool) ghost_balanceForwarderFlag; -// function CVLIsBalanceForwarderEnabled(uint256 userStorage_data) returns bool { -// return ghost_balanceForwarderFlag[userStorage_data]; -// } //passing: // https://prover.certora.com/output/65266/e2a397f3bb864a9eaf4eefdfd35529bc?anonymousKey=aa5dace26320fee72d3611b84d337413ac48c2da diff --git a/certora/specs/ERC4626-NoUpdate.spec b/certora/specs/ERC4626-NoUpdate.spec deleted file mode 100644 index e69de29b..00000000 diff --git a/certora/specs/ERC4626-UpdatingSummary.spec b/certora/specs/ERC4626-UpdatingSummary.spec deleted file mode 100644 index cd0d6899..00000000 --- a/certora/specs/ERC4626-UpdatingSummary.spec +++ /dev/null @@ -1,11 +0,0 @@ - -// This provides a configuration for the VaultERC4626 rules -// that will use the `CVLLoadVault` summary which includes a model -// for the update for fees. - -import "./LoadVaultSummary.spec"; -import "./VaultERC4626.spec"; - -methods { - function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLLoadVault(e); -} \ No newline at end of file diff --git a/certora/specs/EvaultERC4626.spec b/certora/specs/EvaultERC4626.spec deleted file mode 100644 index 2b96f7d9..00000000 --- a/certora/specs/EvaultERC4626.spec +++ /dev/null @@ -1,480 +0,0 @@ -/* - * This is a specification file to formally verify BorrowSystem.sol - * smart contract using the Certora Prover. For more information, - * visit: https://www.certora.com/ - * - */ - - -// reference from the spec to additional contracts used in the verification - -// using DummyERC20A as ERC20a; -// using DummyERC20B as ERC20b; -using EVaultHarness as ERC20a; - -/* - Declaration of methods that are used in the rules. envfree indicate that - the method is not dependent on the environment (msg.value, msg.sender). - Methods that are not declared here are assumed to be dependent on env. -*/ -methods { - function name() external returns string envfree; - function symbol() external returns string envfree; - function decimals() external returns uint8 envfree; - function asset() external returns address envfree; - - function totalSupply() external returns uint256 envfree; - function balanceOf(address) external returns uint256 envfree; - // Not implemented by EVault - // function nonces(address) external returns uint256 envfree; - - function approve(address,uint256) external returns bool; - function deposit(uint256,address) external; - function mint(uint256,address) external; - function withdraw(uint256,address,address) external; - function redeem(uint256,address,address) external; - - function totalAssets() external returns uint256 envfree; - // Not implemented by EVault - // function userAssets(address) external returns uint256 envfree; - function convertToShares(uint256) external returns uint256 envfree; - function convertToAssets(uint256) external returns uint256 envfree; - function previewDeposit(uint256) external returns uint256 envfree; - function previewMint(uint256) external returns uint256 envfree; - function previewWithdraw(uint256) external returns uint256 envfree; - function previewRedeem(uint256) external returns uint256 envfree; - - function maxDeposit(address) external returns uint256 envfree; - function maxMint(address) external returns uint256 envfree; - function maxWithdraw(address) external returns uint256 envfree; - function maxRedeem(address) external returns uint256 envfree; - - function permit(address,address,uint256,uint256,uint8,bytes32,bytes32) external; - function DOMAIN_SEPARATOR() external returns bytes32; - - //// #ERC20 methods - function _.balanceOf(address) external => DISPATCHER(true); - function _.transfer(address,uint256) external => DISPATCHER(true); - function _.transferFrom(address,address,uint256) external => DISPATCHER(true); - - function ERC20a.balanceOf(address) external returns uint256 envfree; - function ERC20a.transferFrom(address,address,uint256) external returns bool; -} - - - -//////////////////////////////////////////////////////////////////////////////// -//// # asset To shares mathematical properties ///// -//////////////////////////////////////////////////////////////////////////////// - -rule conversionOfZero { - uint256 convertZeroShares = convertToAssets(0); - uint256 convertZeroAssets = convertToShares(0); - - assert convertZeroShares == 0, - "converting zero shares must return zero assets"; - assert convertZeroAssets == 0, - "converting zero assets must return zero shares"; -} - -rule convertToAssetsWeakAdditivity() { - uint256 sharesA; uint256 sharesB; - require sharesA + sharesB < max_uint128 - && convertToAssets(sharesA) + convertToAssets(sharesB) < to_mathint(max_uint256) - && convertToAssets(require_uint256(sharesA + sharesB)) < max_uint256; - assert convertToAssets(sharesA) + convertToAssets(sharesB) <= to_mathint(convertToAssets(require_uint256(sharesA + sharesB))), - "converting sharesA and sharesB to assets then summing them must yield a smaller or equal result to summing them then converting"; -} - -rule convertToSharesWeakAdditivity() { - uint256 assetsA; uint256 assetsB; - require assetsA + assetsB < max_uint128 - && convertToAssets(assetsA) + convertToAssets(assetsB) < to_mathint(max_uint256) - && convertToAssets(require_uint256(assetsA + assetsB)) < max_uint256; - assert convertToAssets(assetsA) + convertToAssets(assetsB) <= to_mathint(convertToAssets(require_uint256(assetsA + assetsB))), - "converting assetsA and assetsB to shares then summing them must yield a smaller or equal result to summing them then converting"; -} - -rule conversionWeakMonotonicity { - uint256 smallerShares; uint256 largerShares; - uint256 smallerAssets; uint256 largerAssets; - - assert smallerShares < largerShares => convertToAssets(smallerShares) <= convertToAssets(largerShares), - "converting more shares must yield equal or greater assets"; - assert smallerAssets < largerAssets => convertToShares(smallerAssets) <= convertToShares(largerAssets), - "converting more assets must yield equal or greater shares"; -} - -rule conversionWeakIntegrity() { - uint256 sharesOrAssets; - assert convertToShares(convertToAssets(sharesOrAssets)) <= sharesOrAssets, - "converting shares to assets then back to shares must return shares less than or equal to the original amount"; - assert convertToAssets(convertToShares(sharesOrAssets)) <= sharesOrAssets, - "converting assets to shares then back to assets must return assets less than or equal to the original amount"; -} - -rule convertToCorrectness(uint256 amount, uint256 shares) -{ - assert amount >= convertToAssets(convertToShares(amount)); - assert shares >= convertToShares(convertToAssets(shares)); -} - - -//////////////////////////////////////////////////////////////////////////////// -//// # Unit Test ///// -//////////////////////////////////////////////////////////////////////////////// - -rule depositMonotonicity() { - env e; storage start = lastStorage; - - uint256 smallerAssets; uint256 largerAssets; - address receiver; - require currentContract != e.msg.sender && currentContract != receiver; - - safeAssumptions(e, e.msg.sender, receiver); - - deposit(e, smallerAssets, receiver); - uint256 smallerShares = balanceOf(receiver) ; - - deposit(e, largerAssets, receiver) at start; - uint256 largerShares = balanceOf(receiver) ; - - assert smallerAssets < largerAssets => smallerShares <= largerShares, - "when supply tokens outnumber asset tokens, a larger deposit of assets must produce an equal or greater number of shares"; -} - - -rule zeroDepositZeroShares(uint assets, address receiver) -{ - env e; - - uint shares = deposit(e,assets, receiver); - - assert shares == 0 <=> assets == 0; -} - -//////////////////////////////////////////////////////////////////////////////// -//// # Valid State ///// -//////////////////////////////////////////////////////////////////////////////// - -invariant assetsMoreThanSupply() - totalAssets() >= totalSupply() - { - preserved with (env e) { - require e.msg.sender != currentContract; - address any; - safeAssumptions(e, any , e.msg.sender); - } - } - -invariant noAssetsIfNoSupply() - ( userAssets(currentContract) == 0 => totalSupply() == 0 ) && - ( totalAssets() == 0 => ( totalSupply() == 0 )) - - { - preserved with (env e) { - address any; - safeAssumptions(e, any, e.msg.sender); - } - } - -invariant noSupplyIfNoAssets() - noSupplyIfNoAssetsDef() // see defition in "helpers and miscellaneous" section - { - preserved with (env e) { - safeAssumptions(e, _, e.msg.sender); - } - } - - - -ghost mathint sumOfBalances { - init_state axiom sumOfBalances == 0; -} - -hook Sstore balanceOf[KEY address addy] uint256 newValue (uint256 oldValue) { - sumOfBalances = sumOfBalances + newValue - oldValue; -} - -hook Sload uint256 val balanceOf[KEY address addy] { - require sumOfBalances >= to_mathint(val); -} - -invariant totalSupplyIsSumOfBalances() - to_mathint(totalSupply()) == sumOfBalances; - - - -//////////////////////////////////////////////////////////////////////////////// -//// # State Transition ///// -//////////////////////////////////////////////////////////////////////////////// - - -rule totalsMonotonicity() { - method f; env e; calldataarg args; - require e.msg.sender != currentContract; - uint256 totalSupplyBefore = totalSupply(); - uint256 totalAssetsBefore = totalAssets(); - address receiver; - safeAssumptions(e, receiver, e.msg.sender); - callReceiverFunctions(f, e, receiver); - - uint256 totalSupplyAfter = totalSupply(); - uint256 totalAssetsAfter = totalAssets(); - - // possibly assert totalSupply and totalAssets must not change in opposite directions - assert totalSupplyBefore < totalSupplyAfter <=> totalAssetsBefore < totalAssetsAfter, - "if totalSupply changes by a larger amount, the corresponding change in totalAssets must remain the same or grow"; - assert totalSupplyAfter == totalSupplyBefore => totalAssetsBefore == totalAssetsAfter, - "equal size changes to totalSupply must yield equal size changes to totalAssets"; -} - -rule underlyingCannotChange() { - address originalAsset = asset(); - - method f; env e; calldataarg args; - f(e, args); - - address newAsset = asset(); - - assert originalAsset == newAsset, - "the underlying asset of a contract must not change"; -} - -//////////////////////////////////////////////////////////////////////////////// -//// # High Level ///// -//////////////////////////////////////////////////////////////////////////////// - -//// # This rules timeout - we will show how to deal with timeouts -/* rule totalAssetsOfUser(method f, address user ) { - env e; - calldataarg args; - safeAssumptions(e, e.msg.sender, user); - require user != currentContract; - mathint before = userAssets(user) + maxWithdraw(user); - - // need to ignore cases where user is msg.sender but someone else the receiver - address receiver; - require e.msg.sender != user; - uint256 assets; uint256 shares; - callFunctionsWithReceiverAndOwner(e, f, assets, shares, receiver, e.msg.sender); - mathint after = userAssets(user) + maxWithdraw(user); - assert after >= before; -} -*/ - -rule dustFavorsTheHouse(uint assetsIn ) -{ - env e; - - require e.msg.sender != currentContract; - safeAssumptions(e,e.msg.sender,e.msg.sender); - uint256 totalSupplyBefore = totalSupply(); - - uint balanceBefore = ERC20a.balanceOf(currentContract); - - uint shares = deposit(e,assetsIn, e.msg.sender); - uint assetsOut = redeem(e,shares,e.msg.sender,e.msg.sender); - - uint balanceAfter = ERC20a.balanceOf(currentContract); - - assert balanceAfter >= balanceBefore; -} - -//////////////////////////////////////////////////////////////////////////////// -//// # Risk Analysis ///////// -//////////////////////////////////////////////////////////////////////////////// - - -invariant vaultSolvency() - totalAssets() >= totalSupply() && userAssets(currentContract) >= totalAssets() { - preserved with(env e){ - requireInvariant totalSupplyIsSumOfBalances(); - require e.msg.sender != currentContract; - require currentContract != asset(); - } - } - - - -rule redeemingAllValidity() { - address owner; - uint256 shares; require shares == balanceOf(owner); - - env e; safeAssumptions(e, _, owner); - redeem(e, shares, _, owner); - uint256 ownerBalanceAfter = balanceOf(owner); - assert ownerBalanceAfter == 0; -} - - -//////////////////////////////////////////////////////////////////////////////// -//// # stakeholder properties (Risk Analysis ) ////////// -//////////////////////////////////////////////////////////////////////////////// - -rule contributingProducesShares(method f) -filtered { - f -> f.selector == sig:deposit(uint256,address).selector - || f.selector == sig:mint(uint256,address).selector -} -{ - env e; uint256 assets; uint256 shares; - address contributor; require contributor == e.msg.sender; - address receiver; - require currentContract != contributor - && currentContract != receiver; - - require previewDeposit(assets) + balanceOf(receiver) <= max_uint256; // safe assumption because call to _mint will revert if totalSupply += amount overflows - require shares + balanceOf(receiver) <= max_uint256; // same as above - - safeAssumptions(e, contributor, receiver); - - uint256 contributorAssetsBefore = userAssets(contributor); - uint256 receiverSharesBefore = balanceOf(receiver); - - callContributionMethods(e, f, assets, shares, receiver); - - uint256 contributorAssetsAfter = userAssets(contributor); - uint256 receiverSharesAfter = balanceOf(receiver); - - assert contributorAssetsBefore > contributorAssetsAfter <=> receiverSharesBefore < receiverSharesAfter, - "a contributor's assets must decrease if and only if the receiver's shares increase"; -} - -rule onlyContributionMethodsReduceAssets(method f) { - address user; require user != currentContract; - uint256 userAssetsBefore = userAssets(user); - - env e; calldataarg args; - safeAssumptions(e, user, _); - - f(e, args); - - uint256 userAssetsAfter = userAssets(user); - - assert userAssetsBefore > userAssetsAfter => - (f.selector == sig:deposit(uint256,address).selector || - f.selector == sig:mint(uint256,address).selector), - "a user's assets must not go down except on calls to contribution methods"; -} - -rule reclaimingProducesAssets(method f) -filtered { - f -> f.selector == sig:withdraw(uint256,address,address).selector - || f.selector == sig:redeem(uint256,address,address).selector -} -{ - env e; uint256 assets; uint256 shares; - address receiver; address owner; - require currentContract != e.msg.sender - && currentContract != receiver - && currentContract != owner; - - safeAssumptions(e, receiver, owner); - - uint256 ownerSharesBefore = balanceOf(owner); - uint256 receiverAssetsBefore = userAssets(receiver); - - callReclaimingMethods(e, f, assets, shares, receiver, owner); - - uint256 ownerSharesAfter = balanceOf(owner); - uint256 receiverAssetsAfter = userAssets(receiver); - - assert ownerSharesBefore > ownerSharesAfter <=> receiverAssetsBefore < receiverAssetsAfter, - "an owner's shares must decrease if and only if the receiver's assets increase"; -} - - - -//////////////////////////////////////////////////////////////////////////////// -//// # helpers and miscellaneous ////////// -//////////////////////////////////////////////////////////////////////////////// - -definition noSupplyIfNoAssetsDef() returns bool = - ( userAssets(currentContract) == 0 => totalSupply() == 0 ) && - ( totalAssets() == 0 => ( totalSupply() == 0 )); - -// definition noSupplyIfNoAssetsStrongerDef() returns bool = // fails for ERC4626BalanceOfHarness as explained in the readme -// ( userAssets(currentContract) == 0 => totalSupply() == 0 ) && -// ( totalAssets() == 0 <=> ( totalSupply() == 0 )); - - -function safeAssumptions(env e, address receiver, address owner) { - require currentContract != asset(); // Although this is not disallowed, we assume the contract's underlying asset is not the contract itself - requireInvariant totalSupplyIsSumOfBalances(); - requireInvariant vaultSolvency(); - requireInvariant noAssetsIfNoSupply(); - requireInvariant noSupplyIfNoAssets(); - requireInvariant assetsMoreThanSupply(); - - //// # Note : we don't want to use singleBalanceBounded and singleBalanceBounded invariants - /* requireInvariant sumOfBalancePairsBounded(receiver, owner ); - requireInvariant singleBalanceBounded(receiver); - requireInvariant singleBalanceBounded(owner); - */ - ///// # but, it safe to assume that a single balance is less than sum of balances - require ( (receiver != owner => balanceOf(owner) + balanceOf(receiver) <= to_mathint(totalSupply())) && - balanceOf(receiver) <= totalSupply() && - balanceOf(owner) <= totalSupply()); -} - - -// A helper function to set the receiver -function callReceiverFunctions(method f, env e, address receiver) { - uint256 amount; - if (f.selector == sig:deposit(uint256,address).selector) { - deposit(e, amount, receiver); - } else if (f.selector == sig:mint(uint256,address).selector) { - mint(e, amount, receiver); - } else if (f.selector == sig:withdraw(uint256,address,address).selector) { - address owner; - withdraw(e, amount, receiver, owner); - } else if (f.selector == sig:redeem(uint256,address,address).selector) { - address owner; - redeem(e, amount, receiver, owner); - } else { - calldataarg args; - f(e, args); - } -} - - -function callContributionMethods(env e, method f, uint256 assets, uint256 shares, address receiver) { - if (f.selector == sig:deposit(uint256,address).selector) { - deposit(e, assets, receiver); - } - if (f.selector == sig:mint(uint256,address).selector) { - mint(e, shares, receiver); - } -} - -function callReclaimingMethods(env e, method f, uint256 assets, uint256 shares, address receiver, address owner) { - if (f.selector == sig:withdraw(uint256,address,address).selector) { - withdraw(e, assets, receiver, owner); - } - if (f.selector == sig:redeem(uint256,address,address).selector) { - redeem(e, shares, receiver, owner); - } -} - -function callFunctionsWithReceiverAndOwner(env e, method f, uint256 assets, uint256 shares, address receiver, address owner) { - if (f.selector == sig:withdraw(uint256,address,address).selector) { - withdraw(e, assets, receiver, owner); - } - if (f.selector == sig:redeem(uint256,address,address).selector) { - redeem(e, shares, receiver, owner); - } - if (f.selector == sig:deposit(uint256,address).selector) { - deposit(e, assets, receiver); - } - if (f.selector == sig:mint(uint256,address).selector) { - mint(e, shares, receiver); - } - if (f.selector == sig:transferFrom(address,address,uint256).selector) { - transferFrom(e, owner, receiver, shares); - } - else { - calldataarg args; - f(e, args); - } -} From ffd05e5b59acd093d7ec81de60f84499481a6b52 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 17 Jun 2024 12:58:15 +0100 Subject: [PATCH 097/152] Deleting commented code in ERC4626 spec --- certora/conf/EVault/modules/Liquidation.conf | 3 - certora/conf/EVault/modules/RiskManager.conf | 4 - certora/specs/VaultERC4626.spec | 78 +------------------- 3 files changed, 1 insertion(+), 84 deletions(-) diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index 7d5674b9..d6dc71d0 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -17,9 +17,6 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - // "rule": [ - // "getCollateralValue_borrowing_lower" - // ], "parametric_contracts": ["LiquidationHarness"], "rule_sanity": "basic", "prover_version": "master", diff --git a/certora/conf/EVault/modules/RiskManager.conf b/certora/conf/EVault/modules/RiskManager.conf index bd7379db..29beeb4e 100644 --- a/certora/conf/EVault/modules/RiskManager.conf +++ b/certora/conf/EVault/modules/RiskManager.conf @@ -16,10 +16,6 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - // "rule": [ - // "ltv_borrowing_lower" - // // "liquidations_equal_for_one" - // ], "parametric_contracts": ["RiskManagerHarness"], "rule_sanity": "basic", "server": "production", diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index ce029fe5..beac6149 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -26,11 +26,6 @@ methods { function decimals() external returns uint8 envfree; function asset() external returns address envfree; - // function totalSupply() external returns uint256 envfree; - // function balanceOf(address) external returns uint256 envfree; //NOT ENVFREE - // Not implemented by EVault - // function nonces(address) external returns uint256 envfree; - function approve(address,uint256) external returns bool; function deposit(uint256,address) external; function mint(uint256,address) external; @@ -38,30 +33,11 @@ methods { function redeem(uint256,address,address) external; - // function totalAssets() external returns uint256 envfree; - // function userAssets(address) external returns uint256 envfree; - // function convertToShares(uint256) external returns uint256 envfree; - // function convertToAssets(uint256) external returns uint256 envfree; - // function previewDeposit(uint256) external returns uint256 envfree; - // function previewMint(uint256) external returns uint256 envfree; - // function previewWithdraw(uint256) external returns uint256 envfree; - // function previewRedeem(uint256) external returns uint256 envfree; - - // function maxDeposit(address) external returns uint256 envfree; - // function maxMint(address) external returns uint256 envfree; - // function maxWithdraw(address) external returns uint256 envfree; - // function maxRedeem(address) external returns uint256 envfree; - function permit(address,address,uint256,uint256,uint8,bytes32,bytes32) external; function DOMAIN_SEPARATOR() external returns bytes32; //// #ERC20 methods - // These are done in Base - // function _.balanceOf(address) external => DISPATCHER(true); - // function _.transfer(address,uint256) external => DISPATCHER(true); - // function _.transferFrom(address,address,uint256) external => DISPATCHER(true); - // function ERC20a.balanceOf(address) external returns uint256 envfree; // NOT ENVFREE function ERC20a.transferFrom(address,address,uint256) external returns bool; // not envfree @@ -286,15 +262,6 @@ hook Sload Vault.PackedUserSlot val currentContract.vaultStorage.users[KEY addre require sumOfBalances >= to_mathint(val); } -// hook Sstore balanceOf[KEY address addy] uint256 newValue (uint256 oldValue) { -// sumOfBalances = sumOfBalances + newValue - oldValue; -// } - - -// hook Sload uint256 val balanceOf[KEY address addy] { -// require sumOfBalances >= to_mathint(val); -// } - // passing: https://prover.certora.com/output/65266/de3636d287d2473294463c07263fc11e/?anonymousKey=ac8f74e6c5c1298f0954a21fafd41cccf32b9ffb invariant totalSupplyIsSumOfBalances(env e) // to_mathint(totalSupply(e)) == sumOfBalances + accumulatedFees(e); @@ -342,24 +309,6 @@ rule underlyingCannotChange() { //// # High Level ///// //////////////////////////////////////////////////////////////////////////////// -//// # This rules timeout - we will show how to deal with timeouts -/* rule totalAssetsOfUser(method f, address user ) { - env e; - calldataarg args; - safeAssumptions(e, e.msg.sender, user); - require user != currentContract; - mathint before = userAssets(user) + maxWithdraw(user); - - // need to ignore cases where user is msg.sender but someone else the receiver - address receiver; - require e.msg.sender != user; - uint256 assets; uint256 shares; - callFunctionsWithReceiverAndOwner(e, f, assets, shares, receiver, e.msg.sender); - mathint after = userAssets(user) + maxWithdraw(user); - assert after >= before; -} -*/ - // passing // run: https://prover.certora.com/output/65266/1912c053cdf8485087f2c050146c64aa/?anonymousKey=a12e3d573258a4d8136a19b612448a50f80b9a21 rule dustFavorsTheHouse(uint assetsIn ) @@ -370,13 +319,11 @@ rule dustFavorsTheHouse(uint assetsIn ) safeAssumptions(e,e.msg.sender,e.msg.sender); uint256 totalSupplyBefore = totalSupply(e); - // uint balanceBefore = ERC20a.balanceOf(e, currentContract); uint balanceBefore = currentContract.balanceOf(e, currentContract); uint shares = deposit(e,assetsIn, e.msg.sender); uint assetsOut = redeem(e,shares,e.msg.sender,e.msg.sender); - // uint balanceAfter = ERC20a.balanceOf(e, currentContract); uint balanceAfter = currentContract.balanceOf(e, currentContract); assert balanceAfter >= balanceBefore; } @@ -507,10 +454,6 @@ filtered { definition noSupplyIfNoAssetsDef(env e) returns bool = ( totalAssets(e) == 0 => ( totalSupply(e) == 0 )); -// definition noSupplyIfNoAssetsStrongerDef() returns bool = // fails for ERC4626BalanceOfHarness as explained in the readme -// ( userAssets(currentContract) == 0 => totalSupply() == 0 ) && -// ( totalAssets() == 0 <=> ( totalSupply() == 0 )); - function safeAssumptions(env e, address receiver, address owner) { require currentContract != asset(); // Although this is not disallowed, we assume the contract's underlying asset is not the contract itself @@ -521,10 +464,6 @@ function safeAssumptions(env e, address receiver, address owner) { requireInvariant assetsMoreThanSupply(e); //// # Note : we don't want to use singleBalanceBounded and singleBalanceBounded invariants - /* requireInvariant sumOfBalancePairsBounded(receiver, owner ); - requireInvariant singleBalanceBounded(receiver); - requireInvariant singleBalanceBounded(owner); - */ ///// # but, it safe to assume that a single balance is less than sum of balances require ( (receiver != owner => balanceOf(e, owner) + balanceOf(e, receiver) <= to_mathint(totalSupply(e))) && balanceOf(e, receiver) <= totalSupply(e) && @@ -597,19 +536,4 @@ rule sanity (method f) { calldataarg args; f(e, args); assert false; -} - -// rule vaultCacheSanity (method f) { -// env e; -// BaseHarness.VaultCache vaultCache; -// CVLInitVaultCache(e, vaultCache); -// assert false; -// } - -// rule totalSupplySanity { -// env e; -// BaseHarness.VaultCache vaultCache; -// CVLInitVaultCache(e, vaultCache); -// totalSupply(e); -// assert false; -// } \ No newline at end of file +} \ No newline at end of file From f03f1ac15b5e98da87efa11faa899cb3a7ad14b7 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 18 Jun 2024 12:55:17 +0100 Subject: [PATCH 098/152] Fix summary bug. split confs --- .../VaultERC4626-vaultSolvency-redeem.conf | 36 +++++++++++++++++++ .../VaultERC4626-vaultSolvency-withdraw.conf | 36 +++++++++++++++++++ certora/conf/VaultERC4626.conf | 8 ++--- certora/specs/VaultERC4626.spec | 20 +++++------ 4 files changed, 84 insertions(+), 16 deletions(-) create mode 100644 certora/conf/VaultERC4626-vaultSolvency-redeem.conf create mode 100644 certora/conf/VaultERC4626-vaultSolvency-withdraw.conf diff --git a/certora/conf/VaultERC4626-vaultSolvency-redeem.conf b/certora/conf/VaultERC4626-vaultSolvency-redeem.conf new file mode 100644 index 00000000..3373ca15 --- /dev/null +++ b/certora/conf/VaultERC4626-vaultSolvency-redeem.conf @@ -0,0 +1,36 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule": ["vaultSolvency"], + "method": "redeem(uint256,address,address)", + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "prover_args" : [ + "-smt_nonLinearArithmetic true", + "-adaptiveSolverConfig false", + "-splitParallel true" + ], + "smt_timeout": "7200", +} + diff --git a/certora/conf/VaultERC4626-vaultSolvency-withdraw.conf b/certora/conf/VaultERC4626-vaultSolvency-withdraw.conf new file mode 100644 index 00000000..88381145 --- /dev/null +++ b/certora/conf/VaultERC4626-vaultSolvency-withdraw.conf @@ -0,0 +1,36 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule": ["vaultSolvency"], + "method": "withdraw(uint256,address,address)", + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "prover_args" : [ + "-smt_nonLinearArithmetic true", + "-adaptiveSolverConfig false", + "-splitParallel true" + ], + "smt_timeout": "7200", +} + diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index e9688e0f..d3b78618 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -16,12 +16,12 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - // "rule": [ + "rule": [ // // // "conversionWeakMonotonicity", // // "assetsMoreThanSupply", - // // // "contributingProducesShares", + "contributingProducesShares", // // // "depositMonotonicity", - // // // "dustFavorsTheHouse", + // "dustFavorsTheHouse", // // // "noAssetsIfNoSupply", // // // "noSupplyIfNoAssets", // // // "supplyLessThanUnderlyingAsset", @@ -30,7 +30,7 @@ // // // "redeemingAllValidity", // // // "totalsMonotonicity", // // // "vaultSolvency" - // ], + ], // "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", // "method" : "deposit(uint256,address)", diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index beac6149..c777eb74 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -57,10 +57,10 @@ methods { // trySafeTransferFrom cannot be summarized as NONDET (due to return type // that includes bytes memory). So it is summarized as // DummyERC20a.transferFrom - function _.trySafeTransferFrom(address token, address from, address to, uint256 value) internal with (env e) => CVLTrySafeTransferFrom(e, from, to, value) expect (bool, bytes memory); + function _.trySafeTransferFrom(address token, address from, address to, uint256 value) internal with (env e) => CVLTrySafeTransferFrom(e, token,from, to, value) expect (bool, bytes memory); // safeTransferFrom is summarized as transferFrom // from DummyERC20a to avoid dealing with the low-level `call` - function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLTrySafeTransferFrom(e, from, to, value) expect (bool, bytes memory); + function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLSafeTransferFrom(e, token, from, to, value) expect void; function _.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal => NONDET; function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; // Type Conversions @@ -97,11 +97,14 @@ function CVLgetCurrentOnBehalfOfAccount(address addr) returns (address, bool) { persistent ghost CVLGetAccountOwner(address) returns address; // Summarize trySafeTransferFrom as DummyERC20 transferFrom -function CVLTrySafeTransferFrom(env e, address from, address to, uint256 value) returns (bool, bytes) { - bytes ret; // Ideally bytes("") if there is a way to do this - return (ERC20a.transferFrom(e, from, to, value), ret); +function CVLSafeTransferFrom(env e, address token, address from, address to, uint256 value) { + ERC20a.transferFrom(e, from, to, value); } +function CVLTrySafeTransferFrom(env e, address token, address from, address to, uint256 value) returns (bool, bytes) { + bytes ret; + return (ERC20a.transferFrom(e, from, to, value), ret); +} //////////////////////////////////////////////////////////////////////////////// //// # asset To shares mathematical properties ///// @@ -529,11 +532,4 @@ function callFunctionsWithReceiverAndOwner(env e, method f, uint256 assets, uint calldataarg args; f(e, args); } -} - -rule sanity (method f) { - env e; - calldataarg args; - f(e, args); - assert false; } \ No newline at end of file From f442fc9560442ac27687c1ac7da4f35b10d25e7e Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 25 Jun 2024 11:42:33 +0100 Subject: [PATCH 099/152] Holy grail for liquidate --- .../healthStatus/BorrowingHealthStatus.conf | 18 +- .../healthStatus/LiquidateHealthStatus.conf | 41 +++ .../healthStatus/LiquidationHSHarness.sol | 5 + certora/specs/LiquidateHealthStatus.spec | 316 ++++++++++++++++++ certora/specs/Liquidation.spec | 27 +- 5 files changed, 372 insertions(+), 35 deletions(-) create mode 100644 certora/conf/healthStatus/LiquidateHealthStatus.conf create mode 100644 certora/specs/LiquidateHealthStatus.spec diff --git a/certora/conf/healthStatus/BorrowingHealthStatus.conf b/certora/conf/healthStatus/BorrowingHealthStatus.conf index 1acc6bff..9d93ae73 100644 --- a/certora/conf/healthStatus/BorrowingHealthStatus.conf +++ b/certora/conf/healthStatus/BorrowingHealthStatus.conf @@ -18,12 +18,11 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy", - // "accountsStayHealthy_strategy" + "accountsStayHealthy_strategy" ], // "method" : "borrow(uint256,address)", - "method" : "pullDebt(uint256,address)", - // "method" : "repayWithShares(uint256,address)", + // "method" : "pullDebt(uint256,address)", + "method" : "repayWithShares(uint256,address)", // "method" : "repay(uint256,address)", "build_cache": true, "prover_version": "master", @@ -36,12 +35,13 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, - // "prover_args": [ - // " -smt_easy_LIA true" - // ], "prover_args": [ - "-timeoutCracker true" + " -timeoutCracker true" ], + "smt_timeout": "7200", + // "prover_args": [ + // "-timeoutCracker true" + // ], // "smt_timeout": "7200", - "smt_timeout": "28800", + // "smt_timeout": "28800", } \ No newline at end of file diff --git a/certora/conf/healthStatus/LiquidateHealthStatus.conf b/certora/conf/healthStatus/LiquidateHealthStatus.conf new file mode 100644 index 00000000..1bd7f2d4 --- /dev/null +++ b/certora/conf/healthStatus/LiquidateHealthStatus.conf @@ -0,0 +1,41 @@ +{ + "files": [ + "certora/harness/EVCHarness.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "certora/helpers/DummyERC20A.sol", + "src/EVault/modules/Vault.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/TokenHarness.sol", + "certora/harness/healthStatus/LiquidationHSHarness.sol", + ], + "link": [ + "LiquidationHSHarness:evc=EVCHarness", + ], + "verify": "LiquidationHSHarness:certora/specs/LiquidateHealthStatus.spec", + "solc": "solc8.23", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule" : [ + // "liquidateAccountsStayHealthy_liquidator_no_debt_socialization", + "liquidateAccountsStayHealthy_liquidator_with_debt_socialization", + // "liquidateAccountsStayHealthy_not_violator" + ], + "method":"liquidate(address,address,uint256,uint256)", + "build_cache": true, + "prover_version": "master", + "server" : "production", + "parametric_contracts": ["LiquidationHSHarness"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, + "prover_args": [ + "-smt_easy_LIA true" + ], + "smt_timeout": "7200", +} \ No newline at end of file diff --git a/certora/harness/healthStatus/LiquidationHSHarness.sol b/certora/harness/healthStatus/LiquidationHSHarness.sol index 797e9ebd..42617366 100644 --- a/certora/harness/healthStatus/LiquidationHSHarness.sol +++ b/certora/harness/healthStatus/LiquidationHSHarness.sol @@ -11,4 +11,9 @@ import "../../../src/EVault/modules/Liquidation.sol"; contract LiquidationHSHarness is LiquidationModule, RiskManagerModule, AbstractBaseHarness { constructor(Integrations memory integrations) Base(integrations) {} + + function hasDebtSocialization() external returns (bool) { + VaultCache memory vaultCache = loadVault(); + return vaultCache.configFlags.isNotSet(CFG_DONT_SOCIALIZE_DEBT); + } } \ No newline at end of file diff --git a/certora/specs/LiquidateHealthStatus.spec b/certora/specs/LiquidateHealthStatus.spec new file mode 100644 index 00000000..9bcb2212 --- /dev/null +++ b/certora/specs/LiquidateHealthStatus.spec @@ -0,0 +1,316 @@ +import "Base.spec"; +import "LoadVaultSummary.spec"; +using DummyERC20A as ERC20a; +using TokenHarness as EToken; // Used to assume collaterals are ETokens + +methods { + function checkAccountMagicValue() external returns (bytes4) envfree; + // healthStatusCheck reverts unless this is true. We assume it's true + // approximate the real situation where these checks get triggered + // by the EVC before which this flag will be set. + function EVCHarness.areChecksInProgress() external returns bool => CVLAreChecksInProgress(); + // unresolved calls that havoc all contracts + function _.isHookTarget() external => NONDET; + function _.invokeHookTarget(address caller) internal => NONDET; + function _.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal => NONDET; + function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; + function _.emitTransfer(address from, address to, uint256 value) external => NONDET; + function EVCHarness.disableController(address account) external => NONDET; + function _.computeInterestRate(address vault, uint256 cash, uint256 borrows) external => NONDET; + function _.onFlashLoan(bytes data) external => NONDET; + // function _.computeInterestRate(BaseHarness.VaultCache memory vaultCache) internal => NONDET; + + // Harness + function LiquidationHSHarness.hasDebtSocialization() external returns (bool) envfree; + + // EVC + function _.requireVaultStatusCheck() external => DISPATCHER(true); + function _.requireAccountAndVaultStatusCheck(address) external => DISPATCHER(true); + + // Summaries + function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLSafeTransferFrom(e, token, from, to, value) expect void; + function _.enforceCollateralTransfer(address collateral, uint256 amount, + address from, address receiver) internal with (env e) => + CVLEnforceCollateralTransfer(e, collateral, amount, from, receiver) expect void; + // To deal with changes between LTV values: + // function _.getLTV(address collateral, bool liquidation) internal => CVLGetLTV(collateral, liquidation) expect (BaseHarness.ConfigAmount); + // We can't handle the low-level call in + // EthereumVaultConnector.checkAccountStatusInternal + // and so reroute it to RiskManager's status check with this summary. + function EthereumVaultConnector.checkAccountStatusInternal(address account) internal returns (bool, bytes memory) with (env e) => + CVLCheckAccountStatusInternal(e, account); + function EthereumVaultConnector.checkVaultStatusInternal(address vault) internal returns (bool, bytes memory) with(env e) => + CVLCheckVaultStatusInternal(e); + + function _.EVCRequireStatusChecks(address account) internal => + EVCRequireStatusChecksCVL(account) expect void; +} + +//----------------------------------------------------------------------------- +// Summaries and Ghost State +//----------------------------------------------------------------------------- + +persistent ghost address accountToCheckGhost; +function EVCRequireStatusChecksCVL(address account) { + accountToCheckGhost = account; +} + +// We summarize EthereumVaultConnector.checkAccountStatusInternal +// because we need to direct the low-level call to RiskManager. +// checkAccountStatus and this linking doesn't happen automatically +function CVLCheckAccountStatusInternalBool(env e, address account) returns bool { + address[] collaterals = evc.getCollaterals(e, account); + checkAccountStatus@withrevert(e, account, collaterals); + return !lastReverted; +} + +function CVLCheckAccountStatusInternal(env e, address account) returns (bool, bytes) { + return (CVLCheckAccountStatusInternalBool(e, account), + checkAccountMagicValueMemory(e)); +} + +function CVLCheckVaultStatusInternalBool(env e) returns bool { + checkVaultStatus@withrevert(e); + return !lastReverted; +} + +function CVLCheckVaultStatusInternal(env e) returns (bool, bytes) { + return (CVLCheckVaultStatusInternalBool(e), + checkVaultMagicValueMemory(e)); +} + +function CVLAreChecksInProgress() returns bool { + return true; +} + +function CVLSafeTransferFrom(env e, address token, address from, address to, uint256 value) { + if (token == ERC20a) { + ERC20a.transferFrom(e, from, to, value); + } else if (token == EToken) { + EToken.transferFrom(e, from, to, value); + } +} + +/* +* The prover struggles to reason about the low-level call operations involved +* in the real EVCClient.enforceControlCollateral function, so we need +* to emulate the real behavior here. Here's how it works in the real code: +* - EVCClient calls evc.controlCollateral passing a call to `transfer(receiver, amount)` along with the collateral and from addresses +* - In controlCollateral the from address is used to set the onBehalfOfAccount +* and some authentication is done +* - After this, callWithContextInternal invokes the transfer function +* on the collateral address +* - Collaterals in the EVK must all be Token.sol and token's transfer +* implementation calls initOperation which enqueues an account status +* check on the EVC for the onBehalfOfAccount it also gets from EVC. +* Because onBehalfOfAccount was set to the from address in callWithContextInternal this status check is for the from account +* To emulate this, we: +* - explicitly call EToken.transferFrom using the expected addresses +* - enqueue a status check on the evc for the "from" address +*/ +// Because calling to requireAccountStatusCheck on EVC is expensive +// for the prover, instead assign which account gets checked to a ghost +persistent ghost address collateralTransferCheckedAccount; +function CVLEnforceCollateralTransfer(env e, address collateral, uint256 amount, address from, address receiver) { + // evc.requireAccountStatusCheck(e, from); + collateralTransferCheckedAccount = from; + EToken.transferFromInternalHarnessed(e, from, receiver, amount); +} + +//----------------------------------------------------------------------------- +// Rules +//----------------------------------------------------------------------------- +/* +For Liquidation.liquidate we need to split this rule into cases: + - account checked != liquidator and account checked != violator + - account checked == liquidator and account checked != violator and: + - debt socialization disabled + - debt socialization enabled +These cases are handled separately: + - account checked == violator: + if this account was healthy before the call does nothing. This is not + only easy to see manually but we prove this in + checkLiquidation.healthy() in Liquidation.sol + - account checked == violator: + In this case the call reverts which is easy to check but also + proved in liquidate_mustRevert +*/ + +rule liquidateAccountsStayHealthy_liquidator_no_debt_socialization { + env e; + address account; + address[] collaterals = evc.getCollaterals(e, account); + require collaterals.length == 2; // loop bound + require oracleAddress != 0; + // Vault cannot be a user of itself + require account != currentContract; + // Vault should not be used as a collateral + require collaterals[0] != currentContract; + require collaterals[1] != currentContract; + // Collaterals must be ETokens + require collaterals[0] == EToken; + require collaterals[1] == EToken; + // not sure the following 4 are really needed + require account != erc20; + require account != oracleAddress; + require account != evc; + require account != unitOfAccount; + + require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[0])); + require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[1])); + + address violator; + address collateral; + uint256 repayAssets; + uint256 minYieldBalance; + + // disable debt socialization + require !hasDebtSocialization(); + + // initialize checked accounts to 0 + require accountToCheckGhost == 0; // account checked in initialize + require collateralTransferCheckedAccount == 0; + + // account eq liquidator case + require collateral == collaterals[0] || collateral == collaterals[1]; + address liquidator = actualCaller(e); + require account == liquidator; + require violator != liquidator; + + bool healthyBefore = checkLiquidityReturning(e, account, collaterals); + currentContract.liquidate(e, violator, collateral, repayAssets, minYieldBalance); + + // replace the real call path involving the EVC calling back into the + // vault with a direct call on checkAccountStatus from the vault + + if(accountToCheckGhost != 0) { + currentContract.checkAccountStatus(e, accountToCheckGhost, collaterals); + } + if(collateralTransferCheckedAccount != 0) { + currentContract.checkAccountStatus(e, collateralTransferCheckedAccount, collaterals); + } + + bool healthyAfter = checkLiquidityReturning(e, account, collaterals); + assert healthyBefore => healthyAfter; +} + +rule liquidateAccountsStayHealthy_liquidator_with_debt_socialization { + env e; + address account; + address[] collaterals = evc.getCollaterals(e, account); + require collaterals.length == 2; // loop bound + require oracleAddress != 0; + // Vault cannot be a user of itself + require account != currentContract; + // Vault should not be used as a collateral + require collaterals[0] != currentContract; + require collaterals[1] != currentContract; + // Collaterals must be ETokens + require collaterals[0] == EToken; + require collaterals[1] == EToken; + // not sure the following 4 are really needed + require account != erc20; + require account != oracleAddress; + require account != evc; + require account != unitOfAccount; + + require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[0])); + require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[1])); + + address violator; + address collateral; + uint256 repayAssets; + uint256 minYieldBalance; + + // enable debt socialization + require hasDebtSocialization(); + + // initialize checked accounts to 0 + require accountToCheckGhost == 0; // account checked in initialize + require collateralTransferCheckedAccount == 0; + + // account eq liquidator case + require collateral == collaterals[0] || collateral == collaterals[1]; + address liquidator = actualCaller(e); + require account == liquidator; + require violator != liquidator; + + bool healthyBefore = checkLiquidityReturning(e, account, collaterals); + currentContract.liquidate(e, violator, collateral, repayAssets, minYieldBalance); + + // replace the real call path involving the EVC calling back into the + // vault with a direct call on checkAccountStatus from the vault + + if(accountToCheckGhost != 0) { + currentContract.checkAccountStatus(e, accountToCheckGhost, collaterals); + } + if(collateralTransferCheckedAccount != 0) { + currentContract.checkAccountStatus(e, collateralTransferCheckedAccount, collaterals); + } + + bool healthyAfter = checkLiquidityReturning(e, account, collaterals); + assert healthyBefore => healthyAfter; +} + + +rule liquidateAccountsStayHealthy_not_violator { + env e; + address account; + address[] collaterals = evc.getCollaterals(e, account); + require collaterals.length == 2; // loop bound + require oracleAddress != 0; + // Vault cannot be a user of itself + require account != currentContract; + // Vault should not be used as a collateral + require collaterals[0] != currentContract; + require collaterals[1] != currentContract; + // Collaterals must be ETokens + require collaterals[0] == EToken; + require collaterals[1] == EToken; + // not sure the following 4 are really needed + require account != erc20; + require account != oracleAddress; + require account != evc; + require account != unitOfAccount ; + + require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[0])); + require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[1])); + + address violator; + address collateral; + uint256 repayAssets; + uint256 minYieldBalance; + + address liquidator = actualCaller(e); + + // initialize checked accounts to 0 + require accountToCheckGhost == 0; // account checked in initialize + require collateralTransferCheckedAccount == 0; + + // account NE violator case + require account != violator; + require liquidator != violator; + require account != liquidator; + + bool healthyBefore = checkLiquidityReturning(e, account, collaterals); + currentContract.liquidate(e, violator, collateral, repayAssets, minYieldBalance); + // The only way to call a vault funciton is through EVC's call, batch, + // or permit. During all of these status checks are deferred and at the end + // these call restoreExecutionContext which triggers the deferred checks. + // Replace the real call path involving the EVC calling back into the + // vault with a direct call on checkAccountStatus from the vault. + // (For the not_violator / not liquidator case, we can also directly + // call evc.checkStatusAll rather than using these ghosts and + // the direct call on checkAccountStatus, but for the liquidator case + // this will drop performance enough to hit a timeout) + + if(accountToCheckGhost != 0) { + currentContract.checkAccountStatus(e, accountToCheckGhost, collaterals); + } + if(collateralTransferCheckedAccount != 0) { + currentContract.checkAccountStatus(e, collateralTransferCheckedAccount, collaterals); + } + + bool healthyAfter = checkLiquidityReturning(e, account, collaterals); + assert healthyBefore => healthyAfter; +} \ No newline at end of file diff --git a/certora/specs/Liquidation.spec b/certora/specs/Liquidation.spec index 1df13a79..65b1b386 100644 --- a/certora/specs/Liquidation.spec +++ b/certora/specs/Liquidation.spec @@ -57,29 +57,6 @@ rule checkLiquidation_healthy() { assert maxYield == 0; } -// counterexample -rule checkLiquidation_maxYieldGreater { - env e; - address liquidator; - address violator; - address collateral; - uint256 maxRepay; - uint256 maxYield; - - uint256 collateralValue; - uint256 liabilityValue; - (collateralValue, liabilityValue) = - calculateLiquidityExternal(e, violator); - - require oracleAddress != 0; - require collateralValue > 0; - require liabilityValue > 0; - require collateralValue < liabilityValue; - - (maxRepay, maxYield) = checkLiquidation(e, liquidator, violator, collateral); - assert maxRepay > 0 => maxRepay <= maxYield; -} - // passing rule checkLiquidation_mustRevert { env e; @@ -147,7 +124,7 @@ rule calculateLiquidation_setViolator { assert violator != liquidator; } -// formerly passing but broke. must fix +// passed rule liquidate_mustRevert { env e; address violator; @@ -165,8 +142,6 @@ rule liquidate_mustRevert { bool oracleConfigured = vaultCacheOracleConfigured(e); liquidate(e, violator, collateral, repayAssets, minYieldBalance); - // TODO liquidate operation not disabled - // TODO amount of collateral to be seized is less than the desired amount of assert !selfLiquidation; assert recognizedCollateral; assert enabledCollateral; From a462c1eda4d23dd03aca21085f49b51d3950d9d6 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 25 Jun 2024 12:08:41 +0100 Subject: [PATCH 100/152] add run links to Liquidation health status --- .../VaultERC4626-vaultSolvency-redeem.conf | 5 +- .../VaultERC4626-vaultSolvency-withdraw.conf | 5 +- certora/conf/VaultERC4626.conf | 63 +++++++------------ certora/specs/LiquidateHealthStatus.spec | 4 +- 4 files changed, 28 insertions(+), 49 deletions(-) diff --git a/certora/conf/VaultERC4626-vaultSolvency-redeem.conf b/certora/conf/VaultERC4626-vaultSolvency-redeem.conf index 3373ca15..ea753642 100644 --- a/certora/conf/VaultERC4626-vaultSolvency-redeem.conf +++ b/certora/conf/VaultERC4626-vaultSolvency-redeem.conf @@ -27,9 +27,8 @@ "optimistic_loop": true, "loop_iter": "2", "prover_args" : [ - "-smt_nonLinearArithmetic true", - "-adaptiveSolverConfig false", - "-splitParallel true" + "-smt_nonLinearArithmetic false", + "-timeoutCracker true", ], "smt_timeout": "7200", } diff --git a/certora/conf/VaultERC4626-vaultSolvency-withdraw.conf b/certora/conf/VaultERC4626-vaultSolvency-withdraw.conf index 88381145..149c7849 100644 --- a/certora/conf/VaultERC4626-vaultSolvency-withdraw.conf +++ b/certora/conf/VaultERC4626-vaultSolvency-withdraw.conf @@ -27,9 +27,8 @@ "optimistic_loop": true, "loop_iter": "2", "prover_args" : [ - "-smt_nonLinearArithmetic true", - "-adaptiveSolverConfig false", - "-splitParallel true" + "-smt_nonLinearArithmetic false", + "-timeoutCracker true", ], "smt_timeout": "7200", } diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index d3b78618..f3cae373 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -16,60 +16,39 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - "rule": [ - // // // "conversionWeakMonotonicity", - // // "assetsMoreThanSupply", - "contributingProducesShares", - // // // "depositMonotonicity", - // "dustFavorsTheHouse", - // // // "noAssetsIfNoSupply", - // // // "noSupplyIfNoAssets", - // // // "supplyLessThanUnderlyingAsset", - // // // "onlyContributionMethodsReduceAssets", - // // // "reclaimingProducesAssets", - // // // "redeemingAllValidity", - // // // "totalsMonotonicity", - // // // "vaultSolvency" - ], + // "rule": [ + // // // // "conversionWeakMonotonicity", + // // // "assetsMoreThanSupply", + // // "contributingProducesShares", + // // // // "depositMonotonicity", + // // "dustFavorsTheHouse", + // // // // "noAssetsIfNoSupply", + // // // // "noSupplyIfNoAssets", + // // // // "supplyLessThanUnderlyingAsset", + // "onlyContributionMethodsReduceAssets", + // // // // "reclaimingProducesAssets", + // // // // "redeemingAllValidity", + // // // // "totalsMonotonicity", + // // // // "vaultSolvency" + // ], // "method": "redeem(uint256,address,address)", // "method": "withdraw(uint256,address,address)", // "method" : "deposit(uint256,address)", // "method" : "mint(uint256,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, - "prover_version" : "master", + "prover_version" : "yuvalbd/CERT-6495", // Performance tuning options below this line "solc_via_ir": true, "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", - "prover_args" : [ - "-smt_nonLinearArithmetic true", - "-adaptiveSolverConfig false", - "-splitParallel true" - ], - // assetsMoreThanSupply // "prover_args" : [ - // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -splitParallel true -splitParallelInitialDepth 2 -depth 15 -s [z3:arith1,yices:def] -mediumTimeout 2 -splitParallelTimelimit 7200" - // ], - // "prover_args": [ - // "-adaptiveSolverConfig false -depth 0 -smt_nonLinearArithmetic true" + // "-smt_nonLinearArithmetic true", + // "-adaptiveSolverConfig false", + // "-splitParallel true" // ], - // "prover_args": [ - // "-maxConcurrentTransforms INLINED_HOOKS:8", - // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 " - // ] - // "prover_args": [ - // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 -s [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" - // ] , - // old config - // "prover_args": [ - // "-smt_nonLinearArithmetic true", - // "-adaptiveSolverConfig false", - // "-solvers [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]", - // "-splitParallel true" - // ], - // "nondet_difficult_funcs": true, - // "smt_timeout": "7200" + "disable_auto_cache_key_gen": true, + "fe_version": "latest", } diff --git a/certora/specs/LiquidateHealthStatus.spec b/certora/specs/LiquidateHealthStatus.spec index 9bcb2212..4ff24c13 100644 --- a/certora/specs/LiquidateHealthStatus.spec +++ b/certora/specs/LiquidateHealthStatus.spec @@ -136,6 +136,7 @@ These cases are handled separately: proved in liquidate_mustRevert */ +// passing: https://prover.certora.com/output/65266/132c942ca2a2463b84e15b77becdfa11/?anonymousKey=431faa27dd7b649306de7e37067b7a75e57271f8 rule liquidateAccountsStayHealthy_liquidator_no_debt_socialization { env e; address account; @@ -194,6 +195,7 @@ rule liquidateAccountsStayHealthy_liquidator_no_debt_socialization { assert healthyBefore => healthyAfter; } +// passing: https://prover.certora.com/output/65266/2d955907619c4c748e82791a3bb5843e/?anonymousKey=dfb93c10b2311ab0f99ffaf551bcd1fc4b7447b0 rule liquidateAccountsStayHealthy_liquidator_with_debt_socialization { env e; address account; @@ -252,7 +254,7 @@ rule liquidateAccountsStayHealthy_liquidator_with_debt_socialization { assert healthyBefore => healthyAfter; } - +// passing: https://prover.certora.com/output/65266/83941c4b1c3448a6bd56c3edebf44ced/?anonymousKey=11866ef668f148318b2bea213560da6a5b6df937 rule liquidateAccountsStayHealthy_not_violator { env e; address account; From fda76d2a347076e96e20d5afd77951a2d5028026 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 26 Jun 2024 11:03:44 +0100 Subject: [PATCH 101/152] Fix comment --- certora/specs/LiquidateHealthStatus.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/certora/specs/LiquidateHealthStatus.spec b/certora/specs/LiquidateHealthStatus.spec index 4ff24c13..0d9e2ea9 100644 --- a/certora/specs/LiquidateHealthStatus.spec +++ b/certora/specs/LiquidateHealthStatus.spec @@ -130,10 +130,10 @@ These cases are handled separately: - account checked == violator: if this account was healthy before the call does nothing. This is not only easy to see manually but we prove this in - checkLiquidation.healthy() in Liquidation.sol + checkLiquidation.healthy() in Liquidation.spec - account checked == violator: In this case the call reverts which is easy to check but also - proved in liquidate_mustRevert + proved in liquidate_mustRevert in Liquidation.spec */ // passing: https://prover.certora.com/output/65266/132c942ca2a2463b84e15b77becdfa11/?anonymousKey=431faa27dd7b649306de7e37067b7a75e57271f8 From 3f542d15278451a605eb90486cee6f13e5eb65d0 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 26 Jun 2024 11:06:31 +0100 Subject: [PATCH 102/152] fix comment again --- certora/specs/LiquidateHealthStatus.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certora/specs/LiquidateHealthStatus.spec b/certora/specs/LiquidateHealthStatus.spec index 0d9e2ea9..1d680a97 100644 --- a/certora/specs/LiquidateHealthStatus.spec +++ b/certora/specs/LiquidateHealthStatus.spec @@ -131,7 +131,7 @@ These cases are handled separately: if this account was healthy before the call does nothing. This is not only easy to see manually but we prove this in checkLiquidation.healthy() in Liquidation.spec - - account checked == violator: + - liquidator == violator: In this case the call reverts which is easy to check but also proved in liquidate_mustRevert in Liquidation.spec */ From 911c60878bc366725934e1ebdbf1aa4596c1c56c Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 27 Jun 2024 15:52:52 +0100 Subject: [PATCH 103/152] minor cleanup --- certora/specs/HealthStatusInvariant.spec | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index ac245048..6238f75f 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -18,7 +18,6 @@ methods { function EVCHarness.disableController(address account) external => NONDET; function _.computeInterestRate(address vault, uint256 cash, uint256 borrows) external => NONDET; function _.onFlashLoan(bytes data) external => NONDET; - // function _.computeInterestRate(BaseHarness.VaultCache memory vaultCache) internal => NONDET; // EVC function _.requireVaultStatusCheck() external => DISPATCHER(true); @@ -29,8 +28,6 @@ methods { function _.enforceCollateralTransfer(address collateral, uint256 amount, address from, address receiver) internal with (env e) => CVLEnforceCollateralTransfer(e, collateral, amount, from, receiver) expect void; - // To deal with changes between LTV values: - // function _.getLTV(address collateral, bool liquidation) internal => CVLGetLTV(collateral, liquidation) expect (BaseHarness.ConfigAmount); // We can't handle the low-level call in // EthereumVaultConnector.checkAccountStatusInternal // and so reroute it to RiskManager's status check with this summary. @@ -40,13 +37,6 @@ methods { CVLCheckVaultStatusInternal(e); } -// TODO ideally delete this if it is really not needed. -// persistent ghost uint16 ghost_ltv; -// function CVLGetLTV(address collateral, bool liquidation) returns uint16 { -// require ghost_ltv > 0; -// return ghost_ltv; -// } - // We summarize EthereumVaultConnector.checkAccountStatusInternal // because we need to direct the low-level call to RiskManager. // checkAccountStatus and this linking doesn't happen automatically From f2b555bf47550701e352bb0e33ecaeabc6c6fd2f Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 28 Jun 2024 12:40:24 +0100 Subject: [PATCH 104/152] try fixing submodule commits --- lib/ethereum-vault-connector | 2 +- lib/forge-std | 2 +- lib/openzeppelin-contracts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ethereum-vault-connector b/lib/ethereum-vault-connector index 0229f62f..1601566c 160000 --- a/lib/ethereum-vault-connector +++ b/lib/ethereum-vault-connector @@ -1 +1 @@ -Subproject commit 0229f62f92856201e1f33bee9e59daf68938ba34 +Subproject commit 1601566c2183ee9e9d0e3197d8f5de2e94371517 diff --git a/lib/forge-std b/lib/forge-std index 1d9650e9..75b3fcf0 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 1d9650e951204a0ddce9ff89c32f1997984cef4d +Subproject commit 75b3fcf052cc7886327e4c2eac3d1a1f36942b41 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index e682c7e5..9af280dc 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit e682c7e5b5b347662868b9c8378a694626406c01 +Subproject commit 9af280dc4b45ee5bda96ba47ff829b407eaab67e From 9945a5fa65b7835b32122240d7bc0ff27e947d4b Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 28 Jun 2024 15:20:18 +0100 Subject: [PATCH 105/152] Try fixing lib/forge-std commit --- lib/forge-std | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/forge-std b/lib/forge-std index 75b3fcf0..b6a506db 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 75b3fcf052cc7886327e4c2eac3d1a1f36942b41 +Subproject commit b6a506db2262cad5ff982a87789ee6d1558ec861 From b49cd69363a01ba2d592d843c0fddaaaa3ae9c58 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 28 Jun 2024 15:22:34 +0100 Subject: [PATCH 106/152] submodule commits evc, openzeppelin --- lib/ethereum-vault-connector | 2 +- lib/openzeppelin-contracts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ethereum-vault-connector b/lib/ethereum-vault-connector index 1601566c..0229f62f 160000 --- a/lib/ethereum-vault-connector +++ b/lib/ethereum-vault-connector @@ -1 +1 @@ -Subproject commit 1601566c2183ee9e9d0e3197d8f5de2e94371517 +Subproject commit 0229f62f92856201e1f33bee9e59daf68938ba34 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 9af280dc..e682c7e5 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 9af280dc4b45ee5bda96ba47ff829b407eaab67e +Subproject commit e682c7e5b5b347662868b9c8378a694626406c01 From 193c646a8fa78b80dd0fc293b712b9a07b1ca87a Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 28 Jun 2024 15:28:27 +0100 Subject: [PATCH 107/152] Cleanup in src --- src/EVault/modules/Liquidation.sol | 1 - src/EVault/modules/Vault.sol | 1 - src/EVault/shared/types/Shares.sol | 8 -------- 3 files changed, 10 deletions(-) diff --git a/src/EVault/modules/Liquidation.sol b/src/EVault/modules/Liquidation.sol index fe53e893..e938a4e0 100644 --- a/src/EVault/modules/Liquidation.sol +++ b/src/EVault/modules/Liquidation.sol @@ -109,7 +109,6 @@ abstract contract LiquidationModule is ILiquidation, BalanceUtils, LiquidityUtil liqCache.repay = desiredRepay.toAssets(); } } - } function calculateMaxLiquidation(LiquidationCache memory liqCache, VaultCache memory vaultCache) diff --git a/src/EVault/modules/Vault.sol b/src/EVault/modules/Vault.sol index 90b8a573..8cd3ea8a 100644 --- a/src/EVault/modules/Vault.sol +++ b/src/EVault/modules/Vault.sol @@ -170,7 +170,6 @@ abstract contract VaultModule is IVault, AssetTransfers, BalanceUtils { Shares shares = amount == type(uint256).max ? vaultStorage.users[owner].getBalance() : amount.toShares(); if (shares.isZero()) return 0; - // Changed by Certora Assets assets = shares.toAssetsDown(vaultCache); if (assets.isZero()) revert E_ZeroAssets(); diff --git a/src/EVault/shared/types/Shares.sol b/src/EVault/shared/types/Shares.sol index 14f9ef50..93731097 100644 --- a/src/EVault/shared/types/Shares.sol +++ b/src/EVault/shared/types/Shares.sol @@ -26,14 +26,6 @@ library SharesLib { } } - // Introduced by Certora - // function toAssetsDownSubShares(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) { - // (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache); - // return TypesLib.toAssets(amount.toUint() * totalAssets / - // (totalShares - amount.toUint())); - // } - - function toAssetsUp(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) { (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache); unchecked { From f6ac01e0c0abefc8e00b936b8f8348859babd433 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 28 Jun 2024 15:51:03 +0100 Subject: [PATCH 108/152] Delete dead file --- .../runERC4626RulesSeparatelySplitConfs.py | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 certora/scripts/runERC4626RulesSeparatelySplitConfs.py diff --git a/certora/scripts/runERC4626RulesSeparatelySplitConfs.py b/certora/scripts/runERC4626RulesSeparatelySplitConfs.py deleted file mode 100644 index 1b25da5f..00000000 --- a/certora/scripts/runERC4626RulesSeparatelySplitConfs.py +++ /dev/null @@ -1,37 +0,0 @@ -import argparse -import subprocess - -parser = argparse.ArgumentParser() -parser.add_argument('-M', '--batchMsg', metavar='M', type=str, nargs='?', - default='', - help='a message for all the jobs') - -rule_confs = { - "conversionOfZero": "BaseERC4626.conf", - "convertToAssetsWeakAdditivity": "BaseERC4626.conf", - "convertToSharesWeakAdditivity": "BaseERC4626.conf" , - "conversionWeakMonotonicity": "BaseERC4626.conf", - "conversionWeakIntegrity": "BaseERC4626.conf", - "convertToCorrectness": "BaseERC4626.conf", - "depositMonotonicity": "BaseERC4626.conf", - "zeroDepositZeroShares": "BaseERC4626.conf", - "assetsMoreThanSupply": "BaseERC4626.conf", - "noAssetsIfNoSupply": "BaseERC4626.conf", - "noSupplyIfNoAssets": "BaseERC4626.conf", - "totalSupplyIsSumOfBalances": "BaseERC4626.conf", - "totalsMonotonicity": "BaseERC4626.conf", - "underlyingCannotChange": "BaseERC4626.conf", - "dustFavorsTheHouse": "BaseERC4626.conf", - "vaultSolvency": "BaseERC4626.conf", - "redeemingAllValidity": "BaseERC4626.conf", - "contributingProducesShares": "BaseERC4626.conf", - "onlyContributionMethodsReduceAssets": "BaseERC4626.conf", - "reclaimingProducesAssets": "BaseERC4626.conf" -} - -for name in rule_confs.keys(): - args = parser.parse_args() - script = f"certora/conf/ERC4626Rules/{rule_confs[name]}" - command = f"certoraRun {script} --rule \"{name}\" --msg \"{name} : {args.batchMsg}\"" - print(f"runing {command}") - subprocess.run(command, shell=True) \ No newline at end of file From 89309d763d3d89b3260206975ba68ee8491ecf4c Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 28 Jun 2024 15:58:53 +0100 Subject: [PATCH 109/152] Try changing comment placement to appease linter --- src/EVault/modules/Liquidation.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EVault/modules/Liquidation.sol b/src/EVault/modules/Liquidation.sol index e938a4e0..e6d36a69 100644 --- a/src/EVault/modules/Liquidation.sol +++ b/src/EVault/modules/Liquidation.sol @@ -56,6 +56,7 @@ abstract contract LiquidationModule is ILiquidation, BalanceUtils, LiquidityUtil executeLiquidation(vaultCache, liqCache, minYieldBalance); } + // Munged to internal by certora to enable harnessing function calculateLiquidation( VaultCache memory vaultCache, address liquidator, From fcf738853ba085bb5bf892143669b43b562da7a7 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 2 Jul 2024 15:20:22 +0100 Subject: [PATCH 110/152] Fix BalanceForwarder --- .../conf/EVault/modules/BalanceForwarder.conf | 9 +++-- certora/specs/BalanceForwarder.spec | 33 ++++--------------- 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/certora/conf/EVault/modules/BalanceForwarder.conf b/certora/conf/EVault/modules/BalanceForwarder.conf index 551532de..b8e99ec2 100644 --- a/certora/conf/EVault/modules/BalanceForwarder.conf +++ b/certora/conf/EVault/modules/BalanceForwarder.conf @@ -1,14 +1,14 @@ { "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "certora/helpers/DummyERC20A.sol", + "certora/harness/EVCHarness.sol", "src/EVault/modules/BalanceForwarder.sol", "certora/harness/BaseHarness.sol", "certora/harness/modules/BalanceForwarderHarness.sol", ], "link": [ - "BalanceForwarderHarness:evc=EthereumVaultConnector", + "BalanceForwarderHarness:evc=EVCHarness", ], "verify": "BalanceForwarderHarness:certora/specs/BalanceForwarder.spec", "solc": "solc8.23", @@ -18,8 +18,7 @@ ], "prover_version": "master", "server" : "production", - // "coverage_info" : "advanced", - "parametric_contracts": ["BalanceForwarder"], + "parametric_contracts": ["BalanceForwarderHarness"], "prover_args": ["-smt_bitVectorTheory", "true"], "optimistic_loop": true, "loop_iter": "2", diff --git a/certora/specs/BalanceForwarder.spec b/certora/specs/BalanceForwarder.spec index d46f947e..8c0d2b73 100644 --- a/certora/specs/BalanceForwarder.spec +++ b/certora/specs/BalanceForwarder.spec @@ -1,48 +1,27 @@ -/* -CER-182 / Verify BalanceForwarder - -EVK-16 enableBalanceForwarder: -If balance tracker specified, enableBalanceForwarder enables the balance -forwarding to the balance tracker for the authenticated account. The balance -tracker hook should be invoked with current balance of the account. - -EVK-17 disableBalanceForwarder -disables the balance forwarding to the -balance tracker for the authenticated account. The balance tracker -hook should be invoked with 0 as the balance of the account. -*/ - import "Base.spec"; +methods { + function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; +} + //passing: -// https://prover.certora.com/output/65266/e2a397f3bb864a9eaf4eefdfd35529bc?anonymousKey=aa5dace26320fee72d3611b84d337413ac48c2da +// https://prover.certora.com/output/65266/ee7d7eb1364e4589a271b46267aa4742?anonymousKey=82512541ee02ddf9a6830a9555878361946ac19a rule enableBalanceForwarder { address account; env e1; env e2; - // require e1.msg.sender != evc; - // require e1.msg.sender == account; require actualCaller(e1) == account; enableBalanceForwarder(e1); assert balanceForwarderEnabled(e2, account); } // passing: -// https://prover.certora.com/output/65266/e2a397f3bb864a9eaf4eefdfd35529bc?anonymousKey=aa5dace26320fee72d3611b84d337413ac48c2da +// https://prover.certora.com/output/65266/ee7d7eb1364e4589a271b46267aa4742?anonymousKey=82512541ee02ddf9a6830a9555878361946ac19a rule disableBalanceForwarder { address account; env e1; env e2; - // require e1.msg.sender != evc; - // require e1.msg.sender == account; require actualCaller(e1) == account; disableBalanceForwarder(e1); assert !balanceForwarderEnabled(e2, account); -} - -rule sanity (method f) { - env e; - calldataarg args; - f(e, args); - satisfy true; } \ No newline at end of file From ea0d0d3c18fd855d047f7f7fbe306f7bc6ab544a Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 4 Jul 2024 11:08:24 +0100 Subject: [PATCH 111/152] Add conf splitting that worked --- .../VaultERC4626-assetsMoreThanSupply.conf | 34 +++++++++++++++ ...ERC4626-convertToAssetsWeakAdditivity.conf | 31 ++++++++++++++ ...ERC4626-convertToSharesWeakAdditivity.conf | 31 ++++++++++++++ .../VaultERC4626-depositMonotonicity.conf | 32 ++++++++++++++ .../VaultERC4626-noAssetsIfNoSupply.conf | 36 ++++++++++++++++ .../VaultERC4626-noSupplyIfNoAssets.conf | 32 ++++++++++++++ ...ERC4626-onlyContributionMethodsReduce.conf | 33 +++++++++++++++ .../VaultERC4626-totalsMonotonicity.conf | 33 +++++++++++++++ .../VaultERC4626-vaultSolvency-most.conf | 33 +++++++++++++++ .../VaultERC4626-vaultSolvency-redeem.conf | 34 +++++++++++++++ .../VaultERC4626-vaultSolvency-withdraw.conf | 42 +++++++++++++++++++ certora/conf/ERC4626Split/VaultERC4626.conf | 31 ++++++++++++++ 12 files changed, 402 insertions(+) create mode 100644 certora/conf/ERC4626Split/VaultERC4626-assetsMoreThanSupply.conf create mode 100644 certora/conf/ERC4626Split/VaultERC4626-convertToAssetsWeakAdditivity.conf create mode 100644 certora/conf/ERC4626Split/VaultERC4626-convertToSharesWeakAdditivity.conf create mode 100644 certora/conf/ERC4626Split/VaultERC4626-depositMonotonicity.conf create mode 100644 certora/conf/ERC4626Split/VaultERC4626-noAssetsIfNoSupply.conf create mode 100644 certora/conf/ERC4626Split/VaultERC4626-noSupplyIfNoAssets.conf create mode 100644 certora/conf/ERC4626Split/VaultERC4626-onlyContributionMethodsReduce.conf create mode 100644 certora/conf/ERC4626Split/VaultERC4626-totalsMonotonicity.conf create mode 100644 certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-most.conf create mode 100644 certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-redeem.conf create mode 100644 certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw.conf create mode 100644 certora/conf/ERC4626Split/VaultERC4626.conf diff --git a/certora/conf/ERC4626Split/VaultERC4626-assetsMoreThanSupply.conf b/certora/conf/ERC4626Split/VaultERC4626-assetsMoreThanSupply.conf new file mode 100644 index 00000000..b49687ff --- /dev/null +++ b/certora/conf/ERC4626Split/VaultERC4626-assetsMoreThanSupply.conf @@ -0,0 +1,34 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule": ["assetsMoreThanSupply",], + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_args": [ + "-maxConcurrentTransforms INLINED_HOOKS:8", + "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -splitParallel true -splitParallelInitialDepth 2 -depth 15 -s [z3:arith1,yices:def] -mediumTimeout 2 -splitParallelTimelimit 7200" + ], + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "smt_timeout": "7200" +} + diff --git a/certora/conf/ERC4626Split/VaultERC4626-convertToAssetsWeakAdditivity.conf b/certora/conf/ERC4626Split/VaultERC4626-convertToAssetsWeakAdditivity.conf new file mode 100644 index 00000000..4c4f32b1 --- /dev/null +++ b/certora/conf/ERC4626Split/VaultERC4626-convertToAssetsWeakAdditivity.conf @@ -0,0 +1,31 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule": ["convertToAssetsWeakAdditivity"], + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "disable_auto_cache_key_gen": true, + "fe_version": "latest", +} + diff --git a/certora/conf/ERC4626Split/VaultERC4626-convertToSharesWeakAdditivity.conf b/certora/conf/ERC4626Split/VaultERC4626-convertToSharesWeakAdditivity.conf new file mode 100644 index 00000000..5b966620 --- /dev/null +++ b/certora/conf/ERC4626Split/VaultERC4626-convertToSharesWeakAdditivity.conf @@ -0,0 +1,31 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule": ["convertToSharesWeakAdditivity"], + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "disable_auto_cache_key_gen": true, + "fe_version": "latest", +} + diff --git a/certora/conf/ERC4626Split/VaultERC4626-depositMonotonicity.conf b/certora/conf/ERC4626Split/VaultERC4626-depositMonotonicity.conf new file mode 100644 index 00000000..b3133b21 --- /dev/null +++ b/certora/conf/ERC4626Split/VaultERC4626-depositMonotonicity.conf @@ -0,0 +1,32 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule": ["depositMonotonicity",], + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "disable_auto_cache_key_gen": true, + "fe_version": "latest", + "smt_timeout": "7200" +} + diff --git a/certora/conf/ERC4626Split/VaultERC4626-noAssetsIfNoSupply.conf b/certora/conf/ERC4626Split/VaultERC4626-noAssetsIfNoSupply.conf new file mode 100644 index 00000000..2ca02811 --- /dev/null +++ b/certora/conf/ERC4626Split/VaultERC4626-noAssetsIfNoSupply.conf @@ -0,0 +1,36 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule": ["noAssetsIfNoSupply",], + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "disable_auto_cache_key_gen": true, + "fe_version": "latest", + "prover_args": [ + "-maxConcurrentTransforms INLINED_HOOKS:6", + "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 " + ], + "smt_timeout": "7200" +} + diff --git a/certora/conf/ERC4626Split/VaultERC4626-noSupplyIfNoAssets.conf b/certora/conf/ERC4626Split/VaultERC4626-noSupplyIfNoAssets.conf new file mode 100644 index 00000000..e454884c --- /dev/null +++ b/certora/conf/ERC4626Split/VaultERC4626-noSupplyIfNoAssets.conf @@ -0,0 +1,32 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule": ["noSupplyIfNoAssets",], + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "disable_auto_cache_key_gen": true, + "fe_version": "latest", + "smt_timeout": "7200" +} + diff --git a/certora/conf/ERC4626Split/VaultERC4626-onlyContributionMethodsReduce.conf b/certora/conf/ERC4626Split/VaultERC4626-onlyContributionMethodsReduce.conf new file mode 100644 index 00000000..3f77397c --- /dev/null +++ b/certora/conf/ERC4626Split/VaultERC4626-onlyContributionMethodsReduce.conf @@ -0,0 +1,33 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-maxConcurrentTransforms INLINED_HOOKS:8" + ], + "rule": ["onlyContributionMethodsReduceAssets"], + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "smt_timeout": "7200" +} + diff --git a/certora/conf/ERC4626Split/VaultERC4626-totalsMonotonicity.conf b/certora/conf/ERC4626Split/VaultERC4626-totalsMonotonicity.conf new file mode 100644 index 00000000..f82c6085 --- /dev/null +++ b/certora/conf/ERC4626Split/VaultERC4626-totalsMonotonicity.conf @@ -0,0 +1,33 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "prover_args": [ + "-maxConcurrentTransforms INLINED_HOOKS:8" + ], + "rule": ["totalsMonotonicity"], + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "smt_timeout": "7200" +} + diff --git a/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-most.conf b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-most.conf new file mode 100644 index 00000000..d6db7472 --- /dev/null +++ b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-most.conf @@ -0,0 +1,33 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule": ["vaultSolvency"], + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "prover_args" : [ + "-smt_nonLinearArithmetic false", + ], + "smt_timeout": "7200", +} + diff --git a/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-redeem.conf b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-redeem.conf new file mode 100644 index 00000000..4af76fed --- /dev/null +++ b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-redeem.conf @@ -0,0 +1,34 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule": ["vaultSolvency"], + "method": "redeem(uint256,address,address)", + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "prover_args" : [ + "-smt_nonLinearArithmetic false", + ], + "smt_timeout": "7200", +} + diff --git a/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw.conf b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw.conf new file mode 100644 index 00000000..f869fa4a --- /dev/null +++ b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw.conf @@ -0,0 +1,42 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule": ["vaultSolvency"], + "method": "withdraw(uint256,address,address)", + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + // "prover_args" : [ + // "-smt_nonLinearArithmetic false", + // ], + // Old options that worked + // "prover_args": [ + // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 -s [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" + // ], + // Old options that worked minus seeds + "prover_args": [ + "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0" + ], + "smt_timeout": "7200", +} + diff --git a/certora/conf/ERC4626Split/VaultERC4626.conf b/certora/conf/ERC4626Split/VaultERC4626.conf new file mode 100644 index 00000000..cf5951d2 --- /dev/null +++ b/certora/conf/ERC4626Split/VaultERC4626.conf @@ -0,0 +1,31 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "disable_auto_cache_key_gen": true, + "fe_version": "latest", + "smt_timeout": "7200" +} + From 53851e42c6ee3e8e8018e352ce175cfabfd384b0 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 2 Jul 2024 15:58:42 +0100 Subject: [PATCH 112/152] Improvements to health status invariant --- .../BalanceForwarderHealthStatus.conf | 3 +- certora/helpers/DummyETokenA.sol | 8 ++++ certora/helpers/DummyETokenB.sol | 8 ++++ certora/specs/HealthStatusInvariant.spec | 38 +++++++++++++------ 4 files changed, 44 insertions(+), 13 deletions(-) create mode 100644 certora/helpers/DummyETokenA.sol create mode 100644 certora/helpers/DummyETokenB.sol diff --git a/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf b/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf index 48d932a9..dbfba46f 100644 --- a/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf +++ b/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf @@ -3,9 +3,10 @@ "certora/harness/EVCHarness.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyETokenA.sol", + "certora/helpers/DummyETokenB.sol", "src/EVault/modules/BalanceForwarder.sol", "certora/harness/BaseHarness.sol", - "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/BalanceForwarderHSHarness.sol", ], "link": [ diff --git a/certora/helpers/DummyETokenA.sol b/certora/helpers/DummyETokenA.sol new file mode 100644 index 00000000..b669c001 --- /dev/null +++ b/certora/helpers/DummyETokenA.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; + +import "certora/harness/TokenHarness.sol"; + +contract DummyETokenA is TokenHarness { + constructor(Integrations memory integrations) TokenHarness(integrations) {} +} \ No newline at end of file diff --git a/certora/helpers/DummyETokenB.sol b/certora/helpers/DummyETokenB.sol new file mode 100644 index 00000000..e72c7059 --- /dev/null +++ b/certora/helpers/DummyETokenB.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; + +import "certora/harness/TokenHarness.sol"; + +contract DummyETokenB is TokenHarness { + constructor(Integrations memory integrations) TokenHarness(integrations) {} +} \ No newline at end of file diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index 6238f75f..9a889900 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -1,7 +1,9 @@ import "Base.spec"; import "LoadVaultSummary.spec"; using DummyERC20A as ERC20a; -using TokenHarness as EToken; // Used to assume collaterals are ETokens +using DummyETokenA as ETokenA; // Used to assume collaterals are ETokens. +using DummyETokenB as ETokenB; // Allows for possibility of multiple + // addresses for different collaterals. methods { function checkAccountMagicValue() external returns (bytes4) envfree; @@ -68,8 +70,10 @@ function CVLAreChecksInProgress() returns bool { function CVLSafeTransferFrom(env e, address token, address from, address to, uint256 value) { if (token == ERC20a) { ERC20a.transferFrom(e, from, to, value); - } else if (token == EToken) { - EToken.transferFrom(e, from, to, value); + } else if (token == ETokenA) { + ETokenA.transferFrom(e, from, to, value); + } else if (token == ETokenB) { + ETokenB.transferFrom(e, from, to, value); } } @@ -91,10 +95,12 @@ function CVLSafeTransferFrom(env e, address token, address from, address to, uin * - enqueue a status check on the evc for the "from" address */ function CVLEnforceCollateralTransfer(env e, address collateral, uint256 amount, address from, address receiver) { - // (Ideally: Cast collateral address into EToken to allow for - // multiple addresses with the EToken contract) evc.requireAccountStatusCheck(e, from); - EToken.transferFromInternalHarnessed(e, from, receiver, amount); + if (collateral == ETokenA) { + ETokenA.transferFromInternalHarnessed(e, from, receiver, amount); + } else if (collateral == ETokenB) { + ETokenB.transferFromInternalHarnessed(e, from, receiver, amount); + } } // Assuming the prices stay the same, a healthy account can never become @@ -102,6 +108,7 @@ function CVLEnforceCollateralTransfer(env e, address collateral, uint256 amount, // in the fact that the summary for GetQuote is an uninterpreted function -- // the prover will model it as a function so it will always return the same // value when given the same arguments. +/* rule accountsStayHealthy (method f) filtered { f -> // Literal selectors are used to avoid compilation errors when // only some of the modules are in the verification scene @@ -155,6 +162,7 @@ rule accountsStayHealthy (method f) filtered { f -> bool healthyAfter= !lastReverted; assert healthyBefore => healthyAfter; } +*/ rule accountsStayHealthy_strategy (method f) filtered { f -> // Literal selectors are used to avoid compilation errors when @@ -170,24 +178,30 @@ rule accountsStayHealthy_strategy (method f) filtered { f -> calldataarg args; address account; address[] collaterals = evc.getCollaterals(e, account); - require collaterals.length == 2; // loop bound + require collaterals.length <= 2; // loop bound require oracleAddress != 0; // Vault cannot be a user of itself - require account != currentContract; + // require account != currentContract; // NOTE recently removed this // Vault should not be used as a collateral require collaterals[0] != currentContract; require collaterals[1] != currentContract; // Collaterals must be ETokens - require collaterals[0] == EToken; - require collaterals[1] == EToken; + // require collaterals[0] == EToken; + // require collaterals[1] == EToken; // not sure the following 4 are really needed require account != erc20; require account != oracleAddress; require account != evc; require account != unitOfAccount; - require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[0])); - require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[1])); + require LTVConfigAssumptions(e, getLTVConfig(e, ETokenA)); + require LTVConfigAssumptions(e, getLTVConfig(e, ETokenB)); + if (collaterals.length > 0) { + require collaterals[0] == ETokenA || collaterals[0] == ETokenB; + } + if (collaterals.length > 1) { + require collaterals[1] == ETokenA || collaterals[1] == ETokenB; + } bool healthyBefore = checkLiquidityReturning(e, account, collaterals); f(e, args); From 3e841504dc95833e98a176d0bdf4ceb6200adfbd Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 4 Jul 2024 08:47:11 +0100 Subject: [PATCH 113/152] Holy Grail first round of improvements --- .../BalanceForwarderHealthStatus.conf | 2 +- .../healthStatus/BorrowingHealthStatus.conf | 16 ++-- .../conf/healthStatus/EVaultHealthStatus.conf | 30 -------- .../healthStatus/GovernanceHealthStatus.conf | 8 +- .../healthStatus/InitializeHealthStatus.conf | 9 ++- .../healthStatus/LiquidateHealthStatus.conf | 11 +-- .../healthStatus/LiquidationHealthStatus.conf | 10 +-- .../conf/healthStatus/TokenHealthStatus.conf | 7 +- .../conf/healthStatus/VaultHealthStatus.conf | 11 ++- certora/scripts/runHealthStatusAllModules.py | 60 +++++++++++---- certora/specs/HealthStatusInvariant.spec | 56 -------------- certora/specs/LiquidateHealthStatus.spec | 73 ++++++++++++------- 12 files changed, 128 insertions(+), 165 deletions(-) delete mode 100644 certora/conf/healthStatus/EVaultHealthStatus.conf diff --git a/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf b/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf index dbfba46f..acdd8250 100644 --- a/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf +++ b/certora/conf/healthStatus/BalanceForwarderHealthStatus.conf @@ -23,7 +23,7 @@ ], "build_cache": true, "prover_version": "master", - "server" : "production", + "server" : "staging", "parametric_contracts": ["BalanceForwarderHSHarness"], "optimistic_loop": true, "loop_iter": "2", diff --git a/certora/conf/healthStatus/BorrowingHealthStatus.conf b/certora/conf/healthStatus/BorrowingHealthStatus.conf index 9d93ae73..2892eb49 100644 --- a/certora/conf/healthStatus/BorrowingHealthStatus.conf +++ b/certora/conf/healthStatus/BorrowingHealthStatus.conf @@ -3,9 +3,10 @@ "certora/harness/EVCHarness.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyETokenA.sol", + "certora/helpers/DummyETokenB.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", - "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/BorrowingHSHarness.sol", ], "link": [ @@ -22,11 +23,11 @@ ], // "method" : "borrow(uint256,address)", // "method" : "pullDebt(uint256,address)", - "method" : "repayWithShares(uint256,address)", + // "method" : "repayWithShares(uint256,address)", // "method" : "repay(uint256,address)", "build_cache": true, "prover_version": "master", - "server" : "production", + "server" : "staging", "parametric_contracts": ["BorrowingHSHarness"], "optimistic_loop": true, "loop_iter": "2", @@ -36,12 +37,7 @@ "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, "prover_args": [ - " -timeoutCracker true" + "-smt_easy_LIA true" ], - "smt_timeout": "7200", - // "prover_args": [ - // "-timeoutCracker true" - // ], - // "smt_timeout": "7200", - // "smt_timeout": "28800", + "smt_timeout": "28800", } \ No newline at end of file diff --git a/certora/conf/healthStatus/EVaultHealthStatus.conf b/certora/conf/healthStatus/EVaultHealthStatus.conf deleted file mode 100644 index d68c4049..00000000 --- a/certora/conf/healthStatus/EVaultHealthStatus.conf +++ /dev/null @@ -1,30 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "certora/helpers/DummyERC20A.sol", - "certora/harness/BaseHarness.sol", - "certora/harness/EVaultHarness.sol", - ], - "link": [ - "EVaultHarness:evc=EthereumVaultConnector", - ], - "verify": "EVaultHarness:certora/specs/HealthStatusInvariant.spec", - "solc": "solc8.23", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "build_cache": true, - "prover_version": "master", - "server" : "production", - // "coverage_info" : "advanced", - "parametric_contracts": ["EVaultHarness"], - // "prover_args": ["-smt_bitVectorTheory", "true"], - "optimistic_loop": true, - "loop_iter": "2", - "solc_via_ir": true, - "solc_optimize": "10000", - "rule_sanity": "basic", - "function_finder_mode" : "relaxed", - "finder_friendly_optimizer" : false, -} \ No newline at end of file diff --git a/certora/conf/healthStatus/GovernanceHealthStatus.conf b/certora/conf/healthStatus/GovernanceHealthStatus.conf index 17194c9b..7d342fe3 100644 --- a/certora/conf/healthStatus/GovernanceHealthStatus.conf +++ b/certora/conf/healthStatus/GovernanceHealthStatus.conf @@ -3,9 +3,10 @@ "certora/harness/EVCHarness.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyETokenA.sol", + "certora/helpers/DummyETokenB.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", - "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/GovernanceHSHarness.sol", ], "link": [ @@ -18,12 +19,11 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - // "accountsStayHealthy", "accountsStayHealthy_strategy" ], "build_cache": true, "prover_version": "master", - "server" : "production", + "server" : "staging", "parametric_contracts": ["GovernanceHSHarness"], // "prover_args": ["-smt_bitVectorTheory", "true"], "optimistic_loop": true, @@ -38,5 +38,5 @@ "-deleteSMTFile false", "-smt_easy_LIA true" ], - "smt_timeout": "4800", + "smt_timeout": "28800", } \ No newline at end of file diff --git a/certora/conf/healthStatus/InitializeHealthStatus.conf b/certora/conf/healthStatus/InitializeHealthStatus.conf index 87db24c5..8e70b6bf 100644 --- a/certora/conf/healthStatus/InitializeHealthStatus.conf +++ b/certora/conf/healthStatus/InitializeHealthStatus.conf @@ -3,9 +3,10 @@ "certora/harness/EVCHarness.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyETokenA.sol", + "certora/helpers/DummyETokenB.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", - "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/InitializeHSHarness.sol", ], "link": [ @@ -18,11 +19,11 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy" + "accountsStayHealthy_strategy" ], "build_cache": true, "prover_version": "master", - "server" : "production", + "server" : "staging", "parametric_contracts": ["InitializeHSHarness"], "optimistic_loop": true, "loop_iter": "2", @@ -36,5 +37,5 @@ "-deleteSMTFile false", "-smt_easy_LIA true" ], - "smt_timeout": "4800", + "smt_timeout": "28800", } \ No newline at end of file diff --git a/certora/conf/healthStatus/LiquidateHealthStatus.conf b/certora/conf/healthStatus/LiquidateHealthStatus.conf index 1bd7f2d4..12d41059 100644 --- a/certora/conf/healthStatus/LiquidateHealthStatus.conf +++ b/certora/conf/healthStatus/LiquidateHealthStatus.conf @@ -3,9 +3,10 @@ "certora/harness/EVCHarness.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyETokenA.sol", + "certora/helpers/DummyETokenB.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", - "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/LiquidationHSHarness.sol", ], "link": [ @@ -18,14 +19,14 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - // "liquidateAccountsStayHealthy_liquidator_no_debt_socialization", + "liquidateAccountsStayHealthy_liquidator_no_debt_socialization", "liquidateAccountsStayHealthy_liquidator_with_debt_socialization", - // "liquidateAccountsStayHealthy_not_violator" + "liquidateAccountsStayHealthy_not_violator" ], "method":"liquidate(address,address,uint256,uint256)", "build_cache": true, "prover_version": "master", - "server" : "production", + "server" : "staging", "parametric_contracts": ["LiquidationHSHarness"], "optimistic_loop": true, "loop_iter": "2", @@ -37,5 +38,5 @@ "prover_args": [ "-smt_easy_LIA true" ], - "smt_timeout": "7200", + "smt_timeout": "28800", } \ No newline at end of file diff --git a/certora/conf/healthStatus/LiquidationHealthStatus.conf b/certora/conf/healthStatus/LiquidationHealthStatus.conf index de8d90b1..62ba115d 100644 --- a/certora/conf/healthStatus/LiquidationHealthStatus.conf +++ b/certora/conf/healthStatus/LiquidationHealthStatus.conf @@ -3,9 +3,10 @@ "certora/harness/EVCHarness.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyETokenA.sol", + "certora/helpers/DummyETokenB.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", - "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/LiquidationHSHarness.sol", ], "link": [ @@ -18,12 +19,11 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - "accountsStayHealthy" + "accountsStayHealthy_strategy" ], - "method":"liquidate(address,address,uint256,uint256)", "build_cache": true, "prover_version": "master", - "server" : "production", + "server" : "staging", "parametric_contracts": ["LiquidationHSHarness"], "optimistic_loop": true, "loop_iter": "2", @@ -35,5 +35,5 @@ "prover_args": [ " -smt_easy_LIA true" ], - "smt_timeout": "7200", + "smt_timeout": "28800", } \ No newline at end of file diff --git a/certora/conf/healthStatus/TokenHealthStatus.conf b/certora/conf/healthStatus/TokenHealthStatus.conf index ec3a3c2d..d9261854 100644 --- a/certora/conf/healthStatus/TokenHealthStatus.conf +++ b/certora/conf/healthStatus/TokenHealthStatus.conf @@ -3,9 +3,10 @@ "certora/harness/EVCHarness.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyETokenA.sol", + "certora/helpers/DummyETokenB.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", - "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/TokenHSHarness.sol", ], "link": [ @@ -22,7 +23,7 @@ ], "build_cache": true, "prover_version": "master", - "server" : "production", + "server" : "staging", "parametric_contracts": ["TokenHSHarness"], "optimistic_loop": true, "loop_iter": "2", @@ -36,5 +37,5 @@ "-deleteSMTFile false", "-smt_easy_LIA true" ], - "smt_timeout": "4800", + "smt_timeout": "28800", } \ No newline at end of file diff --git a/certora/conf/healthStatus/VaultHealthStatus.conf b/certora/conf/healthStatus/VaultHealthStatus.conf index f4128ae2..2d8b5611 100644 --- a/certora/conf/healthStatus/VaultHealthStatus.conf +++ b/certora/conf/healthStatus/VaultHealthStatus.conf @@ -3,10 +3,11 @@ "certora/harness/EVCHarness.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyETokenA.sol", + "certora/helpers/DummyETokenB.sol", "src/EVault/modules/Vault.sol", "src/EVault/modules/Token.sol", "certora/harness/BaseHarness.sol", - "certora/harness/TokenHarness.sol", "certora/harness/healthStatus/VaultHSHarness.sol", ], "link": [ @@ -19,13 +20,11 @@ "forge-std=lib/forge-std/src" ], "rule" : [ - // "accountsStayHealthy_strategy", - "accountsStayHealthy" + "accountsStayHealthy_strategy", ], - "method" : "redeem(uint256,address,address)", "build_cache": true, "prover_version": "master", - "server" : "production", + "server" : "staging", "parametric_contracts": ["VaultHSHarness"], "optimistic_loop": true, "loop_iter": "2", @@ -39,5 +38,5 @@ "-deleteSMTFile false", "-smt_easy_LIA true" ], - "smt_timeout": "4800", + "smt_timeout": "28800", } \ No newline at end of file diff --git a/certora/scripts/runHealthStatusAllModules.py b/certora/scripts/runHealthStatusAllModules.py index fe2c759c..b53dcd75 100644 --- a/certora/scripts/runHealthStatusAllModules.py +++ b/certora/scripts/runHealthStatusAllModules.py @@ -5,24 +5,56 @@ parser.add_argument('-M', '--batchMsg', metavar='M', type=str, nargs='?', default='', help='a message for all the jobs') +args = parser.parse_args() hs_confs = [ - # "BalanceForwarder", - # "Borrowing", + "BalanceForwarder", + "Borrowing", "Governance", - # "Initialize", - # "Liquidation", - # "Token", - # "Vault" + "Initialize", + "Liquidation", + "Token", + "Vault" ] for conf in hs_confs: - args = parser.parse_args() script = f"certora/conf/healthStatus/{conf}HealthStatus.conf" - commands = [ - f"certoraRun {script} --msg \"{conf} : {args.batchMsg}\" --rule \"accountsStayHealthy\"", - f"certoraRun {script} --msg \"{conf} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\"" - ] - for command in commands: - print(f"runing {command}") - subprocess.run(command, shell=True) \ No newline at end of file + command = f"certoraRun {script} --msg \"{conf} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\"" + print(f"runing {command}") + subprocess.run(command, shell=True) + +borrow_separate_methods = [ + "borrow(uint256,address)", + "pullDebt(uint256,address)", + "repayWithShares(uint256,address)", + "repay(uint256,address)", +] + +for method in borrow_separate_methods: + script = f"certora/conf/healthStatus/BorrowingHealthStatus.conf" + command = f"certoraRun {script} --msg \"Borrow.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) + +vault_separate_methods = [ + "redeem(uint256,address,address)", + "withdraw(uint256,address,address)" +] + +for method in vault_separate_methods: + script = f"certora/conf/healthStatus/VaultHealthStatus.conf" + command = f"certoraRun {script} --msg \"Vault.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) + +liquidate_cases = [ + "liquidateAccountsStayHealthy_liquidator_no_debt_socialization", + "liquidateAccountsStayHealthy_liquidator_with_debt_socialization", + "liquidateAccountsStayHealthy_not_violator" +] + +for rule in liquidate_cases: + script = f"certora/conf/healthStatus/LiquidateHealthStatus.conf" + command = f"certoraRun {script} --msg \"Liquidate case: {rule} : {args.batchMsg}\" --rule \"{rule}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index 9a889900..1e192e65 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -108,62 +108,6 @@ function CVLEnforceCollateralTransfer(env e, address collateral, uint256 amount, // in the fact that the summary for GetQuote is an uninterpreted function -- // the prover will model it as a function so it will always return the same // value when given the same arguments. -/* -rule accountsStayHealthy (method f) filtered { f -> - // Literal selectors are used to avoid compilation errors when - // only some of the modules are in the verification scene - // sig:GovernanceModule.clearLTV(address).selector - f.selector != 0x8255d029 && - // sig:GovernanceModule.setLTV(address,uint16,uint16,uint32).selector - f.selector != 0x4bca3d5b && - // sig:InitializeModule.initialize(address).selector - f.selector != 0xc4d66de8 -}{ - env e; - calldataarg args; - address account; - address[] collaterals = evc.getCollaterals(e, account); - require collaterals.length == 2; // loop bound - require oracleAddress != 0; - - // Vault cannot be a user of itself - require account != currentContract; - // Vault should not be used as a collateral - require collaterals[0] != currentContract; - require collaterals[1] != currentContract; - // Collaterals must be ETokens - require collaterals[0] == EToken; - require collaterals[1] == EToken; - // not sure the following 4 are really needed - require account != erc20; - require account != oracleAddress; - require account != evc; - require account != unitOfAccount; - - require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[0])); - require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[1])); - - // Otherwise this can cause an unintersting divide by zero in OwedLib.getCurrentOwed (on the mulDiv) - require getUserInterestAccumulator(e, account) > 0; - require storage_interestAccumulator(e) == getUserInterestAccumulator(e, account); - // otherwise this can cause an uninteresting overflow in mulDiv - require storage_interestAccumulator(e) < max_uint112; - - checkAccountStatus@withrevert(e, account, collaterals); - bool healthyBefore = !lastReverted; - f(e, args); - // The only way to call a vault funciton is through EVC's call, batch, - // or permit. During all of these status checks are deferred and at the end - // these call restoreExecutionContext which triggers the deferred checks. - // This excplicit call to checkStatusAll is a way to get a setup that - // approximates the real situation. - evc.checkStatusAllExt(e); - checkAccountStatus@withrevert(e, account, collaterals); - bool healthyAfter= !lastReverted; - assert healthyBefore => healthyAfter; -} -*/ - rule accountsStayHealthy_strategy (method f) filtered { f -> // Literal selectors are used to avoid compilation errors when // only some of the modules are in the verification scene diff --git a/certora/specs/LiquidateHealthStatus.spec b/certora/specs/LiquidateHealthStatus.spec index 1d680a97..94cd4f15 100644 --- a/certora/specs/LiquidateHealthStatus.spec +++ b/certora/specs/LiquidateHealthStatus.spec @@ -1,7 +1,9 @@ import "Base.spec"; import "LoadVaultSummary.spec"; using DummyERC20A as ERC20a; -using TokenHarness as EToken; // Used to assume collaterals are ETokens +using DummyETokenA as ETokenA; // Used to assume collaterals are ETokens. +using DummyETokenB as ETokenB; // Allows for possibility of multiple + // addresses for different collaterals. methods { function checkAccountMagicValue() external returns (bytes4) envfree; @@ -18,7 +20,6 @@ methods { function EVCHarness.disableController(address account) external => NONDET; function _.computeInterestRate(address vault, uint256 cash, uint256 borrows) external => NONDET; function _.onFlashLoan(bytes data) external => NONDET; - // function _.computeInterestRate(BaseHarness.VaultCache memory vaultCache) internal => NONDET; // Harness function LiquidationHSHarness.hasDebtSocialization() external returns (bool) envfree; @@ -86,8 +87,10 @@ function CVLAreChecksInProgress() returns bool { function CVLSafeTransferFrom(env e, address token, address from, address to, uint256 value) { if (token == ERC20a) { ERC20a.transferFrom(e, from, to, value); - } else if (token == EToken) { - EToken.transferFrom(e, from, to, value); + } else if (token == ETokenA) { + ETokenA.transferFrom(e, from, to, value); + } else if (token == ETokenB) { + ETokenB.transferFrom(e, from, to, value); } } @@ -114,7 +117,11 @@ persistent ghost address collateralTransferCheckedAccount; function CVLEnforceCollateralTransfer(env e, address collateral, uint256 amount, address from, address receiver) { // evc.requireAccountStatusCheck(e, from); collateralTransferCheckedAccount = from; - EToken.transferFromInternalHarnessed(e, from, receiver, amount); + if (collateral == ETokenA) { + ETokenA.transferFromInternalHarnessed(e, from, receiver, amount); + } else if (collateral == ETokenB) { + ETokenB.transferFromInternalHarnessed(e, from, receiver, amount); + } } //----------------------------------------------------------------------------- @@ -141,24 +148,28 @@ rule liquidateAccountsStayHealthy_liquidator_no_debt_socialization { env e; address account; address[] collaterals = evc.getCollaterals(e, account); - require collaterals.length == 2; // loop bound + require collaterals.length <= 2; // loop bound require oracleAddress != 0; // Vault cannot be a user of itself - require account != currentContract; + // require account != currentContract; // Vault should not be used as a collateral require collaterals[0] != currentContract; require collaterals[1] != currentContract; - // Collaterals must be ETokens - require collaterals[0] == EToken; - require collaterals[1] == EToken; // not sure the following 4 are really needed require account != erc20; require account != oracleAddress; require account != evc; require account != unitOfAccount; - require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[0])); - require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[1])); + require LTVConfigAssumptions(e, getLTVConfig(e, ETokenA)); + require LTVConfigAssumptions(e, getLTVConfig(e, ETokenB)); + // Collaterals must be ETokens + if (collaterals.length > 0) { + require collaterals[0] == ETokenA || collaterals[0] == ETokenB; + } + if (collaterals.length > 1) { + require collaterals[1] == ETokenA || collaterals[1] == ETokenB; + } address violator; address collateral; @@ -173,7 +184,7 @@ rule liquidateAccountsStayHealthy_liquidator_no_debt_socialization { require collateralTransferCheckedAccount == 0; // account eq liquidator case - require collateral == collaterals[0] || collateral == collaterals[1]; + require collateral == ETokenA || collateral == ETokenB; address liquidator = actualCaller(e); require account == liquidator; require violator != liquidator; @@ -200,24 +211,28 @@ rule liquidateAccountsStayHealthy_liquidator_with_debt_socialization { env e; address account; address[] collaterals = evc.getCollaterals(e, account); - require collaterals.length == 2; // loop bound + require collaterals.length <= 2; // loop bound require oracleAddress != 0; // Vault cannot be a user of itself - require account != currentContract; + // require account != currentContract; // Vault should not be used as a collateral require collaterals[0] != currentContract; require collaterals[1] != currentContract; - // Collaterals must be ETokens - require collaterals[0] == EToken; - require collaterals[1] == EToken; // not sure the following 4 are really needed require account != erc20; require account != oracleAddress; require account != evc; require account != unitOfAccount; - require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[0])); - require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[1])); + require LTVConfigAssumptions(e, getLTVConfig(e, ETokenA)); + require LTVConfigAssumptions(e, getLTVConfig(e, ETokenB)); + // Collaterals must be ETokens + if (collaterals.length > 0) { + require collaterals[0] == ETokenA || collaterals[0] == ETokenB; + } + if (collaterals.length > 1) { + require collaterals[1] == ETokenA || collaterals[1] == ETokenB; + } address violator; address collateral; @@ -259,24 +274,28 @@ rule liquidateAccountsStayHealthy_not_violator { env e; address account; address[] collaterals = evc.getCollaterals(e, account); - require collaterals.length == 2; // loop bound + require collaterals.length <= 2; // loop bound require oracleAddress != 0; // Vault cannot be a user of itself - require account != currentContract; + // require account != currentContract; // Vault should not be used as a collateral require collaterals[0] != currentContract; require collaterals[1] != currentContract; - // Collaterals must be ETokens - require collaterals[0] == EToken; - require collaterals[1] == EToken; // not sure the following 4 are really needed require account != erc20; require account != oracleAddress; require account != evc; require account != unitOfAccount ; - require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[0])); - require LTVConfigAssumptions(e, getLTVConfig(e, collaterals[1])); + require LTVConfigAssumptions(e, getLTVConfig(e, ETokenA)); + require LTVConfigAssumptions(e, getLTVConfig(e, ETokenB)); + // Collaterals must be ETokens + if (collaterals.length > 0) { + require collaterals[0] == ETokenA || collaterals[0] == ETokenB; + } + if (collaterals.length > 1) { + require collaterals[1] == ETokenA || collaterals[1] == ETokenB; + } address violator; address collateral; From b16a9a252cbc8f356dfeb1c9cbf6fa131c450311 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 4 Jul 2024 11:45:22 +0100 Subject: [PATCH 114/152] More conf and spec tweaking --- .../conf/healthStatus/VaultHealthStatus.conf | 1 + certora/scripts/runHealthStatusAllModules.py | 83 ++++++++++--------- certora/specs/HealthStatusInvariant.spec | 8 +- certora/specs/LiquidateHealthStatus.spec | 80 ++++++++++++++++-- 4 files changed, 121 insertions(+), 51 deletions(-) diff --git a/certora/conf/healthStatus/VaultHealthStatus.conf b/certora/conf/healthStatus/VaultHealthStatus.conf index 2d8b5611..ac2c3d5b 100644 --- a/certora/conf/healthStatus/VaultHealthStatus.conf +++ b/certora/conf/healthStatus/VaultHealthStatus.conf @@ -34,6 +34,7 @@ "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, "prover_args": [ + "-maxConcurrentTransforms INLINED_HOOKS:8", "-splitParallel true", "-deleteSMTFile false", "-smt_easy_LIA true" diff --git a/certora/scripts/runHealthStatusAllModules.py b/certora/scripts/runHealthStatusAllModules.py index b53dcd75..df6c3464 100644 --- a/certora/scripts/runHealthStatusAllModules.py +++ b/certora/scripts/runHealthStatusAllModules.py @@ -7,50 +7,51 @@ help='a message for all the jobs') args = parser.parse_args() -hs_confs = [ - "BalanceForwarder", - "Borrowing", - "Governance", - "Initialize", - "Liquidation", - "Token", - "Vault" -] - -for conf in hs_confs: - script = f"certora/conf/healthStatus/{conf}HealthStatus.conf" - command = f"certoraRun {script} --msg \"{conf} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\"" - print(f"runing {command}") - subprocess.run(command, shell=True) - -borrow_separate_methods = [ - "borrow(uint256,address)", - "pullDebt(uint256,address)", - "repayWithShares(uint256,address)", - "repay(uint256,address)", -] - -for method in borrow_separate_methods: - script = f"certora/conf/healthStatus/BorrowingHealthStatus.conf" - command = f"certoraRun {script} --msg \"Borrow.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" - print(f"runing {command}") - subprocess.run(command, shell=True) - -vault_separate_methods = [ - "redeem(uint256,address,address)", - "withdraw(uint256,address,address)" -] - -for method in vault_separate_methods: - script = f"certora/conf/healthStatus/VaultHealthStatus.conf" - command = f"certoraRun {script} --msg \"Vault.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" - print(f"runing {command}") - subprocess.run(command, shell=True) - +# hs_confs = [ +# "BalanceForwarder", +# "Borrowing", +# "Governance", +# "Initialize", +# "Liquidation", +# "Token", +# "Vault" +# ] +# +# for conf in hs_confs: +# script = f"certora/conf/healthStatus/{conf}HealthStatus.conf" +# command = f"certoraRun {script} --msg \"{conf} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\"" +# print(f"runing {command}") +# subprocess.run(command, shell=True) +# +# borrow_separate_methods = [ +# "borrow(uint256,address)", +# "pullDebt(uint256,address)", +# "repayWithShares(uint256,address)", +# "repay(uint256,address)", +# ] +# +# for method in borrow_separate_methods: +# script = f"certora/conf/healthStatus/BorrowingHealthStatus.conf" +# command = f"certoraRun {script} --msg \"Borrow.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" +# print(f"runing {command}") +# subprocess.run(command, shell=True) +# +# vault_separate_methods = [ +# "redeem(uint256,address,address)", +# "withdraw(uint256,address,address)" +# ] +# +# for method in vault_separate_methods: +# script = f"certora/conf/healthStatus/VaultHealthStatus.conf" +# command = f"certoraRun {script} --msg \"Vault.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" +# print(f"runing {command}") +# subprocess.run(command, shell=True) +# liquidate_cases = [ "liquidateAccountsStayHealthy_liquidator_no_debt_socialization", "liquidateAccountsStayHealthy_liquidator_with_debt_socialization", - "liquidateAccountsStayHealthy_not_violator" + "liquidateAccountsStayHealthy_not_violator", + "liquidateAccountsStayHealthy_account_cur_contract" ] for rule in liquidate_cases: diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index 1e192e65..aa1e5b98 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -116,7 +116,13 @@ rule accountsStayHealthy_strategy (method f) filtered { f -> // sig:GovernanceModule.setLTV(address,uint16,uint16,uint32).selector f.selector != 0x4bca3d5b && // sig:InitializeModule.initialize(address).selector - f.selector != 0xc4d66de8 + f.selector != 0xc4d66de8 && + // // Added temporarily to improve performance of Vault runs for methods other + // // than these + // // redeem + // f.selector != 0xba087652 && + // // withdraw + // f.selector != 0xb460af94 }{ env e; calldataarg args; diff --git a/certora/specs/LiquidateHealthStatus.spec b/certora/specs/LiquidateHealthStatus.spec index 94cd4f15..6b4297ff 100644 --- a/certora/specs/LiquidateHealthStatus.spec +++ b/certora/specs/LiquidateHealthStatus.spec @@ -151,7 +151,7 @@ rule liquidateAccountsStayHealthy_liquidator_no_debt_socialization { require collaterals.length <= 2; // loop bound require oracleAddress != 0; // Vault cannot be a user of itself - // require account != currentContract; + require account != currentContract; // Vault should not be used as a collateral require collaterals[0] != currentContract; require collaterals[1] != currentContract; @@ -165,10 +165,10 @@ rule liquidateAccountsStayHealthy_liquidator_no_debt_socialization { require LTVConfigAssumptions(e, getLTVConfig(e, ETokenB)); // Collaterals must be ETokens if (collaterals.length > 0) { - require collaterals[0] == ETokenA || collaterals[0] == ETokenB; + require collaterals[0] == ETokenA; } if (collaterals.length > 1) { - require collaterals[1] == ETokenA || collaterals[1] == ETokenB; + require collaterals[1] == ETokenB; } address violator; @@ -214,7 +214,7 @@ rule liquidateAccountsStayHealthy_liquidator_with_debt_socialization { require collaterals.length <= 2; // loop bound require oracleAddress != 0; // Vault cannot be a user of itself - // require account != currentContract; + require account != currentContract; // Vault should not be used as a collateral require collaterals[0] != currentContract; require collaterals[1] != currentContract; @@ -228,10 +228,10 @@ rule liquidateAccountsStayHealthy_liquidator_with_debt_socialization { require LTVConfigAssumptions(e, getLTVConfig(e, ETokenB)); // Collaterals must be ETokens if (collaterals.length > 0) { - require collaterals[0] == ETokenA || collaterals[0] == ETokenB; + require collaterals[0] == ETokenA; } if (collaterals.length > 1) { - require collaterals[1] == ETokenA || collaterals[1] == ETokenB; + require collaterals[1] == ETokenB; } address violator; @@ -277,7 +277,7 @@ rule liquidateAccountsStayHealthy_not_violator { require collaterals.length <= 2; // loop bound require oracleAddress != 0; // Vault cannot be a user of itself - // require account != currentContract; + require account != currentContract; // Vault should not be used as a collateral require collaterals[0] != currentContract; require collaterals[1] != currentContract; @@ -291,10 +291,10 @@ rule liquidateAccountsStayHealthy_not_violator { require LTVConfigAssumptions(e, getLTVConfig(e, ETokenB)); // Collaterals must be ETokens if (collaterals.length > 0) { - require collaterals[0] == ETokenA || collaterals[0] == ETokenB; + require collaterals[0] == ETokenA; } if (collaterals.length > 1) { - require collaterals[1] == ETokenA || collaterals[1] == ETokenB; + require collaterals[1] == ETokenB; } address violator; @@ -332,6 +332,68 @@ rule liquidateAccountsStayHealthy_not_violator { currentContract.checkAccountStatus(e, collateralTransferCheckedAccount, collaterals); } + bool healthyAfter = checkLiquidityReturning(e, account, collaterals); + assert healthyBefore => healthyAfter; +} + +rule liquidateAccountsStayHealthy_account_cur_contract { + env e; + address account; + address[] collaterals = evc.getCollaterals(e, account); + require collaterals.length <= 2; // loop bound + require oracleAddress != 0; + // Vault should not be used as a collateral + require collaterals[0] != currentContract; + require collaterals[1] != currentContract; + // not sure the following 4 are really needed + require account != erc20; + require account != oracleAddress; + require account != evc; + require account != unitOfAccount ; + + require LTVConfigAssumptions(e, getLTVConfig(e, ETokenA)); + require LTVConfigAssumptions(e, getLTVConfig(e, ETokenB)); + // Collaterals must be ETokens + if (collaterals.length > 0) { + require collaterals[0] == ETokenA; + } + if (collaterals.length > 1) { + require collaterals[1] == ETokenB; + } + + // vault is account of itself case + require account == currentContract; + + address violator; + address collateral; + uint256 repayAssets; + uint256 minYieldBalance; + + address liquidator = actualCaller(e); + + // initialize checked accounts to 0 + require accountToCheckGhost == 0; // account checked in initialize + require collateralTransferCheckedAccount == 0; + + bool healthyBefore = checkLiquidityReturning(e, account, collaterals); + currentContract.liquidate(e, violator, collateral, repayAssets, minYieldBalance); + // The only way to call a vault funciton is through EVC's call, batch, + // or permit. During all of these status checks are deferred and at the end + // these call restoreExecutionContext which triggers the deferred checks. + // Replace the real call path involving the EVC calling back into the + // vault with a direct call on checkAccountStatus from the vault. + // (For the not_violator / not liquidator case, we can also directly + // call evc.checkStatusAll rather than using these ghosts and + // the direct call on checkAccountStatus, but for the liquidator case + // this will drop performance enough to hit a timeout) + + if(accountToCheckGhost != 0) { + currentContract.checkAccountStatus(e, accountToCheckGhost, collaterals); + } + if(collateralTransferCheckedAccount != 0) { + currentContract.checkAccountStatus(e, collateralTransferCheckedAccount, collaterals); + } + bool healthyAfter = checkLiquidityReturning(e, account, collaterals); assert healthyBefore => healthyAfter; } \ No newline at end of file From 947180360d56dfc05a83b74f640e2ef519701b2a Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 4 Jul 2024 11:54:26 +0100 Subject: [PATCH 115/152] uncomment important bits of script --- certora/scripts/runHealthStatusAllModules.py | 80 ++++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/certora/scripts/runHealthStatusAllModules.py b/certora/scripts/runHealthStatusAllModules.py index df6c3464..ee72e86a 100644 --- a/certora/scripts/runHealthStatusAllModules.py +++ b/certora/scripts/runHealthStatusAllModules.py @@ -7,46 +7,46 @@ help='a message for all the jobs') args = parser.parse_args() -# hs_confs = [ -# "BalanceForwarder", -# "Borrowing", -# "Governance", -# "Initialize", -# "Liquidation", -# "Token", -# "Vault" -# ] -# -# for conf in hs_confs: -# script = f"certora/conf/healthStatus/{conf}HealthStatus.conf" -# command = f"certoraRun {script} --msg \"{conf} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\"" -# print(f"runing {command}") -# subprocess.run(command, shell=True) -# -# borrow_separate_methods = [ -# "borrow(uint256,address)", -# "pullDebt(uint256,address)", -# "repayWithShares(uint256,address)", -# "repay(uint256,address)", -# ] -# -# for method in borrow_separate_methods: -# script = f"certora/conf/healthStatus/BorrowingHealthStatus.conf" -# command = f"certoraRun {script} --msg \"Borrow.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" -# print(f"runing {command}") -# subprocess.run(command, shell=True) -# -# vault_separate_methods = [ -# "redeem(uint256,address,address)", -# "withdraw(uint256,address,address)" -# ] -# -# for method in vault_separate_methods: -# script = f"certora/conf/healthStatus/VaultHealthStatus.conf" -# command = f"certoraRun {script} --msg \"Vault.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" -# print(f"runing {command}") -# subprocess.run(command, shell=True) -# +hs_confs = [ + "BalanceForwarder", + "Borrowing", + "Governance", + "Initialize", + "Liquidation", + "Token", + "Vault" +] + +for conf in hs_confs: + script = f"certora/conf/healthStatus/{conf}HealthStatus.conf" + command = f"certoraRun {script} --msg \"{conf} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\"" + print(f"runing {command}") + subprocess.run(command, shell=True) + +borrow_separate_methods = [ + "borrow(uint256,address)", + "pullDebt(uint256,address)", + "repayWithShares(uint256,address)", + "repay(uint256,address)", +] + +for method in borrow_separate_methods: + script = f"certora/conf/healthStatus/BorrowingHealthStatus.conf" + command = f"certoraRun {script} --msg \"Borrow.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) + +vault_separate_methods = [ + "redeem(uint256,address,address)", + "withdraw(uint256,address,address)" +] + +for method in vault_separate_methods: + script = f"certora/conf/healthStatus/VaultHealthStatus.conf" + command = f"certoraRun {script} --msg \"Vault.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) + liquidate_cases = [ "liquidateAccountsStayHealthy_liquidator_no_debt_socialization", "liquidateAccountsStayHealthy_liquidator_with_debt_socialization", From 40146dc651e64b7ae64a9849a68a7562c5022e11 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 4 Jul 2024 17:30:43 +0100 Subject: [PATCH 116/152] Comments about NONDET summaries --- certora/specs/HealthStatusInvariant.spec | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index aa1e5b98..e22dd917 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -12,13 +12,28 @@ methods { // by the EVC before which this flag will be set. function EVCHarness.areChecksInProgress() external returns bool => CVLAreChecksInProgress(); // unresolved calls that havoc all contracts - function _.isHookTarget() external => NONDET; - function _.invokeHookTarget(address caller) internal => NONDET; + function _.isHookTarget() external => NONDET; // pure + // calls external contract. Here we assume invokeHookTarget does + // not affect the vault's internal state especially user balances + function _.invokeHookTarget(address caller) internal => NONDET; + // The following two are both related to balanceTrackerHook in the + // RewardStreams repository. The implementation of BalanceTrackerHook + // there does not affect the state of the vault contracts + // https://github.com/euler-xyz/reward-streams/blob/master/src/TrackingRewardStreams.sol#L31-L62 function _.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal => NONDET; function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; - function _.emitTransfer(address from, address to, uint256 value) external => NONDET; - function EVCHarness.disableController(address account) external => NONDET; + // just emits en event so NONDET is safe + function _.emitTransfer(address from, address to, uint256 value) external => NONDET; + // has an actual affect -- disables a controller, but this is only called by RiskManager.disableController which reverts unless the controller abalance is 0. So I think this nondet is safe. + function EVCHarness.disableController(address account) external => NONDET; + // computeInterestRate is not strictly pure -- the implementations of + // this function seem to keep state to calculate the future interest rate + // but modeling this as returning an arbitrary should be OK. (There is + // technically a side effect of storing state but that state only + // affects this return value) function _.computeInterestRate(address vault, uint256 cash, uint256 borrows) external => NONDET; + // onFlashLoan is from an external contract. Here we assume this function + // does not affect the vault's internal state especially user balances function _.onFlashLoan(bytes data) external => NONDET; // EVC From 1a827b15c7c6817f339827abeb42b78339b66736 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 4 Jul 2024 18:14:31 +0100 Subject: [PATCH 117/152] Improve comments --- certora/specs/HealthStatusInvariant.spec | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index e22dd917..e17ac89b 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -12,9 +12,13 @@ methods { // by the EVC before which this flag will be set. function EVCHarness.areChecksInProgress() external returns bool => CVLAreChecksInProgress(); // unresolved calls that havoc all contracts - function _.isHookTarget() external => NONDET; // pure + // pure, so NONDET is safe + function _.isHookTarget() external => NONDET; // calls external contract. Here we assume invokeHookTarget does - // not affect the vault's internal state especially user balances + // not affect the vault's internal state especially user balances. + // This is a pretty safe assumption because it is not the EVC and + // access controls in the vault will not allow non-EVC calls to succeed. + // there is also the nonreentrant modifier in most places. function _.invokeHookTarget(address caller) internal => NONDET; // The following two are both related to balanceTrackerHook in the // RewardStreams repository. The implementation of BalanceTrackerHook @@ -33,7 +37,10 @@ methods { // affects this return value) function _.computeInterestRate(address vault, uint256 cash, uint256 borrows) external => NONDET; // onFlashLoan is from an external contract. Here we assume this function - // does not affect the vault's internal state especially user balances + // does not affect the vault's internal state especially user balances. + // This is a pretty safe assumption because it is not the EVC and + // access controls in the vault will not allow non-EVC calls to succeed. + // there is also the nonreentrant modifier in most places. function _.onFlashLoan(bytes data) external => NONDET; // EVC From d18acf9a41681857962673bb5ee56548c755e479 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 5 Jul 2024 12:06:39 +0100 Subject: [PATCH 118/152] delete commented out code --- certora/specs/HealthStatusInvariant.spec | 6 ------ 1 file changed, 6 deletions(-) diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index e17ac89b..5b05dcd7 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -139,12 +139,6 @@ rule accountsStayHealthy_strategy (method f) filtered { f -> f.selector != 0x4bca3d5b && // sig:InitializeModule.initialize(address).selector f.selector != 0xc4d66de8 && - // // Added temporarily to improve performance of Vault runs for methods other - // // than these - // // redeem - // f.selector != 0xba087652 && - // // withdraw - // f.selector != 0xb460af94 }{ env e; calldataarg args; From 8d5890db25aea647a61e8bf78362d4abc39ecc06 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Jul 2024 10:07:49 +0100 Subject: [PATCH 119/152] Fixes in Cache spec/conf --- certora/conf/Cache.conf | 7 ------- certora/specs/Cache.spec | 6 ------ 2 files changed, 13 deletions(-) diff --git a/certora/conf/Cache.conf b/certora/conf/Cache.conf index 2b0f08d7..adb112bd 100644 --- a/certora/conf/Cache.conf +++ b/certora/conf/Cache.conf @@ -1,13 +1,7 @@ { "files": [ - // "lib/ethereum-vault-connector/src/ExecutionContext.sol", - // "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", - "src/EVault/shared/Cache.sol", "certora/harness/CacheHarness.sol" ], - // "link": [ - // "CacheHarness:evc=EthereumVaultConnector", - // ], "verify": "CacheHarness:certora/specs/Cache.spec", "solc": "solc8.23", "packages": [ @@ -16,7 +10,6 @@ ], "prover_version": "master", "server" : "production", - // "coverage_info" : "advanced", "parametric_contracts": ["CacheHarness"], "optimistic_loop": true, "loop_iter": "2", diff --git a/certora/specs/Cache.spec b/certora/specs/Cache.spec index 2ca6597b..b945551a 100644 --- a/certora/specs/Cache.spec +++ b/certora/specs/Cache.spec @@ -1,9 +1,3 @@ - -methods { - // It's not envfree. block time - // function updateVaultExt() external returns (Cache.VaultCache) envfree; -} - // passing // run: https://prover.certora.com/output/65266/e5dc6fb3648f45fdbe48597c69561bd1/?anonymousKey=12ed8515517a0998ef7af0ed86ecc7008537cec1 rule updateVault_no_unexpected_reverts { From 6a9ae1d6c709cf2899441bf60e0ae385835d09ff Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Jul 2024 10:08:43 +0100 Subject: [PATCH 120/152] delete more commented lines in Cache.spec --- certora/specs/Cache.spec | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/certora/specs/Cache.spec b/certora/specs/Cache.spec index b945551a..b090c2a5 100644 --- a/certora/specs/Cache.spec +++ b/certora/specs/Cache.spec @@ -17,26 +17,12 @@ rule updateVault_no_unexpected_reverts { // assignment to newTotalBorrows, overflows // Note: MAX_SANE_AMOUNT does not work as a bound for these: // https://prover.certora.com/output/65266/e1aab12acdb5435d80e70e661299c504?anonymousKey=c6c63c10fa9ddb5c16b86cd2073643768d3d96e4 - // require getTotalBorrows(e) < 1152921504606846975; //2**60-1 require getTotalBorrows(e) < 1267650600228229401496703205375; //2**100-1 - // require getTotalBorrows(e) < 1208925819614629174706175; //2**80-1 - // require getTotalBorrows(e) < max_uint112; //2**80-1 - // require getInterestAcc(e) < 1152921504606846975; require getInterestAcc(e) < 1267650600228229401496703205375; - // require getInterestAcc(e) < max_uint112; // newTotalBorrows assigment, prevent divide by zero require getInterestAcc(e) > 0; - // typecast of newAccumulatedFees - // Also MAX_SANE_AMOUNT is not a sufficient bound for this - // (because the bounded var is from storage not the new accumulated fees) - // https://prover.certora.com/output/65266/8c53d45891374c4692ea7597de239ba1?anonymousKey=551bfa1d1460c56f30002f5de8aeab4bd49a0fcb - // require getAccumulatedFees(e) < 1267650600228229401496703205375; - // require getAccumulatedFees(e) < max_uint112; - - // require getAccumulatedFees(e) < getTotalShares(e); - updateVaultExt@withrevert(e); assert !lastReverted; } \ No newline at end of file From 001efdf442509dbcd84e051155af63d7d7bd14d5 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Jul 2024 10:10:23 +0100 Subject: [PATCH 121/152] Delete commented options in vaultSolvency-withdraw --- .../ERC4626Split/VaultERC4626-vaultSolvency-withdraw.conf | 8 -------- 1 file changed, 8 deletions(-) diff --git a/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw.conf b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw.conf index f869fa4a..aee26141 100644 --- a/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw.conf +++ b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw.conf @@ -26,14 +26,6 @@ "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", - // "prover_args" : [ - // "-smt_nonLinearArithmetic false", - // ], - // Old options that worked - // "prover_args": [ - // "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 -s [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" - // ], - // Old options that worked minus seeds "prover_args": [ "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0" ], From def503233c26f25d3441f5866fa3ba0a3d9580de Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Jul 2024 10:42:20 +0100 Subject: [PATCH 122/152] Fixes to VaultERC4626.conf --- certora/conf/VaultERC4626.conf | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/VaultERC4626.conf index f3cae373..08b59dd3 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/VaultERC4626.conf @@ -1,7 +1,6 @@ { "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "src/EVault/modules/Vault.sol", "certora/helpers/DummyERC20A.sol", "certora/helpers/DummyERC20B.sol", "certora/harness/EVCHarness.sol", @@ -16,25 +15,6 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], - // "rule": [ - // // // // "conversionWeakMonotonicity", - // // // "assetsMoreThanSupply", - // // "contributingProducesShares", - // // // // "depositMonotonicity", - // // "dustFavorsTheHouse", - // // // // "noAssetsIfNoSupply", - // // // // "noSupplyIfNoAssets", - // // // // "supplyLessThanUnderlyingAsset", - // "onlyContributionMethodsReduceAssets", - // // // // "reclaimingProducesAssets", - // // // // "redeemingAllValidity", - // // // // "totalsMonotonicity", - // // // // "vaultSolvency" - // ], - // "method": "redeem(uint256,address,address)", - // "method": "withdraw(uint256,address,address)", - // "method" : "deposit(uint256,address)", - // "method" : "mint(uint256,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, "prover_version" : "yuvalbd/CERT-6495", @@ -43,11 +23,6 @@ "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", - // "prover_args" : [ - // "-smt_nonLinearArithmetic true", - // "-adaptiveSolverConfig false", - // "-splitParallel true" - // ], "disable_auto_cache_key_gen": true, "fe_version": "latest", } From 1b30c555684c99c65d9d05f6b346a0b95b9216b6 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Jul 2024 10:47:24 +0100 Subject: [PATCH 123/152] Fixes to RiskManager.conf --- certora/conf/EVault/modules/RiskManager.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/certora/conf/EVault/modules/RiskManager.conf b/certora/conf/EVault/modules/RiskManager.conf index 29beeb4e..20e8ed85 100644 --- a/certora/conf/EVault/modules/RiskManager.conf +++ b/certora/conf/EVault/modules/RiskManager.conf @@ -1,14 +1,14 @@ { "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "certora/helpers/DummyERC20A.sol", "src/EVault/modules/RiskManager.sol", "certora/harness/BaseHarness.sol", + "certora/harness/EVCHarness.sol", "certora/harness/modules/RiskManagerHarness.sol", ], "link": [ - "RiskManagerHarness:evc=EthereumVaultConnector", + "RiskManagerHarness:evc=EVCHarness", ], "verify": "RiskManagerHarness:certora/specs/RiskManager.spec", "solc": "solc8.23", From 2d03cdb64a86bf6a8d6bf7ab7a41b53bfc60a6b2 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Jul 2024 11:00:11 +0100 Subject: [PATCH 124/152] Comment in GhostPow. Remove typecast in Cache.spec --- certora/specs/Cache.spec | 2 +- certora/specs/GhostPow.spec | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/certora/specs/Cache.spec b/certora/specs/Cache.spec index b090c2a5..3c0c0efe 100644 --- a/certora/specs/Cache.spec +++ b/certora/specs/Cache.spec @@ -9,7 +9,7 @@ rule updateVault_no_unexpected_reverts { uint256 lastInterestAccUpd = getlastInterestAccumulatorUpdate(e); // assignment to deltaT - require assert_uint256(lastInterestAccUpd) < e.block.timestamp; + require lastInterestAccUpd < e.block.timestamp; // https://prover.certora.com/output/65266/e834a7e7775443ffbe26577bfbc97f87?anonymousKey=98085ba3f887e9b0fd2b22683e73af45bc1a106b diff --git a/certora/specs/GhostPow.spec b/certora/specs/GhostPow.spec index 3dbfd0db..5dc05bc7 100644 --- a/certora/specs/GhostPow.spec +++ b/certora/specs/GhostPow.spec @@ -1,6 +1,8 @@ /// @doc Ghost power function that incorporates mathematical pure x^y axioms. /// @warning Some of these axioms might be false, depending on the Solidity implementation /// The user must bear in mind that equality-like axioms can be violated because of rounding errors. +// _ghostPow summarizes RPow.rpow, or: +// _ghostPow(x, y, scalar) = scalar * x^y ghost _ghostPow(uint256, uint256, uint256) returns mathint { /// x^0 = 1 axiom forall uint256 x. forall uint256 base. _ghostPow(x, 0, base) == to_mathint(base); From bea1106ff23b783163f33adb89908181c0e6ebcd Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Jul 2024 11:16:29 +0100 Subject: [PATCH 125/152] Comment individual rules --- certora/specs/Liquidation.spec | 65 ++++++++++------------------------ 1 file changed, 18 insertions(+), 47 deletions(-) diff --git a/certora/specs/Liquidation.spec b/certora/specs/Liquidation.spec index 65b1b386..473c0edb 100644 --- a/certora/specs/Liquidation.spec +++ b/certora/specs/Liquidation.spec @@ -1,31 +1,3 @@ -/* -CER-162 / Verify EVK-31 -If violator unhealthy, checkLiquidation returns the maximum amount of the debt -asset the liquidator is allowed to liquidate (maxRepay) in exchange for the -returned maximum amount of collateral shares from violator (maxYield). - -If violator healthy, checkLiquidation returns maxRepay and maxYield as 0. - -Unless violator healthy, considering the liquidator bonus is positive and grows -linearly as the health of the violator deteriorates, the value of maxYield is -greater than the value of maxRepay. - -If needed, checkLiquidation must limit the maxRepay as per available amount of -collateral to be seized from the violator. - -If needed, checkLiquidation must limit the maxRepay and the maxYield as per -desired amount to be repaid (desiredRepay) parameter. - -checkLiquidation must revert if: - - violator is the same account as liquidator - - collateral is not accepted - - collateral is not enabled collateral for the violator - - liability vault is not enabled as the only controller of the violator - - violator account status check is deferred - - price oracle is not configured - - price oracle is not configured -*/ - // run: https://prover.certora.com/output/65266/d21dd88f07684b01930ff44d737378d7/?anonymousKey=660fbbe1c86127afc78c999a9ddd58c156ac7dad import "Base.spec"; @@ -34,6 +6,7 @@ methods { } // passing +// If violator healthy, checkLiquidation returns maxRepay and maxYield as 0. rule checkLiquidation_healthy() { env e; address liquidator; @@ -58,6 +31,14 @@ rule checkLiquidation_healthy() { } // passing +// checkLiquidation must revert if: +// - violator is the same account as liquidator +// - collateral is not accepted +// - collateral is not enabled collateral for the violator +// - liability vault is not enabled as the only controller of the violator +// - violator account status check is deferred +// - price oracle is not configured +// - price oracle is not configured rule checkLiquidation_mustRevert { env e; address liquidator; @@ -86,6 +67,7 @@ rule checkLiquidation_mustRevert { } // Passing. Assumptions can be reduced with Euler's fix. +// The borrowing collateral value is lower than the liquidation collateral value rule getCollateralValue_borrowing_lower { env e; Liquidation.VaultCache vaultCache; @@ -105,26 +87,15 @@ rule getCollateralValue_borrowing_lower { } -// passing (though I believe this was only introduced for debugging) -rule calculateLiquidation_setViolator { - env e; - Liquidation.VaultCache vaultCache; - address liquidator; - address violator; - address collateral; - uint256 desiredRepay; - LiquidationModule.LiquidationCache liqCache = calculateLiquidationExt(e, - vaultCache, - liquidator, - violator, - collateral, - desiredRepay); - assert liqCache.violator == violator; - assert liqCache.liquidator == liquidator; - assert violator != liquidator; -} - // passed +// Liquidation must revert if: +// - the liquidator is the violator (self liquidation) +// - the collateral is not recognized +// - the collateral is not enabled +// - the vault does not control the liquidator +// - the vault does not control the violator +// - the status checks are not deferred for the violator +// - the price oracle is not configured rule liquidate_mustRevert { env e; address violator; From 8c58065cbfc7507624784cc9e089794c737d311e Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Jul 2024 11:38:56 +0100 Subject: [PATCH 126/152] delete unused summary --- certora/specs/LoadVaultSummary.spec | 60 ----------------------------- 1 file changed, 60 deletions(-) diff --git a/certora/specs/LoadVaultSummary.spec b/certora/specs/LoadVaultSummary.spec index 336ec58e..a2d9cfa3 100644 --- a/certora/specs/LoadVaultSummary.spec +++ b/certora/specs/LoadVaultSummary.spec @@ -1,9 +1,6 @@ import "./Base.spec"; methods { - // function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLLoadVault(e); function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLLoadVaultAssumeNoUpdate(e); - // function Cache.initVaultCache(BaseHarness.VaultCache memory vaultCache) internal returns (bool) with (env e) => CVLInitVaultCache(e, vaultCache); - // function Cache.initVaultCache(BaseHarness.VaultCache memory vaultCache) internal returns (bool) with (env e) => CVLInitVaultCacheSimpleCopy(e, vaultCache); function storage_lastInterestAccumulatorUpdate() external returns (uint48) envfree; function storage_cash() external returns (BaseHarness.Assets) envfree; @@ -33,63 +30,6 @@ persistent ghost newInterestBorrows(uint256) returns uint256; // not even need to model this. It can just be an uninterp function // because in the ERC4626 spec there are no rules with multiple env. -function CVLLoadVault(env e) returns BaseHarness.VaultCache { - BaseHarness.VaultCache vaultCache; - uint48 lastUpdate = storage_lastInterestAccumulatorUpdate(); - BaseHarness.Owed oldTotalBorrows = storage_totalBorrows(); - BaseHarness.Shares oldTotalShares = storage_totalShares(); - require vaultCache.cash == storage_cash(); - uint48 timestamp48 = require_uint48(e.block.timestamp); - bool updated = timestamp48 != lastUpdate; - if(updated) { - require vaultCache.lastInterestAccumulatorUpdate == timestamp48; - - // totalBorrows - uint256 interestBorrows = newInterestBorrows(e.block.timestamp); - require vaultCache.totalBorrows == require_uint144(oldTotalBorrows + interestBorrows); - - // totalShares - mathint newTotalAssets = vaultCache.cash + vaultCache.totalBorrows; - // underapproximate interesteFee as 1 (1e4 in impl) - // feeAssets is a separate variable just for readability. - uint256 feeAssets = interestBorrows; - require feeAssets < require_uint256(newTotalAssets); - if (feeAssets > 0) { - require vaultCache.totalShares == require_uint112(oldTotalShares * newTotalAssets / (newTotalAssets - feeAssets)); - } else { - require vaultCache.totalShares == oldTotalShares; - } - - // accumulatedFees - mathint accFees = storage_accumulatedFees() + - vaultCache.totalShares - oldTotalShares; - require vaultCache.accumulatedFees == require_uint112(accFees); - - // interestAccumulator - require vaultCache.interestAccumulator >= storage_interestAccumulator(); - - } else { - require vaultCache.lastInterestAccumulatorUpdate == lastUpdate; - require vaultCache.totalBorrows == oldTotalBorrows; - require vaultCache.totalShares == oldTotalShares; - require vaultCache.accumulatedFees == storage_accumulatedFees(); - require vaultCache.interestAccumulator == storage_interestAccumulator(); - } - - // unmodified values - require vaultCache.supplyCap == storage_supplyCap(); - require vaultCache.borrowCap == storage_borrowCap(); - require vaultCache.hookedOps == storage_hookedOps(); - require vaultCache.configFlags == storage_configFlags(); - require vaultCache.snapshotInitialized == storage_snapshotInitialized(); - - require vaultCache.asset == erc20; - require vaultCache.oracle == oracleAddress; - require vaultCache.unitOfAccount == unitOfAccount; - - return vaultCache; -} - function CVLLoadVaultAssumeNoUpdate(env e) returns BaseHarness.VaultCache { BaseHarness.VaultCache vaultCache; uint48 lastUpdate = storage_lastInterestAccumulatorUpdate(); From 935b8ae59b81d07ee305426fecb341e358e12c1a Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Jul 2024 13:36:27 +0100 Subject: [PATCH 127/152] Liquidation CEX fix --- certora/conf/EVault/modules/Liquidation.conf | 6 +-- certora/specs/Liquidation.spec | 47 +++++++++++++++++++- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index d6dc71d0..5f843392 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -1,14 +1,14 @@ { "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "certora/helpers/DummyERC20A.sol", "src/EVault/modules/Liquidation.sol", "certora/harness/BaseHarness.sol", + "certora/harness/EVCHarness.sol", "certora/harness/modules/LiquidationHarness.sol" ], "link": [ - "LiquidationHarness:evc=EthereumVaultConnector", + "LiquidationHarness:evc=EVCHarness", ], "verify": "LiquidationHarness:certora/specs/Liquidation.spec", "solc": "solc8.23", diff --git a/certora/specs/Liquidation.spec b/certora/specs/Liquidation.spec index 473c0edb..7ba825ff 100644 --- a/certora/specs/Liquidation.spec +++ b/certora/specs/Liquidation.spec @@ -3,6 +3,51 @@ import "Base.spec"; methods { function isRecognizedCollateralExt(address collateral) external returns (bool) envfree; + // unresolved calls that havoc all contracts + function _.isHookTarget() external => NONDET; + function _.invokeHookTarget(address caller) internal => NONDET; + function _.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal => NONDET; + function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; + function _.emitTransfer(address from, address to, uint256 value) external => NONDET; + function EVCHarness.disableController(address account) external => NONDET; + function _.computeInterestRate(address vault, uint256 cash, uint256 borrows) external => NONDET; + function _.onFlashLoan(bytes data) external => NONDET; + function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal => NONDET; + function _.enforceCollateralTransfer(address collateral, uint256 amount, + address from, address receiver) internal => NONDET; + + + function EthereumVaultConnector.checkAccountStatusInternal(address account) internal returns (bool, bytes memory) with (env e) => + CVLCheckAccountStatusInternal(e, account); + function EthereumVaultConnector.checkVaultStatusInternal(address vault) internal returns (bool, bytes memory) with(env e) => + CVLCheckVaultStatusInternal(e); + + function _.EVCRequireStatusChecks(address account) internal => NONDET; +} + + +// This returns an arbitrary account status of the prover's choosing. It is +// similar to NONDETing checkAccountStatus internal and is a worakround +// for the tool not supporting NONDET for byte return values. +persistent ghost bool accountStatusGhost; +function CVLCheckAccountStatusInternalBool(env e, address account) returns bool { + return accountStatusGhost; +} + +function CVLCheckAccountStatusInternal(env e, address account) returns (bool, bytes) { + return (CVLCheckAccountStatusInternalBool(e, account), + checkAccountMagicValueMemory(e)); +} + +// This is using a similar pattern as CVLCheckAcountStatusInternal +persistent ghost bool vaultStatusBool; +function CVLCheckVaultStatusInternalBool(env e) returns bool { + return vaultStatusBool; +} + +function CVLCheckVaultStatusInternal(env e) returns (bool, bytes) { + return (CVLCheckVaultStatusInternalBool(e), + checkVaultMagicValueMemory(e)); } // passing @@ -66,7 +111,7 @@ rule checkLiquidation_mustRevert { } -// Passing. Assumptions can be reduced with Euler's fix. +// Passing. // The borrowing collateral value is lower than the liquidation collateral value rule getCollateralValue_borrowing_lower { env e; From 9e38ecf025083dd9f6953accb57c6fb496f76cb5 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Jul 2024 14:12:03 +0100 Subject: [PATCH 128/152] Delete rule that never passed in RiskManager. Other fixes --- certora/conf/Cache.conf | 3 + certora/specs/Cache.spec | 7 +++ certora/specs/Liquidation.spec | 3 +- certora/specs/RiskManager.spec | 106 --------------------------------- 4 files changed, 12 insertions(+), 107 deletions(-) diff --git a/certora/conf/Cache.conf b/certora/conf/Cache.conf index adb112bd..989df4f0 100644 --- a/certora/conf/Cache.conf +++ b/certora/conf/Cache.conf @@ -18,4 +18,7 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, + "prover_args" : [ + "-timeoutCracker true" + ] } \ No newline at end of file diff --git a/certora/specs/Cache.spec b/certora/specs/Cache.spec index 3c0c0efe..b0d2c55b 100644 --- a/certora/specs/Cache.spec +++ b/certora/specs/Cache.spec @@ -23,6 +23,13 @@ rule updateVault_no_unexpected_reverts { // newTotalBorrows assigment, prevent divide by zero require getInterestAcc(e) > 0; + // typecast of newAccumulatedFees + // Also MAX_SANE_AMOUNT is not a sufficient bound for this + // (because the bounded var is from storage not the new accumulated fees) + // https://prover.certora.com/output/65266/8c53d45891374c4692ea7597de239ba1?anonymousKey=551bfa1d1460c56f30002f5de8aeab4bd49a0fcb + require getAccumulatedFees(e) < 1267650600228229401496703205375; + + updateVaultExt@withrevert(e); assert !lastReverted; } \ No newline at end of file diff --git a/certora/specs/Liquidation.spec b/certora/specs/Liquidation.spec index 7ba825ff..7526b73e 100644 --- a/certora/specs/Liquidation.spec +++ b/certora/specs/Liquidation.spec @@ -1,4 +1,5 @@ -// run: https://prover.certora.com/output/65266/d21dd88f07684b01930ff44d737378d7/?anonymousKey=660fbbe1c86127afc78c999a9ddd58c156ac7dad +// Passing +// run: https://prover.certora.com/output/65266/5f1f37520d824e1aa7ab738a0147745e?anonymousKey=9521c3759d1f018559d571cd2a1502b04504399d import "Base.spec"; methods { diff --git a/certora/specs/RiskManager.spec b/certora/specs/RiskManager.spec index ba3eca62..d12e33f9 100644 --- a/certora/specs/RiskManager.spec +++ b/certora/specs/RiskManager.spec @@ -1,77 +1,3 @@ -/* -//----------------------------------------------------------------------------- -// accountLiquidity -//----------------------------------------------------------------------------- -For a given account, accountLiquidity calculates and returns the sum of risk -adjusted values of enabled, and accepted, collaterals and the value of -liability. - -If liquidation parameter is true, the risk adjusted value of collateral is the -value of collateral multiplied by the current LTV factor calculated using the -original LTV factor, the target LTV and ramp duration (assuming the LTV factor -changes linearly from the original LTV to the target LTV in ramp duration time). - -If liquidation parameter is false, the risk adjusted value of collateral is the -value of collateral multiplied by the target LTV. - -accountLiquidity must revert if: - - liability vault is not enabled as the only controller of the account - - price oracle is not configured - -//----------------------------------------------------------------------------- -// accountLiquidityFull -//----------------------------------------------------------------------------- -For a given account, accountLiquidityFull calculates and returns the risk -adjusted values of enabled, and accepted, collaterals and the value of -liability. - -If liquidation parameter is true, the risk adjusted value of collateral is the -value of collateral multiplied by the current LTV factor calculated using the -original LTV factor, the target LTV and ramp duration (assuming the LTV factor -changes linearly from the original LTV to the target LTV in ramp duration time). - -If liquidation parameter is false, the risk adjusted value of collateral is the -value of collateral multiplied by the target LTV. - -accountLiquidityFull must revert if: - - liability vault is not enabled as the only controller of the account - - price oracle is not configured - - -//----------------------------------------------------------------------------- -// checkAccountStatus -//----------------------------------------------------------------------------- -If the authenticated account does not have an outstanding liability, -disableController disables liability vault as a controller for the authenticated -account. disableController must revert if the authenticated account has an -outstanding liability. - -If account healthy, considering the risk adjusted value of collateral is the -value of collateral multiplied by the target LTV, checkAccountStatus returns the -selector of itself. - -checkAccountStatus must revert if: - - not called by the EVC - - not called when checks in progress - - account unhealthy - -//----------------------------------------------------------------------------- -// checkVaultStatus -//----------------------------------------------------------------------------- -If vault status is valid, checkVaultStatus updates the interest rate, clears the -snapshot (if created) and returns the selector of itself. - -The interest rate is updated by calling into the configured interest rate model -contract and cannot exceed the MAX_ALLOWED_INTEREST_RATE. If the interest rate -model contract is not configured OR it reverts, the interest rate must remain -unchanged. - -checkVaultStatus must revert if: - - not called by the EVC - - not called when checks in progress - - vault status invalid - */ - import "Base.spec"; // run: https://prover.certora.com/output/65266/4d1ba56cfd3c4aefbe2661e07fd5c95c/?anonymousKey=800abae52d40b2758c3f1f8c8a42ff82025533cd @@ -82,31 +8,6 @@ methods { } -// counterexamle. -rule liquidations_equal_for_one { - env e; - calldataarg args; - address account; - bool liquidation; - - uint256 collateralValue; - uint256 liabilityValue; - - require oracleAddress != 0; - require unitOfAccount != 0; - - address[] collaterals = getCollateralsExt(e, account); - require collaterals.length == 1; - (collateralValue, liabilityValue) = accountLiquidity(e, account, liquidation); - address[] collaterals_full; - uint256[] collateralValues; - uint256 liabilityValue_full; - (collaterals_full, collateralValues, liabilityValue_full) = accountLiquidityFull(e, account, liquidation); - assert collateralValue == collateralValues[1]; - assert liabilityValue == liabilityValue_full; -} - - // passing: https://prover.certora.com/output/65266/8b94c232c4b14e3aab917cd7e94d501c/?anonymousKey=27f680520b4d7cbb9f387563d3f1bb45de8fc9a7 rule ltv_borrowing_lower { env e; @@ -195,10 +96,3 @@ rule checkVaultStatusMustRevert { assert e.msg.sender == evc; assert checksInProgress; } - -rule sanity (method f) { - env e; - calldataarg args; - f(e, args); - satisfy true; -} \ No newline at end of file From 92c11b5c009d7f073a8dd5e411394bfe2f0e6ce8 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Jul 2024 15:25:18 +0100 Subject: [PATCH 129/152] dustFavorsTheHouseAssets rule passing --- certora/conf/Cache.conf | 4 +--- certora/specs/VaultERC4626.spec | 30 +++++++++++++++++------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/certora/conf/Cache.conf b/certora/conf/Cache.conf index 989df4f0..659f7ff4 100644 --- a/certora/conf/Cache.conf +++ b/certora/conf/Cache.conf @@ -18,7 +18,5 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, - "prover_args" : [ - "-timeoutCracker true" - ] + "smt_timeout": "7000" } \ No newline at end of file diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index c777eb74..cde4a9f6 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -63,9 +63,6 @@ methods { function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLSafeTransferFrom(e, token, from, to, value) expect void; function _.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal => NONDET; function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; - // Type Conversions - // function _.toShares(uint256 amount) internal => CVLToShares(amount) expect (BaseHarness.Shares); - // function _.toAssets(uint256 amount) internal => CVLToAssets(amount) expect (BaseHarness.Assets); // This is NONDET to help avoid timeouts. It should be safe // to NONDET since it is a private view function. function _.resolve(Vault.AmountCap self) internal => NONDET; @@ -169,14 +166,6 @@ rule conversionWeakIntegrity() { "converting assets to shares then back to assets must return assets less than or equal to the original amount"; } -rule convertToCorrectness(uint256 amount, uint256 shares) -{ - env e; - assert amount >= convertToAssets(e, convertToShares(e, amount)); - assert shares >= convertToShares(e, convertToAssets(e, shares)); -} - - //////////////////////////////////////////////////////////////////////////////// //// # Unit Test ///// //////////////////////////////////////////////////////////////////////////////// @@ -331,6 +320,23 @@ rule dustFavorsTheHouse(uint assetsIn ) assert balanceAfter >= balanceBefore; } +// passing: +// run: https://prover.certora.com/output/65266/16c756cc79054db2822d8d77cd7d157b?anonymousKey=ab0d69f0506e327db1fd9180bf8b0259a7bf1f7b +rule dustFavorsTheHouseAssets(uint assetsIn ) +{ + env e; + + require e.msg.sender != currentContract; + safeAssumptions(e,e.msg.sender,e.msg.sender); + uint256 totalAssetsBefore = totalAssets(e); + + uint shares = deposit(e,assetsIn, e.msg.sender); + uint assetsOut = redeem(e,shares,e.msg.sender,e.msg.sender); + uint256 totalAssetsAfter = totalAssets(e); + + assert totalAssetsAfter >= totalAssetsBefore; +} + //////////////////////////////////////////////////////////////////////////////// //// # Risk Analysis ///////// //////////////////////////////////////////////////////////////////////////////// @@ -347,8 +353,6 @@ invariant vaultSolvency(env e) } } - - rule redeemingAllValidity() { env e; address owner; From dfb8b6ad5d789ba4e74621e77a01e34a4f4cfbb6 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Jul 2024 15:25:33 +0100 Subject: [PATCH 130/152] Add conf file for dustFavorsTheHouseAssets --- ...VaultERC4626-dustFavorsTheHouseAssets.conf | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 certora/conf/ERC4626Split/VaultERC4626-dustFavorsTheHouseAssets.conf diff --git a/certora/conf/ERC4626Split/VaultERC4626-dustFavorsTheHouseAssets.conf b/certora/conf/ERC4626Split/VaultERC4626-dustFavorsTheHouseAssets.conf new file mode 100644 index 00000000..11e17588 --- /dev/null +++ b/certora/conf/ERC4626Split/VaultERC4626-dustFavorsTheHouseAssets.conf @@ -0,0 +1,32 @@ +{ + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol", + ], + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", + "solc": "solc8.23", + "rule_sanity": "basic", + "msg": "Vault ERC4626", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule": ["dustFavorsTheHouseAssets",], + "parametric_contracts": ["ERC4626Harness"], + "build_cache": true, + "prover_version" : "master", + // Performance tuning options below this line + "solc_via_ir": true, + "solc_optimize": "10000", + "optimistic_loop": true, + "loop_iter": "2", + "disable_auto_cache_key_gen": true, + "fe_version": "latest", + "smt_timeout": "7200" +} + From 0db0850bf4e65bceced329f5ed3714886eaaec11 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 22 Jul 2024 16:30:46 +0100 Subject: [PATCH 131/152] Merge in Alex rules --- certora/conf/EVault/modules/Governance.conf | 11 ++- certora/harness/modules/GovernanceHarness.sol | 30 +++++- certora/specs/Governance.spec | 99 +++++++++++++++++++ 3 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 certora/specs/Governance.spec diff --git a/certora/conf/EVault/modules/Governance.conf b/certora/conf/EVault/modules/Governance.conf index 0b2e0d37..38e47242 100644 --- a/certora/conf/EVault/modules/Governance.conf +++ b/certora/conf/EVault/modules/Governance.conf @@ -1,18 +1,19 @@ { "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "certora/helpers/DummyERC20A.sol", "src/ProtocolConfig/ProtocolConfig.sol", "src/EVault/modules/Governance.sol", "certora/harness/BaseHarness.sol", + "certora/harness/EVCHarness.sol", "certora/harness/modules/GovernanceHarness.sol", ], "link": [ "GovernanceHarness:protocolConfig=ProtocolConfig", - "GovernanceHarness:evc=EthereumVaultConnector" + "GovernanceHarness:evc=EVCHarness" ], - "verify": "GovernanceHarness:certora/specs/benchmarking/Benchmarking.spec", + "verify": "GovernanceHarness:certora/specs/Governance.spec", + "parametric_contracts": ["GovernanceHarness"], "solc": "solc8.23", "rule_sanity": "basic", "msg": "Governance benchmarking", @@ -20,9 +21,11 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "prover_version": "master", // Performance tuning options below this line "solc_via_ir": true, "solc_optimize": "10000", "optimistic_loop": true, "loop_iter": "2", + "smt_timeout":"7200", } \ No newline at end of file diff --git a/certora/harness/modules/GovernanceHarness.sol b/certora/harness/modules/GovernanceHarness.sol index d8267a7b..fe9fbee2 100644 --- a/certora/harness/modules/GovernanceHarness.sol +++ b/certora/harness/modules/GovernanceHarness.sol @@ -2,8 +2,36 @@ pragma solidity ^0.8.0; import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import "../../../certora/harness/AbstractBaseHarness.sol"; +import "../../../src/EVault/modules/RiskManager.sol"; import "../../../src/EVault/modules/Governance.sol"; -contract GovernanceHarness is Governance, AbstractBaseHarness { +contract GovernanceHarness is Governance, AbstractBaseHarness, RiskManagerModule{ constructor(Integrations memory integrations) Governance (integrations) {} + + function getAccountBalance(address account) external view returns (Shares balance){ + UserStorage storage user = vaultStorage.users[account]; + (balance, ) = user.getBalanceAndBalanceForwarder(); + } + + function getGovernorReciver() external view returns (address governorReceiver){ + governorReceiver = vaultStorage.feeReceiver; + } + + function getProtocolFeeConfig(address vault) external view returns (address protocolReceiver, uint16 protocolFee){ + (protocolReceiver, protocolFee) = protocolConfig.protocolFeeConfig(address(this)); + } + + function getTotalShares() external view returns (Shares){ + return vaultStorage.totalShares; + } + + function getAccumulatedFees() external view returns (Shares){ + VaultCache memory vaultCache; + initVaultCache(vaultCache); + return vaultCache.accumulatedFees; + } + + function getLastAccumulated() external view returns (uint256){ + return uint256(vaultStorage.lastInterestAccumulatorUpdate); + } } \ No newline at end of file diff --git a/certora/specs/Governance.spec b/certora/specs/Governance.spec new file mode 100644 index 00000000..8cdc830d --- /dev/null +++ b/certora/specs/Governance.spec @@ -0,0 +1,99 @@ +methods { + // Havocs here should be OK, but want to remove the linking issues from the tool + function _.calculateDTokenAddress() internal => NONDET; + // IERC20 + function _.name() external => DISPATCHER(true); + function _.symbol() external => DISPATCHER(true); + function _.decimals() external => DISPATCHER(true); + function _.totalSupply() external => DISPATCHER(true); + function _.balanceOf(address) external => DISPATCHER(true); + function _.allowance(address,address) external => DISPATCHER(true); + function _.approve(address,uint256) external => DISPATCHER(true); + function _.transfer(address,uint256) external => DISPATCHER(true); + function _.transferFrom(address,address,uint256) external => DISPATCHER(true); + + // Harness + function getAccountBalance(address) external returns (GovernanceHarness.Shares) envfree; + function getGovernorReciver() external returns (address) envfree; + function getProtocolFeeConfig(address) external returns (address, uint16) envfree; + function getTotalShares() external returns (GovernanceHarness.Shares) envfree; + function getAccumulatedFees() external returns (GovernanceHarness.Shares); + function getLastAccumulated() external returns (uint256) envfree; + + // protocolConfig + function ProtocolConfig.protocolFeeConfig(address) external returns (address, uint16) envfree; + + // unresolved calls havocing all contracts + + // We can't handle the low-level call in + // EthereumVaultConnector.checkAccountStatusInternal + // and so reroute it to RiskManager's status check with this summary. + function EthereumVaultConnector.checkVaultStatusInternal(address vault) internal returns (bool, bytes memory) with(env e) => + CVLCheckVaultStatusInternal(e); + + function _.invokeHookTarget(address caller) internal => NONDET; + + function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; + +} + + +function CVLCheckVaultStatusInternalBool(env e) returns bool { + checkVaultStatus@withrevert(e); + return !lastReverted; +} + +function CVLCheckVaultStatusInternal(env e) returns (bool, bytes) { + return (CVLCheckVaultStatusInternalBool(e), + checkVaultMagicValueMemory(e)); +} + +// Both rules pass. Run with both: +// https://prover.certora.com/output/65266/9207ef71046343e993e83f9dfa761eb1?anonymousKey=401a193cacbcbc774185473b0242384e3e8c5b4d + +// Collecting fees should increase the protocol’s and the governor’s asset (unless the governor is address(0)) +// STATUS: PASSING +rule feeCollectionIncreasesProtocolGovernerAssets(env e){ + + address protocolReceiver; + uint16 protocolFee; + protocolReceiver, protocolFee = getProtocolFeeConfig(currentContract); + require protocolFee > 0; + // require protocolReceiver != 0; + address governorReceiver = getGovernorReciver(); + require governorReceiver != 0; + + // accumulated fee is not zero + uint112 fees = getAccumulatedFees(e); + + // at fee == 1 the governor fee can be rounded down to zero and the 1 wei fee goes to the protocol + require fees >1; + + uint112 protocolReceiverBal_before = getAccountBalance(protocolReceiver); + uint112 governorReceiverBal_before = getAccountBalance(governorReceiver); + + convertFees(e); + + uint112 protocolReceiverBal_after = getAccountBalance(protocolReceiver); + uint112 governorReceiverBal_after = getAccountBalance(governorReceiver); + + assert protocolReceiverBal_after > protocolReceiverBal_before + && governorReceiverBal_after > governorReceiverBal_before, + "collecting fees should icnrease the shares of the governor and protocol"; +} + +// Collecting fees should not change total shares +// STATUS: PASSING +rule collectingFeeDoesntChangeTotalShares(env e){ + + uint112 totalShares_before = getTotalShares(); + // requiring that no fee accumulation happens to increase totalShares + require getLastAccumulated() == e.block.timestamp; + + convertFees(e); + + uint112 totalShares_after = getTotalShares(); + + assert totalShares_after == totalShares_before,"fee collection should not change total shares"; + +} \ No newline at end of file From a67739d2185dadc2749aeb38daad04472e6c2d2a Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 24 Jul 2024 11:48:17 +0100 Subject: [PATCH 132/152] Mainly delete dead code and unused confs --- certora/conf/EVault/DToken.conf | 20 ---- certora/conf/EVault/EVault.conf | 24 ---- certora/conf/EVault/modules/Borrowing.conf | 27 ----- certora/conf/EVault/modules/Initialize.conf | 24 ---- .../conf/EVault/modules/ModuleDispatch.conf | 16 --- certora/conf/EVault/modules/Token.conf | 25 ---- certora/conf/GenericFactory/BeaconProxy.conf | 27 ----- .../conf/GenericFactory/GenericFactory.conf | 27 ----- .../GenericFactory/MetaProxyDeployer.conf | 27 ----- certora/conf/ProtocolConfig.conf | 27 ----- .../interestRateModels/BaseIRMLinearKink.conf | 27 ----- .../conf/interestRateModels/IRMClassLido.conf | 27 ----- .../interestRateModels/IRMClassMajor.conf | 27 ----- certora/specs/Vault.spec | 113 +----------------- certora/specs/benchmarking/Benchmarking.spec | 82 ------------- 15 files changed, 5 insertions(+), 515 deletions(-) delete mode 100644 certora/conf/EVault/DToken.conf delete mode 100644 certora/conf/EVault/EVault.conf delete mode 100644 certora/conf/EVault/modules/Borrowing.conf delete mode 100644 certora/conf/EVault/modules/Initialize.conf delete mode 100644 certora/conf/EVault/modules/ModuleDispatch.conf delete mode 100644 certora/conf/EVault/modules/Token.conf delete mode 100644 certora/conf/GenericFactory/BeaconProxy.conf delete mode 100644 certora/conf/GenericFactory/GenericFactory.conf delete mode 100644 certora/conf/GenericFactory/MetaProxyDeployer.conf delete mode 100644 certora/conf/ProtocolConfig.conf delete mode 100644 certora/conf/interestRateModels/BaseIRMLinearKink.conf delete mode 100644 certora/conf/interestRateModels/IRMClassLido.conf delete mode 100644 certora/conf/interestRateModels/IRMClassMajor.conf delete mode 100644 certora/specs/benchmarking/Benchmarking.spec diff --git a/certora/conf/EVault/DToken.conf b/certora/conf/EVault/DToken.conf deleted file mode 100644 index e34a4737..00000000 --- a/certora/conf/EVault/DToken.conf +++ /dev/null @@ -1,20 +0,0 @@ -{ - "files": [ - "certora/harness/EVaultHarness.sol", - "src/EVault/DToken.sol" - ], - "link": [ - "DToken:eVault=EVaultHarness", - ], - "verify": "DToken:certora/specs/EVault/modules/DToken.spec", - "solc": "solc8.23", - "solc_via_ir": true, - "solc_optimize": "10000", - "rule_sanity": "basic", - "msg": "DToken benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "parametric_contracts": ["DToken"], -} \ No newline at end of file diff --git a/certora/conf/EVault/EVault.conf b/certora/conf/EVault/EVault.conf deleted file mode 100644 index c28d2c6d..00000000 --- a/certora/conf/EVault/EVault.conf +++ /dev/null @@ -1,24 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "certora/harness/EVaultHarness.sol", - ], - "link" : [ - "EVaultHarness:evc=EthereumVaultConnector", - ], - "parametric_contracts": ["EVaultHarness"], - "verify": "EVaultHarness:certora/specs/benchmarking/EVault/EVault.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "solc_via_ir": true, - "solc_optimize": "10000", - "msg": "EVault benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "optimistic_loop": true, - "optimistic_hashing": true, - "loop_iter": "2", -} \ No newline at end of file diff --git a/certora/conf/EVault/modules/Borrowing.conf b/certora/conf/EVault/modules/Borrowing.conf deleted file mode 100644 index 539fb293..00000000 --- a/certora/conf/EVault/modules/Borrowing.conf +++ /dev/null @@ -1,27 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", - "src/EVault/modules/Borrowing.sol", - "certora/harness/BaseHarness.sol", - "certora/harness/modules/BorrowingHarness.sol", - ], - "link": [ - "BorrowingHarness:evc=EthereumVaultConnector", - ], - "verify": "BorrowingHarness:certora/specs/benchmarking/Benchmarking.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "msg": "Borrowing benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "parametric_contracts": ["BorrowingHarness"], - // Performance tuning options below this line - "solc_via_ir": true, - "solc_optimize": "10000", - "optimistic_loop": true, - "loop_iter": "2", -} \ No newline at end of file diff --git a/certora/conf/EVault/modules/Initialize.conf b/certora/conf/EVault/modules/Initialize.conf deleted file mode 100644 index c491daf3..00000000 --- a/certora/conf/EVault/modules/Initialize.conf +++ /dev/null @@ -1,24 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", - "src/EVault/modules/Initialize.sol", - "certora/harness/BaseHarness.sol", - "certora/harness/modules/InitializeHarness.sol", - ], - "link": [ - "InitializeHarness:evc=EthereumVaultConnector", - ], - "verify": "InitializeHarness:certora/specs/benchmarking/Benchmarking.spec", - "solc": "solc8.23", - "solc_via_ir": true, - "solc_optimize": "10000", - "rule_sanity": "basic", - "msg": "Initialize benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "parametric_contracts": ["InitializeHarness"], -} \ No newline at end of file diff --git a/certora/conf/EVault/modules/ModuleDispatch.conf b/certora/conf/EVault/modules/ModuleDispatch.conf deleted file mode 100644 index 8f5bb8f6..00000000 --- a/certora/conf/EVault/modules/ModuleDispatch.conf +++ /dev/null @@ -1,16 +0,0 @@ -{ - "files": [ - "certora/harness/ModuleDispatchHarness.sol" - ], - "verify": "ModuleDispatchHarness:certora/specs/benchmarking/Benchmarking.spec", - "solc": "solc8.23", - "solc_via_ir": true, - "solc_optimize": "10000", - "rule_sanity": "basic", - "msg": "Module Dispatch benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "parametric_contracts": ["ModuleDispatch"], -} \ No newline at end of file diff --git a/certora/conf/EVault/modules/Token.conf b/certora/conf/EVault/modules/Token.conf deleted file mode 100644 index a34610db..00000000 --- a/certora/conf/EVault/modules/Token.conf +++ /dev/null @@ -1,25 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", - "src/EVault/modules/Token.sol", - "certora/harness/BaseHarness.sol", - "certora/harness/modules/TokenHarness.sol", - ], - "link" : [ - "TokenHarness:evc=EthereumVaultConnector", - ], - "parametric_contracts": ["TokenHarness"], - "verify": "TokenHarness:certora/specs/benchmarking/Benchmarking.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "msg": "Token benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - // Performance tuning options below this line - "solc_via_ir": true, - "solc_optimize": "10000", -} \ No newline at end of file diff --git a/certora/conf/GenericFactory/BeaconProxy.conf b/certora/conf/GenericFactory/BeaconProxy.conf deleted file mode 100644 index 87123d87..00000000 --- a/certora/conf/GenericFactory/BeaconProxy.conf +++ /dev/null @@ -1,27 +0,0 @@ -{ - "files": [ - "src/GenericFactory/BeaconProxy.sol" - ], - "verify": "BeaconProxy:certora/specs/benchmarking/Benchmarking.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "assert_autofinder_success" : true, - // "solc_via_ir": true, - // "solc_optimize": "10000", - "msg": "BeaconProxy benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] -} \ No newline at end of file diff --git a/certora/conf/GenericFactory/GenericFactory.conf b/certora/conf/GenericFactory/GenericFactory.conf deleted file mode 100644 index a106cd60..00000000 --- a/certora/conf/GenericFactory/GenericFactory.conf +++ /dev/null @@ -1,27 +0,0 @@ -{ - "files": [ - "src/GenericFactory/GenericFactory.sol" - ], - "verify": "GenericFactory:certora/specs/benchmarking/Benchmarking.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "assert_autofinder_success" : true, - // "solc_via_ir": true, - // "solc_optimize": "10000", - "msg": "GenericFactory benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] -} \ No newline at end of file diff --git a/certora/conf/GenericFactory/MetaProxyDeployer.conf b/certora/conf/GenericFactory/MetaProxyDeployer.conf deleted file mode 100644 index 709ca391..00000000 --- a/certora/conf/GenericFactory/MetaProxyDeployer.conf +++ /dev/null @@ -1,27 +0,0 @@ -{ - "files": [ - "src/GenericFactory/MetaProxyDeployer.sol" - ], - "verify": "MetaProxyDeployer:certora/specs/benchmarking/Benchmarking.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "assert_autofinder_success" : true, - // "solc_via_ir": true, - // "solc_optimize": "10000", - "msg": "MetaProxyDeployer benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] -} \ No newline at end of file diff --git a/certora/conf/ProtocolConfig.conf b/certora/conf/ProtocolConfig.conf deleted file mode 100644 index 4f75ec1b..00000000 --- a/certora/conf/ProtocolConfig.conf +++ /dev/null @@ -1,27 +0,0 @@ -{ - "files": [ - "src/ProtocolConfig/ProtocolConfig.sol" - ], - "verify": "ProtocolConfig:certora/specs/benchmarking/Benchmarking.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "assert_autofinder_success" : true, - // "solc_via_ir": true, - // "solc_optimize": "10000", - "msg": "ProtocolConfig benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] -} \ No newline at end of file diff --git a/certora/conf/interestRateModels/BaseIRMLinearKink.conf b/certora/conf/interestRateModels/BaseIRMLinearKink.conf deleted file mode 100644 index ea3a3f42..00000000 --- a/certora/conf/interestRateModels/BaseIRMLinearKink.conf +++ /dev/null @@ -1,27 +0,0 @@ -{ - "files": [ - "src/interestRateModels/BaseIRMLinearKink.sol" - ], - "verify": "BaseIRMLinearKink:certora/specs/benchmarking/Benchmarking.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "assert_autofinder_success" : true, - // "solc_via_ir": true, - // "solc_optimize": "10000", - "msg": "BaseIRMLinearKink benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] -} \ No newline at end of file diff --git a/certora/conf/interestRateModels/IRMClassLido.conf b/certora/conf/interestRateModels/IRMClassLido.conf deleted file mode 100644 index d13377d3..00000000 --- a/certora/conf/interestRateModels/IRMClassLido.conf +++ /dev/null @@ -1,27 +0,0 @@ -{ - "files": [ - "src/interestRateModels/IRMClassLido.sol" - ], - "verify": "IRMClassLido:certora/specs/benchmarking/Benchmarking.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "assert_autofinder_success" : true, - // "solc_via_ir": true, - // "solc_optimize": "10000", - "msg": "IRMClassLido benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] -} \ No newline at end of file diff --git a/certora/conf/interestRateModels/IRMClassMajor.conf b/certora/conf/interestRateModels/IRMClassMajor.conf deleted file mode 100644 index c43c1dc6..00000000 --- a/certora/conf/interestRateModels/IRMClassMajor.conf +++ /dev/null @@ -1,27 +0,0 @@ -{ - "files": [ - "src/interestRateModels/IRMClassMajor.sol" - ], - "verify": "IRMClassMajor:certora/specs/benchmarking/Benchmarking.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "assert_autofinder_success" : true, - // "solc_via_ir": true, - // "solc_optimize": "10000", - "msg": "IRMClassMajor benchmarking", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "prover_args": [ - "-verifyCache", - "-verifyTACDumps", - "-testMode", - "-checkRuleDigest", - "-callTraceHardFail on" - ], - "java_args": [ - "-ea", - "-Dlevel.setup.helpers=info" - ] -} \ No newline at end of file diff --git a/certora/specs/Vault.spec b/certora/specs/Vault.spec index 9d0cfbbc..b785de7a 100644 --- a/certora/specs/Vault.spec +++ b/certora/specs/Vault.spec @@ -1,105 +1,3 @@ -/* -CER-132 / EVK-45 convertToAssets -returns the amount of assets (rounding down) -that the vault would exchange for the amount of shares provided. The function -must make calculations based on the total shares amount, the amount of assets -held by the vault and the amount of liabilities issued. It must be ensured that -the vault implements the function in a manipulation-resistant manner. -*/ - -/* -CER-133 / EVK-46 convertToShares -returns the amount of shares (rounding down) -that the vault would exchange for the amount of assets provided. The function -must make calculations based on the total shares amount, the amount of assets -held by the vault and the amount of liabilities issued. It must be ensured that -the vault implements the function in a manipulation-resistant manner. -*/ - -/* -CER-134 / EVK-47 deposit -If operation enabled, deposit mints vault shares (rounding -down) to receiver by depositing exactly assets of underlying tokens pulled from -the authenticated account. - -If balance forwarding enabled for the receiver address, the balance tracker hook -must be called with the new shares balance of receiver. This operation is -always called through the EVC. This operation schedules the vault status check. - -This operation affects: -- shares balance of the receiver account -- total shares balance -- total balance of the underlying assets held by the vault -*/ - -/* -CER-135 / EVK-48 mint -If operation enabled, mint mints exactly shares vault shares to receiver by -depositing corresponding amount of underlying tokens (rounding up) pulled from -the authenticated account. If balance forwarding enabled for the receiver -address, the balance tracker hook must be called with the new shares balance of -receiver. This operation is always called through the EVC. This operation -schedules the vault status check. -This operation affects: -- shares balance of the receiver account -- total shares balance -- total balance of the underlying assets held by the vault -*/ - -/* -CER-136 / EVK-49 withdraw -If operation enabled, withdraw burns vault shares (rounding up) from owner and -sends exactly assets of underlying tokens to receiver. If the owner account does -not belong to the authenticated account, the amount of shares burned is a -subject to the ERC20 allowance check. -If balance forwarding enabled for the owner address, the balance tracker hook -must be called with the new shares balance of owner. -If asset receiver validation enabled, this operation must protect user from -sending assets to a virtual account. -This operation is always called through the EVC. -This operation schedules the account status check on the owner address. -This operation schedules the vault status check. -This operation affects: - - shares balance of the owner account - - total shares balance - - total balance of the underlying assets held by the vault -*/ - -/* -CER-137 / EVK-50 redeem -If operation enabled, redeem burns exactly shares vault shares from owner and -sends corresponding amount of underlying tokens (rounding down) to receiver. If -the owner account does not belong to the authenticated account, the amount of -shares burned is a subject to the ERC20 allowance check. -If balance forwarding enabled for the owner address, the balance tracker hook -must be called with the new shares balance of owner. -If asset receiver validation enabled, this operation must protect user from -sending assets to a virtual account. -This operation is always called through the EVC. -This operation schedules the account status check on the owner address. -This operation schedules the vault status check. - -This operation affects: - - shares balance of the owner account - - total shares balance - - total balance of the underlying assets held by the vault -*/ - -/* -CER-138 / EVK-51 skim -If operation enabled, skim mints vault shares (rounding down) to receiver by -assuming that the excess of the underlying tokens, that may occur due to -internal balance tracking, belongs to the receiver. -If balance forwarding enabled for the receiver address, the balance tracker hook -must be called with the new shares balance of receiver. -This operation is always called through the EVC. -This operation schedules the vault status check. -This operation affects: - - shares balance of the receiver account - - total shares balance - - total balance of the underlying assets held by the vault -*/ - // all passing // run: https://prover.certora.com/output/65266/4e6a6aeb5af9454e87e8245498b0207d?anonymousKey=e924e53a6ff7a84beab51de18671463a166885b4 methods { @@ -159,9 +57,10 @@ function CVLCalledBalanceForwarder(address account, uint256 newAccountBalance) { calledForwarder = true; } +// If balance forwarding is enabled and OP is not disabled, +// the Vault methods will call the balance forwarding hook // NOTE: these rules are not parametric because they need // to constrain to the case that the result is nonzero. - rule balance_forwarding_called_deposit { env e; uint256 amount; @@ -184,6 +83,8 @@ rule balance_forwarding_called_deposit { assert result !=0 => calledForwarder; } +// If balance forwarding is enabled and OP is not disabled, +// mint will call the balance forwarding hook rule balance_forwarding_called_mint { env e; uint256 amount; @@ -268,8 +169,4 @@ rule balance_forwarding_called_skim { // balance forwarding hook is called assert result != 0 => calledForwarder; -} - -// NOTE: disabled ops do not cause a revert. They cause the call -// to act like a NOP in callHook (in initOperation). So we could prove -// that the actual call does not happen by writing a "hook" on invokeTarget \ No newline at end of file +} \ No newline at end of file diff --git a/certora/specs/benchmarking/Benchmarking.spec b/certora/specs/benchmarking/Benchmarking.spec deleted file mode 100644 index dc0798e5..00000000 --- a/certora/specs/benchmarking/Benchmarking.spec +++ /dev/null @@ -1,82 +0,0 @@ -methods { - // Havocs here should be OK, but want to remove the linking issues from the tool - function _.calculateDTokenAddress() internal => NONDET; - // IERC20 - function _.name() external => DISPATCHER(true); - function _.symbol() external => DISPATCHER(true); - function _.decimals() external => DISPATCHER(true); - function _.totalSupply() external => DISPATCHER(true); - function _.balanceOf(address) external => DISPATCHER(true); - function _.allowance(address,address) external => DISPATCHER(true); - function _.approve(address,uint256) external => DISPATCHER(true); - function _.transfer(address,uint256) external => DISPATCHER(true); - function _.transferFrom(address,address,uint256) external => DISPATCHER(true); -} - -use builtin rule sanity; -use builtin rule hasDelegateCalls; -use builtin rule msgValueInLoopRule; -use builtin rule viewReentrancy; - - -rule noRevert(method f) { - env e; - calldataarg arg; - require e.msg.value == 0; - f@withrevert(e, arg); - assert !lastReverted; -} - - -rule alwaysRevert(method f) { - env e; - calldataarg arg; - f@withrevert(e, arg); - assert lastReverted; -} - -/* -This rule find which functions that can be called, may fail due to someone else calling a function right before. - -This is n expensive rule - might fail on the demo site on big contracts -*/ -rule simpleFrontRunning(method f, address privileged) filtered { f-> !f.isView } { - env e1; - calldataarg arg; - require e1.msg.sender == privileged; - storage initialStorage = lastStorage; - f(e1, arg); - bool firstSucceeded = !lastReverted; - env e2; - calldataarg arg2; - require e2.msg.sender != e1.msg.sender; - f(e2, arg2) at initialStorage; - f@withrevert(e1, arg); - bool succeeded = !lastReverted; - assert succeeded; -} - - -/* -This rule find which functions are privileged. -A function is privileged if there is only one address that can call it. - -The rules finds this by finding which functions can be called by two different users. -*/ -rule privilegedOperation(method f, address privileged) { - env e1; - calldataarg arg; - require e1.msg.sender == privileged; - - storage initialStorage = lastStorage; - f@withrevert(e1, arg); // privileged succeeds executing candidate privileged operation. - bool firstSucceeded = !lastReverted; - - env e2; - calldataarg arg2; - require e2.msg.sender != privileged; - f@withrevert(e2, arg2) at initialStorage; // unprivileged - bool secondSucceeded = !lastReverted; - - assert !(firstSucceeded && secondSucceeded); -} From d8e07b4e64a7dcaeb12237462f816a759f2072b4 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 24 Jul 2024 11:49:57 +0100 Subject: [PATCH 133/152] delete some commented code --- certora/harness/ERC4626Harness.sol | 2 -- certora/harness/EVCHarness.sol | 2 -- certora/harness/healthStatus/BorrowingHSHarness.sol | 1 - certora/harness/healthStatus/GovernanceHSHarness.sol | 1 - certora/harness/healthStatus/InitializeHSHarness.sol | 1 - certora/harness/healthStatus/LiquidationHSHarness.sol | 1 - certora/harness/healthStatus/TokenHSHarness.sol | 1 - certora/harness/healthStatus/VaultHSHarness.sol | 1 - 8 files changed, 10 deletions(-) diff --git a/certora/harness/ERC4626Harness.sol b/certora/harness/ERC4626Harness.sol index 70661da7..c2f1f962 100644 --- a/certora/harness/ERC4626Harness.sol +++ b/certora/harness/ERC4626Harness.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -// import {IERC20} from "../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import "../../certora/harness/AbstractBaseHarness.sol"; import "../../src/EVault/modules/Vault.sol"; import "../../src/EVault/modules/Token.sol"; -// import "../../src/EVault/shared/types/Types.sol"; contract ERC4626Harness is VaultModule, TokenModule, AbstractBaseHarness { constructor(Integrations memory integrations) Base(integrations) {} diff --git a/certora/harness/EVCHarness.sol b/certora/harness/EVCHarness.sol index ddddbb31..1e2ab8f4 100644 --- a/certora/harness/EVCHarness.sol +++ b/certora/harness/EVCHarness.sol @@ -11,7 +11,5 @@ contract EVCHarness is EthereumVaultConnector { // explicitly. function checkStatusAllExt() external { checkStatusAll(SetType.Account); - // it's not needed - // checkStatusAll(SetType.Vault); } } \ No newline at end of file diff --git a/certora/harness/healthStatus/BorrowingHSHarness.sol b/certora/harness/healthStatus/BorrowingHSHarness.sol index a25a8705..2b9329ec 100644 --- a/certora/harness/healthStatus/BorrowingHSHarness.sol +++ b/certora/harness/healthStatus/BorrowingHSHarness.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import "../../../src/interfaces/IPriceOracle.sol"; -// import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import "../../../certora/harness/AbstractBaseHarness.sol"; import "../../../src/EVault/modules/RiskManager.sol"; import "../../../src/EVault/modules/Borrowing.sol"; diff --git a/certora/harness/healthStatus/GovernanceHSHarness.sol b/certora/harness/healthStatus/GovernanceHSHarness.sol index 4eeee085..5a9645f6 100644 --- a/certora/harness/healthStatus/GovernanceHSHarness.sol +++ b/certora/harness/healthStatus/GovernanceHSHarness.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import "../../../src/interfaces/IPriceOracle.sol"; -// import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import "../../../certora/harness/AbstractBaseHarness.sol"; import "../../../src/EVault/modules/RiskManager.sol"; import "../../../src/EVault/modules/Governance.sol"; diff --git a/certora/harness/healthStatus/InitializeHSHarness.sol b/certora/harness/healthStatus/InitializeHSHarness.sol index 68f94f45..691cbfea 100644 --- a/certora/harness/healthStatus/InitializeHSHarness.sol +++ b/certora/harness/healthStatus/InitializeHSHarness.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import "../../../src/interfaces/IPriceOracle.sol"; -// import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import "../../../certora/harness/AbstractBaseHarness.sol"; import "../../../src/EVault/modules/RiskManager.sol"; import "../../../src/EVault/modules/Initialize.sol"; diff --git a/certora/harness/healthStatus/LiquidationHSHarness.sol b/certora/harness/healthStatus/LiquidationHSHarness.sol index 42617366..e6479576 100644 --- a/certora/harness/healthStatus/LiquidationHSHarness.sol +++ b/certora/harness/healthStatus/LiquidationHSHarness.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import "../../../src/interfaces/IPriceOracle.sol"; -// import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import "../../../certora/harness/AbstractBaseHarness.sol"; import "../../../src/EVault/modules/RiskManager.sol"; import "../../../src/EVault/modules/Liquidation.sol"; diff --git a/certora/harness/healthStatus/TokenHSHarness.sol b/certora/harness/healthStatus/TokenHSHarness.sol index 31f6cd02..935ab6c7 100644 --- a/certora/harness/healthStatus/TokenHSHarness.sol +++ b/certora/harness/healthStatus/TokenHSHarness.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import "../../../src/interfaces/IPriceOracle.sol"; -// import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import "../../../certora/harness/AbstractBaseHarness.sol"; import "../../../src/EVault/modules/RiskManager.sol"; import "../../../src/EVault/modules/Token.sol"; diff --git a/certora/harness/healthStatus/VaultHSHarness.sol b/certora/harness/healthStatus/VaultHSHarness.sol index 143fa6c4..990f39e1 100644 --- a/certora/harness/healthStatus/VaultHSHarness.sol +++ b/certora/harness/healthStatus/VaultHSHarness.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import "../../../src/interfaces/IPriceOracle.sol"; -// import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import "../../../certora/harness/AbstractBaseHarness.sol"; import "../../../src/EVault/modules/RiskManager.sol"; import "../../../src/EVault/modules/Vault.sol"; From d13aec8e8999b79a7d0216fe07d458a1115a77ab Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 24 Jul 2024 12:29:47 +0100 Subject: [PATCH 134/152] ERC4626 script and cleanup. REAMEs for groups of confs --- certora/conf/ERC4626Split/README.md | 1 + .../VaultERC4626-dustFavorsTheHouse.conf} | 5 ++- certora/conf/EVault/modules/Vault.conf | 6 +-- .../VaultERC4626-vaultSolvency-redeem.conf | 35 ------------------ .../VaultERC4626-vaultSolvency-withdraw.conf | 35 ------------------ certora/conf/healthStatus/README.md | 4 ++ certora/scripts/runERC4626RulesSeparately.py | 37 ------------------- certora/scripts/runERC4626RulesSplitConfs.py | 31 ++++++++++++++++ 8 files changed, 43 insertions(+), 111 deletions(-) create mode 100644 certora/conf/ERC4626Split/README.md rename certora/conf/{VaultERC4626.conf => ERC4626Split/VaultERC4626-dustFavorsTheHouse.conf} (86%) delete mode 100644 certora/conf/VaultERC4626-vaultSolvency-redeem.conf delete mode 100644 certora/conf/VaultERC4626-vaultSolvency-withdraw.conf create mode 100644 certora/conf/healthStatus/README.md delete mode 100644 certora/scripts/runERC4626RulesSeparately.py create mode 100644 certora/scripts/runERC4626RulesSplitConfs.py diff --git a/certora/conf/ERC4626Split/README.md b/certora/conf/ERC4626Split/README.md new file mode 100644 index 00000000..7878fab4 --- /dev/null +++ b/certora/conf/ERC4626Split/README.md @@ -0,0 +1 @@ +To run all of these conf files easily, use the certora/scripts/runERC4626RulesSplitConfs.py \ No newline at end of file diff --git a/certora/conf/VaultERC4626.conf b/certora/conf/ERC4626Split/VaultERC4626-dustFavorsTheHouse.conf similarity index 86% rename from certora/conf/VaultERC4626.conf rename to certora/conf/ERC4626Split/VaultERC4626-dustFavorsTheHouse.conf index 08b59dd3..3786ca25 100644 --- a/certora/conf/VaultERC4626.conf +++ b/certora/conf/ERC4626Split/VaultERC4626-dustFavorsTheHouse.conf @@ -1,6 +1,7 @@ { "files": [ "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", "certora/helpers/DummyERC20A.sol", "certora/helpers/DummyERC20B.sol", "certora/harness/EVCHarness.sol", @@ -15,9 +16,10 @@ "ethereum-vault-connector=lib/ethereum-vault-connector/src", "forge-std=lib/forge-std/src" ], + "rule": ["dustFavorsTheHouse",], "parametric_contracts": ["ERC4626Harness"], "build_cache": true, - "prover_version" : "yuvalbd/CERT-6495", + "prover_version" : "master", // Performance tuning options below this line "solc_via_ir": true, "solc_optimize": "10000", @@ -25,5 +27,6 @@ "loop_iter": "2", "disable_auto_cache_key_gen": true, "fe_version": "latest", + "smt_timeout": "7200" } diff --git a/certora/conf/EVault/modules/Vault.conf b/certora/conf/EVault/modules/Vault.conf index bd4b1504..9b20d1cb 100644 --- a/certora/conf/EVault/modules/Vault.conf +++ b/certora/conf/EVault/modules/Vault.conf @@ -1,14 +1,14 @@ { "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", "lib/ethereum-vault-connector/src/ExecutionContext.sol", - "lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol", + "certora/helpers/DummyERC20A.sol", "src/EVault/modules/Vault.sol", "certora/harness/BaseHarness.sol", + "certora/harness/EVCHarness.sol", "certora/harness/modules/VaultHarness.sol", ], "link" : [ - "VaultHarness:evc=EthereumVaultConnector", + "VaultHarness:evc=EVCHarness", ], "verify": "VaultHarness:certora/specs/Vault.spec", "solc": "solc8.23", diff --git a/certora/conf/VaultERC4626-vaultSolvency-redeem.conf b/certora/conf/VaultERC4626-vaultSolvency-redeem.conf deleted file mode 100644 index ea753642..00000000 --- a/certora/conf/VaultERC4626-vaultSolvency-redeem.conf +++ /dev/null @@ -1,35 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "src/EVault/modules/Vault.sol", - "certora/helpers/DummyERC20A.sol", - "certora/helpers/DummyERC20B.sol", - "certora/harness/EVCHarness.sol", - "certora/harness/BaseHarness.sol", - "certora/harness/ERC4626Harness.sol", - ], - "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "msg": "Vault ERC4626", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "rule": ["vaultSolvency"], - "method": "redeem(uint256,address,address)", - "parametric_contracts": ["ERC4626Harness"], - "build_cache": true, - "prover_version" : "master", - // Performance tuning options below this line - "solc_via_ir": true, - "solc_optimize": "10000", - "optimistic_loop": true, - "loop_iter": "2", - "prover_args" : [ - "-smt_nonLinearArithmetic false", - "-timeoutCracker true", - ], - "smt_timeout": "7200", -} - diff --git a/certora/conf/VaultERC4626-vaultSolvency-withdraw.conf b/certora/conf/VaultERC4626-vaultSolvency-withdraw.conf deleted file mode 100644 index 149c7849..00000000 --- a/certora/conf/VaultERC4626-vaultSolvency-withdraw.conf +++ /dev/null @@ -1,35 +0,0 @@ -{ - "files": [ - "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", - "src/EVault/modules/Vault.sol", - "certora/helpers/DummyERC20A.sol", - "certora/helpers/DummyERC20B.sol", - "certora/harness/EVCHarness.sol", - "certora/harness/BaseHarness.sol", - "certora/harness/ERC4626Harness.sol", - ], - "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", - "solc": "solc8.23", - "rule_sanity": "basic", - "msg": "Vault ERC4626", - "packages": [ - "ethereum-vault-connector=lib/ethereum-vault-connector/src", - "forge-std=lib/forge-std/src" - ], - "rule": ["vaultSolvency"], - "method": "withdraw(uint256,address,address)", - "parametric_contracts": ["ERC4626Harness"], - "build_cache": true, - "prover_version" : "master", - // Performance tuning options below this line - "solc_via_ir": true, - "solc_optimize": "10000", - "optimistic_loop": true, - "loop_iter": "2", - "prover_args" : [ - "-smt_nonLinearArithmetic false", - "-timeoutCracker true", - ], - "smt_timeout": "7200", -} - diff --git a/certora/conf/healthStatus/README.md b/certora/conf/healthStatus/README.md new file mode 100644 index 00000000..eb3aebbb --- /dev/null +++ b/certora/conf/healthStatus/README.md @@ -0,0 +1,4 @@ +Note that for most modules the spec HealthStatusInvariant.spec is used, but for Liquidation, +the spec needs to be split into more cases for performance reasons so it uses LiquidateHealthStatus.spec + +To run all of these configurations easily, use certora/scripts/runHealthStatusAllModules.py \ No newline at end of file diff --git a/certora/scripts/runERC4626RulesSeparately.py b/certora/scripts/runERC4626RulesSeparately.py deleted file mode 100644 index 5d79ec78..00000000 --- a/certora/scripts/runERC4626RulesSeparately.py +++ /dev/null @@ -1,37 +0,0 @@ -import argparse -import subprocess - -parser = argparse.ArgumentParser() -parser.add_argument('-M', '--batchMsg', metavar='M', type=str, nargs='?', - default='', - help='a message for all the jobs') - -rule_names = [ - "conversionOfZero", - "convertToAssetsWeakAdditivity", - "convertToSharesWeakAdditivity", - "conversionWeakMonotonicity", - "conversionWeakIntegrity", - "convertToCorrectness", - "depositMonotonicity", - "zeroDepositZeroShares", - "assetsMoreThanSupply", - "noAssetsIfNoSupply", - "noSupplyIfNoAssets", - "totalSupplyIsSumOfBalances", - "totalsMonotonicity", - "underlyingCannotChange", - "dustFavorsTheHouse", - "vaultSolvency", - "redeemingAllValidity", - "contributingProducesShares", - "onlyContributionMethodsReduceAssets", - "reclaimingProducesAssets" -] - -for name in rule_names: - args = parser.parse_args() - script = "certora/conf/VaultERC4626.conf" - command = f"certoraRun {script} --rule \"{name}\" --msg \"{name} : {args.batchMsg}\"" - print(f"runing {command}") - subprocess.run(command, shell=True) \ No newline at end of file diff --git a/certora/scripts/runERC4626RulesSplitConfs.py b/certora/scripts/runERC4626RulesSplitConfs.py new file mode 100644 index 00000000..5746009b --- /dev/null +++ b/certora/scripts/runERC4626RulesSplitConfs.py @@ -0,0 +1,31 @@ +import argparse +import subprocess + +parser = argparse.ArgumentParser() +parser.add_argument('-M', '--batchMsg', metavar='M', type=str, nargs='?', + default='', + help='a message for all the jobs') + +erc4626_confs = { + "", + "-assetsMoreThanSupply", + "-convertToAssetsWeakAdditivity", + "-convertToSharesWeakAdditivity", + "-depositMonotonicity", + "-dustFavorsTheHouse", + "-dustFavorsTheHouseAssets", + "-noAssetsIfNoSupply", + "-noSupplyIfNoAssets", + "-onlyContributionMethodsReduce", + "-totalsMonotonicity", + "-vaultSolvency-most", + "-vaultSolvency-redeem", + "-vaultSolvency-withdraw" +} + +for name in erc4626_confs: + args = parser.parse_args() + script = f"certora/conf/ERC4626Split/VaultERC4626{name}.conf" + command = f"certoraRun {script} --msg \"{name} : {args.batchMsg}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) \ No newline at end of file From 475fc05eda065e15076d3f2b0f16d96a07c9caa8 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 25 Jul 2024 09:28:24 +0100 Subject: [PATCH 135/152] setup for staging for vaultSolvency, splitting for base conf --- .../VaultERC4626-vaultSolvency-most.conf | 4 +++- .../VaultERC4626-vaultSolvency-redeem.conf | 4 +++- .../VaultERC4626-vaultSolvency-withdraw.conf | 4 +++- certora/scripts/runERC4626RulesSplitConfs.py | 22 +++++++++---------- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-most.conf b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-most.conf index d6db7472..f347bc3a 100644 --- a/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-most.conf +++ b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-most.conf @@ -19,6 +19,7 @@ "rule": ["vaultSolvency"], "parametric_contracts": ["ERC4626Harness"], "build_cache": true, + "server": "staging", // 10 hour queue "prover_version" : "master", // Performance tuning options below this line "solc_via_ir": true, @@ -28,6 +29,7 @@ "prover_args" : [ "-smt_nonLinearArithmetic false", ], - "smt_timeout": "7200", + // "smt_timeout": "7200", + "smt_timeout": "28800", } diff --git a/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-redeem.conf b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-redeem.conf index 4af76fed..a65cac9d 100644 --- a/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-redeem.conf +++ b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-redeem.conf @@ -20,6 +20,7 @@ "method": "redeem(uint256,address,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, + "server": "staging", // 10 hour queue "prover_version" : "master", // Performance tuning options below this line "solc_via_ir": true, @@ -29,6 +30,7 @@ "prover_args" : [ "-smt_nonLinearArithmetic false", ], - "smt_timeout": "7200", + // "smt_timeout": "7200", + "smt_timeout": "28800", } diff --git a/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw.conf b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw.conf index aee26141..f21e139f 100644 --- a/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw.conf +++ b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw.conf @@ -20,6 +20,7 @@ "method": "withdraw(uint256,address,address)", "parametric_contracts": ["ERC4626Harness"], "build_cache": true, + "server": "staging", // 10 hour queue "prover_version" : "master", // Performance tuning options below this line "solc_via_ir": true, @@ -29,6 +30,7 @@ "prover_args": [ "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0" ], - "smt_timeout": "7200", + // "smt_timeout": "7200", + "smt_timeout": "28800", } diff --git a/certora/scripts/runERC4626RulesSplitConfs.py b/certora/scripts/runERC4626RulesSplitConfs.py index 5746009b..7fb83168 100644 --- a/certora/scripts/runERC4626RulesSplitConfs.py +++ b/certora/scripts/runERC4626RulesSplitConfs.py @@ -7,17 +7,17 @@ help='a message for all the jobs') erc4626_confs = { - "", - "-assetsMoreThanSupply", - "-convertToAssetsWeakAdditivity", - "-convertToSharesWeakAdditivity", - "-depositMonotonicity", - "-dustFavorsTheHouse", - "-dustFavorsTheHouseAssets", - "-noAssetsIfNoSupply", - "-noSupplyIfNoAssets", - "-onlyContributionMethodsReduce", - "-totalsMonotonicity", + # "", + # "-assetsMoreThanSupply", + # "-convertToAssetsWeakAdditivity", + # "-convertToSharesWeakAdditivity", + # "-depositMonotonicity", + # "-dustFavorsTheHouse", + # "-dustFavorsTheHouseAssets", + # "-noAssetsIfNoSupply", + # "-noSupplyIfNoAssets", + # "-onlyContributionMethodsReduce", + # "-totalsMonotonicity", "-vaultSolvency-most", "-vaultSolvency-redeem", "-vaultSolvency-withdraw" From ef2bf732164a63220f9849f907d74595c5b61abf Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 25 Jul 2024 14:29:18 +0100 Subject: [PATCH 136/152] PR Fixes --- .../ETokenCollateralHealthStatus.conf | 41 +++++++++++++++++++ certora/conf/healthStatus/README.md | 4 ++ .../UnderlyingTokenHealthStatus.conf | 41 +++++++++++++++++++ .../runERC4626RulesSeparatelyBaseConf.py | 39 ++++++++++++++++++ certora/scripts/runHealthStatusAllModules.py | 4 +- certora/specs/HealthStatusInvariant.spec | 24 ++++------- 6 files changed, 135 insertions(+), 18 deletions(-) create mode 100644 certora/conf/healthStatus/ETokenCollateralHealthStatus.conf create mode 100644 certora/conf/healthStatus/UnderlyingTokenHealthStatus.conf create mode 100644 certora/scripts/runERC4626RulesSeparatelyBaseConf.py diff --git a/certora/conf/healthStatus/ETokenCollateralHealthStatus.conf b/certora/conf/healthStatus/ETokenCollateralHealthStatus.conf new file mode 100644 index 00000000..24cbfa1c --- /dev/null +++ b/certora/conf/healthStatus/ETokenCollateralHealthStatus.conf @@ -0,0 +1,41 @@ +{ + "files": [ + "certora/harness/EVCHarness.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyETokenA.sol", + "certora/helpers/DummyETokenB.sol", + "src/EVault/modules/BalanceForwarder.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/healthStatus/BalanceForwarderHSHarness.sol", + ], + "link": [ + "BalanceForwarderHSHarness:evc=EVCHarness", + ], + "verify": "BalanceForwarderHSHarness:certora/specs/HealthStatusInvariant.spec", + "solc": "solc8.23", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule" : [ + "accountsStayHealthy_strategy" + ], + "build_cache": true, + "prover_version": "master", + "server" : "staging", + "parametric_contracts": ["DummyETokenA"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, + "prover_args": [ + "-splitParallel true", + "-deleteSMTFile false", + "-smt_easy_LIA true" + ], + "smt_timeout": "4800", +} \ No newline at end of file diff --git a/certora/conf/healthStatus/README.md b/certora/conf/healthStatus/README.md index eb3aebbb..caf79ad0 100644 --- a/certora/conf/healthStatus/README.md +++ b/certora/conf/healthStatus/README.md @@ -1,4 +1,8 @@ Note that for most modules the spec HealthStatusInvariant.spec is used, but for Liquidation, the spec needs to be split into more cases for performance reasons so it uses LiquidateHealthStatus.spec +Also note that for ETokenCollateralHealthStatus is used to verify functions called on the collateral +EToken contract rather than the vault under test, and UnderlyingTokenHealthStatus is used to verify +functions called on the underlying asset. + To run all of these configurations easily, use certora/scripts/runHealthStatusAllModules.py \ No newline at end of file diff --git a/certora/conf/healthStatus/UnderlyingTokenHealthStatus.conf b/certora/conf/healthStatus/UnderlyingTokenHealthStatus.conf new file mode 100644 index 00000000..e84a7b49 --- /dev/null +++ b/certora/conf/healthStatus/UnderlyingTokenHealthStatus.conf @@ -0,0 +1,41 @@ +{ + "files": [ + "certora/harness/EVCHarness.sol", + "lib/ethereum-vault-connector/src/ExecutionContext.sol", + "certora/helpers/DummyERC20A.sol", + "certora/helpers/DummyETokenA.sol", + "certora/helpers/DummyETokenB.sol", + "src/EVault/modules/BalanceForwarder.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/healthStatus/BalanceForwarderHSHarness.sol", + ], + "link": [ + "BalanceForwarderHSHarness:evc=EVCHarness", + ], + "verify": "BalanceForwarderHSHarness:certora/specs/HealthStatusInvariant.spec", + "solc": "solc8.23", + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "rule" : [ + "accountsStayHealthy_strategy" + ], + "build_cache": true, + "prover_version": "master", + "server" : "staging", + "parametric_contracts": ["DummyERC20A"], + "optimistic_loop": true, + "loop_iter": "2", + "solc_via_ir": true, + "solc_optimize": "10000", + "rule_sanity": "basic", + "function_finder_mode" : "relaxed", + "finder_friendly_optimizer" : false, + "prover_args": [ + "-splitParallel true", + "-deleteSMTFile false", + "-smt_easy_LIA true" + ], + "smt_timeout": "4800", +} \ No newline at end of file diff --git a/certora/scripts/runERC4626RulesSeparatelyBaseConf.py b/certora/scripts/runERC4626RulesSeparatelyBaseConf.py new file mode 100644 index 00000000..82886034 --- /dev/null +++ b/certora/scripts/runERC4626RulesSeparatelyBaseConf.py @@ -0,0 +1,39 @@ +import argparse +import subprocess + +parser = argparse.ArgumentParser() +parser.add_argument('-M', '--batchMsg', metavar='M', type=str, nargs='?', + default='', + help='a message for all the jobs') + +# The commented out ones are the ones that need a special config file. +# Those can be run easily by running runERC4626RulesSplitConfs.py +rule_names = [ + # "assetsMoreThanSupply", + "contributingProducesShares", + "conversionOfZero", + "conversionWeakIntegrity", + "conversionWeakMonotonicity", + # "convertToAssetsWeakAdditivity", + # "convertToSharesWeakAdditivity", + # "depositMonotonicity", + # "dustFavorsTheHouse", + # "dustFavorsTheHouseAssets", + # "noAssetsIfNoSupply", + # "noSupplyIfNoAssets", + # "onlyContributionMethodsReduceAssets", + "reclaimingProducesAssets", + "redeemingAllValidity", + "totalSupplyIsSumOfBalances", + # "totalsMonotonicity", + "zeroDepositZeroShares", + "underlyingCannotChange", + # "vaultSolvency", +] + +for name in rule_names: + args = parser.parse_args() + script = "certora/conf/ERC4626Split/VaultERC4626.conf" + command = f"certoraRun {script} --rule \"{name}\" --msg \"{name} : {args.batchMsg}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) \ No newline at end of file diff --git a/certora/scripts/runHealthStatusAllModules.py b/certora/scripts/runHealthStatusAllModules.py index ee72e86a..358f13b3 100644 --- a/certora/scripts/runHealthStatusAllModules.py +++ b/certora/scripts/runHealthStatusAllModules.py @@ -14,7 +14,9 @@ "Initialize", "Liquidation", "Token", - "Vault" + "Vault", + "ETokenCollateral", + "UnderlyingToken" ] for conf in hs_confs: diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index 5b05dcd7..8b9632fa 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -26,9 +26,9 @@ methods { // https://github.com/euler-xyz/reward-streams/blob/master/src/TrackingRewardStreams.sol#L31-L62 function _.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal => NONDET; function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; - // just emits en event so NONDET is safe + // just emits an event so NONDET is safe function _.emitTransfer(address from, address to, uint256 value) external => NONDET; - // has an actual affect -- disables a controller, but this is only called by RiskManager.disableController which reverts unless the controller abalance is 0. So I think this nondet is safe. + // has an actual affect -- disables a controller, but this is only called by RiskManager.disableController which reverts unless the controller balance is 0. So I think this nondet is safe. function EVCHarness.disableController(address account) external => NONDET; // computeInterestRate is not strictly pure -- the implementations of // this function seem to keep state to calculate the future interest rate @@ -138,7 +138,7 @@ rule accountsStayHealthy_strategy (method f) filtered { f -> // sig:GovernanceModule.setLTV(address,uint16,uint16,uint32).selector f.selector != 0x4bca3d5b && // sig:InitializeModule.initialize(address).selector - f.selector != 0xc4d66de8 && + f.selector != 0xc4d66de8 }{ env e; calldataarg args; @@ -146,14 +146,6 @@ rule accountsStayHealthy_strategy (method f) filtered { f -> address[] collaterals = evc.getCollaterals(e, account); require collaterals.length <= 2; // loop bound require oracleAddress != 0; - // Vault cannot be a user of itself - // require account != currentContract; // NOTE recently removed this - // Vault should not be used as a collateral - require collaterals[0] != currentContract; - require collaterals[1] != currentContract; - // Collaterals must be ETokens - // require collaterals[0] == EToken; - // require collaterals[1] == EToken; // not sure the following 4 are really needed require account != erc20; require account != oracleAddress; @@ -176,12 +168,10 @@ rule accountsStayHealthy_strategy (method f) filtered { f -> // these call restoreExecutionContext which triggers the deferred checks. // This excplicit call to checkStatusAll is a way to get a setup that // approximates the real situation. + // We proved separately that EVC really does always call checkStatus all + // at the end of a call/batch. + // run: https://prover.certora.com/output/65266/2523dd890b324c9cb6c1fcec767e030e/?anonymousKey=5c7f3132f51538a96a5d8d4fb0de61f4ed892ccc evc.checkStatusAllExt(e); bool healthyAfter = checkLiquidityReturning(e, account, collaterals); assert healthyBefore => healthyAfter; -} - -// - prove separately that every call to vault is from evc (except maybe view) -// - prove on EVC: at the end of call/batch/permit we really do always call -// checkStatusAll --> After looking at the tickets I think we did not prove -// this already. +} \ No newline at end of file From ecc6afbfa40abe3931195739ee5984bb898a8bac Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 25 Jul 2024 16:42:52 +0100 Subject: [PATCH 137/152] Fix linking for EToken collateral --- certora/conf/healthStatus/ETokenCollateralHealthStatus.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/certora/conf/healthStatus/ETokenCollateralHealthStatus.conf b/certora/conf/healthStatus/ETokenCollateralHealthStatus.conf index 24cbfa1c..4fba82c6 100644 --- a/certora/conf/healthStatus/ETokenCollateralHealthStatus.conf +++ b/certora/conf/healthStatus/ETokenCollateralHealthStatus.conf @@ -11,6 +11,7 @@ ], "link": [ "BalanceForwarderHSHarness:evc=EVCHarness", + "DummyETokenA:evc=EVCHarness", ], "verify": "BalanceForwarderHSHarness:certora/specs/HealthStatusInvariant.spec", "solc": "solc8.23", From 7ec7c2b262b13f3161440e9b4cc3165a89d3714a Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 26 Jul 2024 12:31:12 +0100 Subject: [PATCH 138/152] Antti configs for cache. EToken tweaks. --- certora/conf/Cache.conf | 5 +++++ .../healthStatus/ETokenCollateralHealthStatus.conf | 12 ++++++------ certora/specs/HealthStatusInvariant.spec | 4 +++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/certora/conf/Cache.conf b/certora/conf/Cache.conf index 659f7ff4..04c06c80 100644 --- a/certora/conf/Cache.conf +++ b/certora/conf/Cache.conf @@ -18,5 +18,10 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, + "prover_args": [ + "-smt_nonLinearArithmetic true", + "-adaptiveSolverConfig false", + "-solvers [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" + ], "smt_timeout": "7000" } \ No newline at end of file diff --git a/certora/conf/healthStatus/ETokenCollateralHealthStatus.conf b/certora/conf/healthStatus/ETokenCollateralHealthStatus.conf index 4fba82c6..9e18f729 100644 --- a/certora/conf/healthStatus/ETokenCollateralHealthStatus.conf +++ b/certora/conf/healthStatus/ETokenCollateralHealthStatus.conf @@ -33,10 +33,10 @@ "rule_sanity": "basic", "function_finder_mode" : "relaxed", "finder_friendly_optimizer" : false, - "prover_args": [ - "-splitParallel true", - "-deleteSMTFile false", - "-smt_easy_LIA true" - ], - "smt_timeout": "4800", + // "prover_args": [ + // "-splitParallel true", + // "-deleteSMTFile false", + // "-smt_easy_LIA true" + // ], + "smt_timeout": "28800", } \ No newline at end of file diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index 8b9632fa..25830039 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -138,7 +138,9 @@ rule accountsStayHealthy_strategy (method f) filtered { f -> // sig:GovernanceModule.setLTV(address,uint16,uint16,uint32).selector f.selector != 0x4bca3d5b && // sig:InitializeModule.initialize(address).selector - f.selector != 0xc4d66de8 + f.selector != 0xc4d66de8 && + // sig:TokenHarnes.transferFromInternalHarnessed (this is a harness method only) + f.selector != 0xd3110e86 }{ env e; calldataarg args; From de287100936741398f789138067674e04daff852 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 26 Jul 2024 15:02:46 +0100 Subject: [PATCH 139/152] Add revised Cache.spec --- certora/specs/Cache.spec | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/certora/specs/Cache.spec b/certora/specs/Cache.spec index b0d2c55b..979af48c 100644 --- a/certora/specs/Cache.spec +++ b/certora/specs/Cache.spec @@ -1,5 +1,5 @@ // passing -// run: https://prover.certora.com/output/65266/e5dc6fb3648f45fdbe48597c69561bd1/?anonymousKey=12ed8515517a0998ef7af0ed86ecc7008537cec1 +// run: https://prover.certora.com/output/65266/11417156b83b43c0b03fc0e7cd7f84e9?anonymousKey=68ebc9dadced7038e1193557a50c2c9183abbd72 rule updateVault_no_unexpected_reverts { env e; @@ -11,24 +11,11 @@ rule updateVault_no_unexpected_reverts { // assignment to deltaT require lastInterestAccUpd < e.block.timestamp; - - // https://prover.certora.com/output/65266/e834a7e7775443ffbe26577bfbc97f87?anonymousKey=98085ba3f887e9b0fd2b22683e73af45bc1a106b - - // assignment to newTotalBorrows, overflows - // Note: MAX_SANE_AMOUNT does not work as a bound for these: - // https://prover.certora.com/output/65266/e1aab12acdb5435d80e70e661299c504?anonymousKey=c6c63c10fa9ddb5c16b86cd2073643768d3d96e4 - require getTotalBorrows(e) < 1267650600228229401496703205375; //2**100-1 - - require getInterestAcc(e) < 1267650600228229401496703205375; // newTotalBorrows assigment, prevent divide by zero require getInterestAcc(e) > 0; // typecast of newAccumulatedFees - // Also MAX_SANE_AMOUNT is not a sufficient bound for this - // (because the bounded var is from storage not the new accumulated fees) - // https://prover.certora.com/output/65266/8c53d45891374c4692ea7597de239ba1?anonymousKey=551bfa1d1460c56f30002f5de8aeab4bd49a0fcb - require getAccumulatedFees(e) < 1267650600228229401496703205375; - + require getAccumulatedFees(e) < getTotalShares(e); updateVaultExt@withrevert(e); assert !lastReverted; From 4ccbd7eff9cb795327ff2d0038d511ff66ad506d Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 2 Aug 2024 09:48:58 +0100 Subject: [PATCH 140/152] Rule version of specs for withdraw --- ...RC4626-vaultSolvency-withdraw-as-rule.conf | 41 +++++++++++++ certora/scripts/runERC4626RulesSplitConfs.py | 27 +++++---- certora/specs/VaultERC4626.spec | 57 ++++++++++++++++--- 3 files changed, 105 insertions(+), 20 deletions(-) create mode 100644 certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw-as-rule.conf diff --git a/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw-as-rule.conf b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw-as-rule.conf new file mode 100644 index 00000000..a64f4ac6 --- /dev/null +++ b/certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw-as-rule.conf @@ -0,0 +1,41 @@ +{ + "build_cache": true, + "files": [ + "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", + "src/EVault/modules/Vault.sol", + "certora/helpers/DummyERC20A.sol", + // "certora/helpers/DummyERC20B.sol", + "certora/harness/EVCHarness.sol", + "certora/harness/BaseHarness.sol", + "certora/harness/ERC4626Harness.sol" + ], + "loop_iter": "2", + "msg": " -vaultSolvency-withdraw : rerun ERC4626 for wrap up", + "optimistic_loop": true, + "packages": [ + "ethereum-vault-connector=lib/ethereum-vault-connector/src", + "forge-std=lib/forge-std/src" + ], + "parametric_contracts": [ + "ERC4626Harness" + ], + "process": "emv", + "prover_args": [ + "-smt_nonLinearArithmetic true", + "-adaptiveSolverConfig false", + "-deleteSMTFile false", + "-depth 20", + ], + "prover_version" : "master", + "rule": [ + "vaultSolvencyWithdraw_totals", + "vaultSolvencyWithdraw_underlying", + ], + "rule_sanity": "basic", + "server": "production", + "smt_timeout": "7800", + "solc": "solc8.23", + "solc_optimize": "10000", + "solc_via_ir": true, + "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec" +} \ No newline at end of file diff --git a/certora/scripts/runERC4626RulesSplitConfs.py b/certora/scripts/runERC4626RulesSplitConfs.py index 7fb83168..0f665aac 100644 --- a/certora/scripts/runERC4626RulesSplitConfs.py +++ b/certora/scripts/runERC4626RulesSplitConfs.py @@ -7,20 +7,22 @@ help='a message for all the jobs') erc4626_confs = { - # "", - # "-assetsMoreThanSupply", - # "-convertToAssetsWeakAdditivity", - # "-convertToSharesWeakAdditivity", - # "-depositMonotonicity", - # "-dustFavorsTheHouse", - # "-dustFavorsTheHouseAssets", - # "-noAssetsIfNoSupply", - # "-noSupplyIfNoAssets", - # "-onlyContributionMethodsReduce", - # "-totalsMonotonicity", + "", + "-assetsMoreThanSupply", + "-convertToAssetsWeakAdditivity", + "-convertToSharesWeakAdditivity", + "-depositMonotonicity", + "-dustFavorsTheHouse", + "-dustFavorsTheHouseAssets", + "-noAssetsIfNoSupply", + "-noSupplyIfNoAssets", + "-onlyContributionMethodsReduce", + "-totalsMonotonicity", "-vaultSolvency-most", "-vaultSolvency-redeem", "-vaultSolvency-withdraw" + # In case the invariant times out for withdraw + "-vaultSolvency-withdraw-as-rule" } for name in erc4626_confs: @@ -28,4 +30,5 @@ script = f"certora/conf/ERC4626Split/VaultERC4626{name}.conf" command = f"certoraRun {script} --msg \"{name} : {args.batchMsg}\"" print(f"runing {command}") - subprocess.run(command, shell=True) \ No newline at end of file + subprocess.run(command, shell=True) + diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index cde4a9f6..6690eeb1 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -13,7 +13,7 @@ import "./GhostPow.spec"; import "./LoadVaultSummary.spec"; using DummyERC20A as ERC20a; -using DummyERC20B as ERC20b; +// using DummyERC20B as ERC20b; /* Declaration of methods that are used in the rules. envfree indicate that @@ -65,16 +65,16 @@ methods { function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; // This is NONDET to help avoid timeouts. It should be safe // to NONDET since it is a private view function. - function _.resolve(Vault.AmountCap self) internal => NONDET; + function _.resolve(Vault.AmountCap self) internal => CONSTANT; } -function CVLToShares(uint256 amount) returns BaseHarness.Shares { - return require_uint112(amount); -} -function CVLToAssets(uint256 amount) returns BaseHarness.Assets { - return require_uint112(amount); -} +// function CVLToShares(uint256 amount) returns BaseHarness.Shares { +// return require_uint112(amount); +// } +// function CVLToAssets(uint256 amount) returns BaseHarness.Assets { +// return require_uint112(amount); +// } // This is not in the scene for this config, so we just want it to be // an uninterpreted function rather than NONDET so that @@ -353,6 +353,47 @@ invariant vaultSolvency(env e) } } +rule vaultSolvencyWithdraw_totals { + env e; + requireInvariant totalSupplyIsSumOfBalances(e); + require e.msg.sender != currentContract; + require actualCaller(e) != currentContract; + require currentContract != asset(); + uint256 amount; + address receiver; + address owner; + require totalAssets(e) >= totalSupply(e); + require userAssets(e, currentContract) >= require_uint256(cache_cash(e)); + withdraw(e, amount, receiver, owner); + assert totalAssets(e) >= totalSupply(e); +} + +rule withdraw_amount_max { + env e; + require e.msg.sender != currentContract; + require actualCaller(e) != currentContract; + require currentContract != asset(); + uint256 amount; + address receiver; + address owner; + withdraw(e, amount, receiver, owner); + assert amount <= max_uint112 || amount == max_uint256; +} + +rule vaultSolvencyWithdraw_underlying { + env e; + requireInvariant totalSupplyIsSumOfBalances(e); + require e.msg.sender != currentContract; + require actualCaller(e) != currentContract; + require currentContract != asset(); + uint256 amount; + address receiver; + address owner; + require userAssets(e, currentContract) >= require_uint256(cache_cash(e)); + withdraw(e, amount, receiver, owner); + assert userAssets(e, currentContract) >= require_uint256(cache_cash(e)); +} + rule redeemingAllValidity() { env e; address owner; From 3c52bf22db40f8964d5b032fb37f20ce99de9598 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 2 Aug 2024 09:50:23 +0100 Subject: [PATCH 141/152] delete unused code --- certora/specs/VaultERC4626.spec | 7 ------- 1 file changed, 7 deletions(-) diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 6690eeb1..7495f767 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -69,13 +69,6 @@ methods { } -// function CVLToShares(uint256 amount) returns BaseHarness.Shares { -// return require_uint112(amount); -// } -// function CVLToAssets(uint256 amount) returns BaseHarness.Assets { -// return require_uint112(amount); -// } - // This is not in the scene for this config, so we just want it to be // an uninterpreted function rather than NONDET so that // we get the same value when this is called for different parts From 6e2851d0dd8828c69c31be8fadab21c857b36b4a Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 7 Aug 2024 10:50:25 +0100 Subject: [PATCH 142/152] Add rule about LTV parameter assumptions --- .../conf/EVault/modules/BalanceForwarder.conf | 1 + certora/conf/EVault/modules/Governance.conf | 1 + certora/conf/EVault/modules/Liquidation.conf | 1 + certora/conf/EVault/modules/RiskManager.conf | 1 + certora/harness/modules/GovernanceHarness.sol | 5 ++++ certora/specs/Governance.spec | 25 +++++++++++++++++-- 6 files changed, 32 insertions(+), 2 deletions(-) diff --git a/certora/conf/EVault/modules/BalanceForwarder.conf b/certora/conf/EVault/modules/BalanceForwarder.conf index b8e99ec2..a9ef9f78 100644 --- a/certora/conf/EVault/modules/BalanceForwarder.conf +++ b/certora/conf/EVault/modules/BalanceForwarder.conf @@ -19,6 +19,7 @@ "prover_version": "master", "server" : "production", "parametric_contracts": ["BalanceForwarderHarness"], + "build_cache": true, "prover_args": ["-smt_bitVectorTheory", "true"], "optimistic_loop": true, "loop_iter": "2", diff --git a/certora/conf/EVault/modules/Governance.conf b/certora/conf/EVault/modules/Governance.conf index 38e47242..283a5ee7 100644 --- a/certora/conf/EVault/modules/Governance.conf +++ b/certora/conf/EVault/modules/Governance.conf @@ -22,6 +22,7 @@ "forge-std=lib/forge-std/src" ], "prover_version": "master", + "build_cache": true, // Performance tuning options below this line "solc_via_ir": true, "solc_optimize": "10000", diff --git a/certora/conf/EVault/modules/Liquidation.conf b/certora/conf/EVault/modules/Liquidation.conf index 5f843392..08edecf5 100644 --- a/certora/conf/EVault/modules/Liquidation.conf +++ b/certora/conf/EVault/modules/Liquidation.conf @@ -22,6 +22,7 @@ "prover_version": "master", "server" : "production", // "coverage_info" : "advanced", + "build_cache": true, // Performance tuing options below this line "prover_args": [ "-depth 10", diff --git a/certora/conf/EVault/modules/RiskManager.conf b/certora/conf/EVault/modules/RiskManager.conf index 20e8ed85..bef8006e 100644 --- a/certora/conf/EVault/modules/RiskManager.conf +++ b/certora/conf/EVault/modules/RiskManager.conf @@ -19,6 +19,7 @@ "parametric_contracts": ["RiskManagerHarness"], "rule_sanity": "basic", "server": "production", + "build_cache": true, // "coverage_info" : "advanced", // Performance tuning options below this line "prover_args": [ diff --git a/certora/harness/modules/GovernanceHarness.sol b/certora/harness/modules/GovernanceHarness.sol index fe9fbee2..ce51cc5b 100644 --- a/certora/harness/modules/GovernanceHarness.sol +++ b/certora/harness/modules/GovernanceHarness.sol @@ -34,4 +34,9 @@ contract GovernanceHarness is Governance, AbstractBaseHarness, RiskManagerModule function getLastAccumulated() external view returns (uint256){ return uint256(vaultStorage.lastInterestAccumulatorUpdate); } + + function getLTVHarness(address collateral, bool liquidation) public view virtual returns (ConfigAmount) { + return getLTV(collateral, liquidation); + } + } \ No newline at end of file diff --git a/certora/specs/Governance.spec b/certora/specs/Governance.spec index 8cdc830d..dfd49d1e 100644 --- a/certora/specs/Governance.spec +++ b/certora/specs/Governance.spec @@ -48,11 +48,10 @@ function CVLCheckVaultStatusInternal(env e) returns (bool, bytes) { checkVaultMagicValueMemory(e)); } -// Both rules pass. Run with both: -// https://prover.certora.com/output/65266/9207ef71046343e993e83f9dfa761eb1?anonymousKey=401a193cacbcbc774185473b0242384e3e8c5b4d // Collecting fees should increase the protocol’s and the governor’s asset (unless the governor is address(0)) // STATUS: PASSING +// https://prover.certora.com/output/65266/9207ef71046343e993e83f9dfa761eb1?anonymousKey=401a193cacbcbc774185473b0242384e3e8c5b4d rule feeCollectionIncreasesProtocolGovernerAssets(env e){ address protocolReceiver; @@ -84,6 +83,7 @@ rule feeCollectionIncreasesProtocolGovernerAssets(env e){ // Collecting fees should not change total shares // STATUS: PASSING +// https://prover.certora.com/output/65266/9207ef71046343e993e83f9dfa761eb1?anonymousKey=401a193cacbcbc774185473b0242384e3e8c5b4d rule collectingFeeDoesntChangeTotalShares(env e){ uint112 totalShares_before = getTotalShares(); @@ -96,4 +96,25 @@ rule collectingFeeDoesntChangeTotalShares(env e){ assert totalShares_after == totalShares_before,"fee collection should not change total shares"; +} + +// These are assumed elsewhere in the specs +// Pasing. Run link: https://prover.certora.com/output/65266/c078d73b9aaf41b69de58a059ec9c0ea?anonymousKey=3c865aa300106c0b53d38a8dc479dc0668774e48 +rule LTVConfigProperties { + env e; + address collateral; + uint16 borrowLTV; + uint16 liquidationLTV; + uint32 rampDuration; + uint16 old_borrowLTVOut = getLTVHarness(e, collateral, false); + uint16 old_liquidationLTVOut = getLTVHarness(e, collateral, true); + require old_borrowLTVOut <= 10000 && + old_liquidationLTVOut <= 10000 && + old_liquidationLTVOut >= old_borrowLTVOut; + setLTV(e, collateral, borrowLTV, liquidationLTV, rampDuration); + uint16 borrowLTVOut = getLTVHarness(e, collateral, false); + uint16 liquidationLTVOut = getLTVHarness(e, collateral, true); + assert borrowLTVOut <= 10000 && + liquidationLTVOut <= 10000 && + liquidationLTVOut >= borrowLTVOut; } \ No newline at end of file From 1eb83245d22e110a35e2a7cd4503ee9537843a74 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Wed, 7 Aug 2024 11:27:01 +0100 Subject: [PATCH 143/152] Delete comment --- src/EVault/modules/Liquidation.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/EVault/modules/Liquidation.sol b/src/EVault/modules/Liquidation.sol index e6d36a69..e938a4e0 100644 --- a/src/EVault/modules/Liquidation.sol +++ b/src/EVault/modules/Liquidation.sol @@ -56,7 +56,6 @@ abstract contract LiquidationModule is ILiquidation, BalanceUtils, LiquidityUtil executeLiquidation(vaultCache, liqCache, minYieldBalance); } - // Munged to internal by certora to enable harnessing function calculateLiquidation( VaultCache memory vaultCache, address liquidator, From 738743e32cfb45c8cfa45eb0439e5bf11d48dd94 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Thu, 8 Aug 2024 10:36:17 +0100 Subject: [PATCH 144/152] Reverify Cache.spec with LEQ --- certora/specs/Cache.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certora/specs/Cache.spec b/certora/specs/Cache.spec index 979af48c..90a95031 100644 --- a/certora/specs/Cache.spec +++ b/certora/specs/Cache.spec @@ -9,7 +9,7 @@ rule updateVault_no_unexpected_reverts { uint256 lastInterestAccUpd = getlastInterestAccumulatorUpdate(e); // assignment to deltaT - require lastInterestAccUpd < e.block.timestamp; + require lastInterestAccUpd <= e.block.timestamp; // newTotalBorrows assigment, prevent divide by zero require getInterestAcc(e) > 0; From 93f551affb67dba9574940493b4c9ff3a297f9c2 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Fri, 9 Aug 2024 11:31:12 +0100 Subject: [PATCH 145/152] drop check in EnforceCollateralTransfer, scripts to run governance spearately for holy grail --- certora/scripts/runHealthStatusAllModules.py | 24 +++++++++++++++++++- certora/specs/Cache.spec | 2 +- certora/specs/HealthStatusInvariant.spec | 1 - certora/specs/LiquidateHealthStatus.spec | 1 - 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/certora/scripts/runHealthStatusAllModules.py b/certora/scripts/runHealthStatusAllModules.py index 358f13b3..e82db11c 100644 --- a/certora/scripts/runHealthStatusAllModules.py +++ b/certora/scripts/runHealthStatusAllModules.py @@ -25,6 +25,28 @@ print(f"runing {command}") subprocess.run(command, shell=True) +# List includes all but only all public non-view methods +gov_separate_methods = [ + "convertFees()", + "setGovernorAdmin(address)", + "setFeeReceiver(address)", + "setLTV(address,uint16,uint16,uint32)", + "clearLTV(address)", + "setMaxLiquidationDiscount(uint16)", + "setLiquidationCoolOffTime(uint16)", + "setInterestRateModel(address)", + "setHookConfig(address,uint32)", + "setConfigFlags(uint32)", + "setCaps(uint16,uint16)", + "setInterestFee(uint16)" +] + +for method in gov_separate_methods: + script = f"certora/conf/healthStatus/GovernanceHealthStatus.conf" + command = f"certoraRun {script} --msg \"Governance.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) + borrow_separate_methods = [ "borrow(uint256,address)", "pullDebt(uint256,address)", @@ -60,4 +82,4 @@ script = f"certora/conf/healthStatus/LiquidateHealthStatus.conf" command = f"certoraRun {script} --msg \"Liquidate case: {rule} : {args.batchMsg}\" --rule \"{rule}\"" print(f"runing {command}") - subprocess.run(command, shell=True) + subprocess.run(command, shell=True) \ No newline at end of file diff --git a/certora/specs/Cache.spec b/certora/specs/Cache.spec index 90a95031..8183fdc2 100644 --- a/certora/specs/Cache.spec +++ b/certora/specs/Cache.spec @@ -1,5 +1,5 @@ // passing -// run: https://prover.certora.com/output/65266/11417156b83b43c0b03fc0e7cd7f84e9?anonymousKey=68ebc9dadced7038e1193557a50c2c9183abbd72 +// run: https://prover.certora.com/output/65266/7c027fe6b03f4ead8d1fc08b876c8e75?anonymousKey=475104b4504c765772a29b9124ee15355a4cf2c9 rule updateVault_no_unexpected_reverts { env e; diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index 25830039..f62c587f 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -117,7 +117,6 @@ function CVLSafeTransferFrom(env e, address token, address from, address to, uin * - enqueue a status check on the evc for the "from" address */ function CVLEnforceCollateralTransfer(env e, address collateral, uint256 amount, address from, address receiver) { - evc.requireAccountStatusCheck(e, from); if (collateral == ETokenA) { ETokenA.transferFromInternalHarnessed(e, from, receiver, amount); } else if (collateral == ETokenB) { diff --git a/certora/specs/LiquidateHealthStatus.spec b/certora/specs/LiquidateHealthStatus.spec index 6b4297ff..e89e699c 100644 --- a/certora/specs/LiquidateHealthStatus.spec +++ b/certora/specs/LiquidateHealthStatus.spec @@ -115,7 +115,6 @@ function CVLSafeTransferFrom(env e, address token, address from, address to, uin // for the prover, instead assign which account gets checked to a ghost persistent ghost address collateralTransferCheckedAccount; function CVLEnforceCollateralTransfer(env e, address collateral, uint256 amount, address from, address receiver) { - // evc.requireAccountStatusCheck(e, from); collateralTransferCheckedAccount = from; if (collateral == ETokenA) { ETokenA.transferFromInternalHarnessed(e, from, receiver, amount); From ac2d4b62663af831d5b4328760621d37e5b1696c Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 12 Aug 2024 13:15:42 +0100 Subject: [PATCH 146/152] Fix issue with env variables --- certora/scripts/runHealthStatusAllModules.py | 42 +++++++-------- certora/specs/HealthStatusInvariant.spec | 53 +++++++++++++------ certora/specs/LiquidateHealthStatus.spec | 55 ++++++++++++++------ 3 files changed, 95 insertions(+), 55 deletions(-) diff --git a/certora/scripts/runHealthStatusAllModules.py b/certora/scripts/runHealthStatusAllModules.py index e82db11c..9f0a71fc 100644 --- a/certora/scripts/runHealthStatusAllModules.py +++ b/certora/scripts/runHealthStatusAllModules.py @@ -25,27 +25,27 @@ print(f"runing {command}") subprocess.run(command, shell=True) -# List includes all but only all public non-view methods -gov_separate_methods = [ - "convertFees()", - "setGovernorAdmin(address)", - "setFeeReceiver(address)", - "setLTV(address,uint16,uint16,uint32)", - "clearLTV(address)", - "setMaxLiquidationDiscount(uint16)", - "setLiquidationCoolOffTime(uint16)", - "setInterestRateModel(address)", - "setHookConfig(address,uint32)", - "setConfigFlags(uint32)", - "setCaps(uint16,uint16)", - "setInterestFee(uint16)" -] - -for method in gov_separate_methods: - script = f"certora/conf/healthStatus/GovernanceHealthStatus.conf" - command = f"certoraRun {script} --msg \"Governance.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" - print(f"runing {command}") - subprocess.run(command, shell=True) +# # List includes all public non-view methods +# gov_separate_methods = [ +# "convertFees()", +# "setGovernorAdmin(address)", +# "setFeeReceiver(address)", +# "setLTV(address,uint16,uint16,uint32)", +# "clearLTV(address)", +# "setMaxLiquidationDiscount(uint16)", +# "setLiquidationCoolOffTime(uint16)", +# "setInterestRateModel(address)", +# "setHookConfig(address,uint32)", +# "setConfigFlags(uint32)", +# "setCaps(uint16,uint16)", +# "setInterestFee(uint16)" +# ] +# +# for method in gov_separate_methods: +# script = f"certora/conf/healthStatus/GovernanceHealthStatus.conf" +# command = f"certoraRun {script} --msg \"Governance.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" +# print(f"runing {command}") +# subprocess.run(command, shell=True) borrow_separate_methods = [ "borrow(uint256,address)", diff --git a/certora/specs/HealthStatusInvariant.spec b/certora/specs/HealthStatusInvariant.spec index f62c587f..0b5ad404 100644 --- a/certora/specs/HealthStatusInvariant.spec +++ b/certora/specs/HealthStatusInvariant.spec @@ -7,6 +7,9 @@ using DummyETokenB as ETokenB; // Allows for possibility of multiple methods { function checkAccountMagicValue() external returns (bytes4) envfree; + function checkAccountMagicValueMemory() external returns (bytes memory) envfree; + function checkVaultMagicValueMemory() external returns (bytes memory) envfree; + function EVCHarness.areChecksDeferred() external returns (bool) envfree; // healthStatusCheck reverts unless this is true. We assume it's true // approximate the real situation where these checks get triggered // by the EVC before which this flag will be set. @@ -42,37 +45,42 @@ methods { // access controls in the vault will not allow non-EVC calls to succeed. // there is also the nonreentrant modifier in most places. function _.onFlashLoan(bytes data) external => NONDET; + function EVCHarness.getCollaterals(address) external returns (address[] memory) envfree; // EVC function _.requireVaultStatusCheck() external => DISPATCHER(true); function _.requireAccountAndVaultStatusCheck(address) external => DISPATCHER(true); // Summaries - function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLSafeTransferFrom(e, token, from, to, value) expect void; + function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal => CVLSafeTransferFrom(token, from, to, value) expect void; function _.enforceCollateralTransfer(address collateral, uint256 amount, - address from, address receiver) internal with (env e) => - CVLEnforceCollateralTransfer(e, collateral, amount, from, receiver) expect void; + address from, address receiver) internal => + CVLEnforceCollateralTransfer(collateral, amount, from, receiver) expect void; // We can't handle the low-level call in // EthereumVaultConnector.checkAccountStatusInternal // and so reroute it to RiskManager's status check with this summary. - function EthereumVaultConnector.checkAccountStatusInternal(address account) internal returns (bool, bytes memory) with (env e) => - CVLCheckAccountStatusInternal(e, account); - function EthereumVaultConnector.checkVaultStatusInternal(address vault) internal returns (bool, bytes memory) with(env e) => - CVLCheckVaultStatusInternal(e); + function EthereumVaultConnector.checkAccountStatusInternal(address account) internal returns (bool, bytes memory) => + CVLCheckAccountStatusInternal(account); + function EthereumVaultConnector.checkVaultStatusInternal(address vault) internal returns (bool, bytes memory) => + CVLCheckVaultStatusInternal(); } // We summarize EthereumVaultConnector.checkAccountStatusInternal // because we need to direct the low-level call to RiskManager. // checkAccountStatus and this linking doesn't happen automatically function CVLCheckAccountStatusInternalBool(env e, address account) returns bool { - address[] collaterals = evc.getCollaterals(e, account); + address[] collaterals = evc.getCollaterals(account); checkAccountStatus@withrevert(e, account, collaterals); return !lastReverted; } -function CVLCheckAccountStatusInternal(env e, address account) returns (bool, bytes) { - return (CVLCheckAccountStatusInternalBool(e, account), - checkAccountMagicValueMemory(e)); +function CVLCheckAccountStatusInternal(address account) returns (bool, bytes) { + // We need a new env for the first function. + // Since the vault calls the EVC, otherwise msg.sender + // would become the vault unless we declare a fresh environment. + env eEVC; + return (CVLCheckAccountStatusInternalBool(eEVC, account), + checkAccountMagicValueMemory()); } function CVLCheckVaultStatusInternalBool(env e) returns bool { @@ -80,16 +88,25 @@ function CVLCheckVaultStatusInternalBool(env e) returns bool { return !lastReverted; } -function CVLCheckVaultStatusInternal(env e) returns (bool, bytes) { - return (CVLCheckVaultStatusInternalBool(e), - checkVaultMagicValueMemory(e)); +function CVLCheckVaultStatusInternal() returns (bool, bytes) { + // We need a new env for the first function. + // Since the vault calls the EVC, otherwise msg.sender + // would become the vault unless we declare a fresh environment. + env eEVC; + return (CVLCheckVaultStatusInternalBool(eEVC), + checkVaultMagicValueMemory()); } function CVLAreChecksInProgress() returns bool { return true; } -function CVLSafeTransferFrom(env e, address token, address from, address to, uint256 value) { +function CVLSafeTransferFrom(address token, address from, address to, uint256 value) { + // We need a new env since this will + // be a call from the vault to the ERC20 rather than a call + // from the original message sender to the ERC20. + // would become the vault unless we declare a fresh environment. + env e; if (token == ERC20a) { ERC20a.transferFrom(e, from, to, value); } else if (token == ETokenA) { @@ -116,7 +133,8 @@ function CVLSafeTransferFrom(env e, address token, address from, address to, uin * - explicitly call EToken.transferFrom using the expected addresses * - enqueue a status check on the evc for the "from" address */ -function CVLEnforceCollateralTransfer(env e, address collateral, uint256 amount, address from, address receiver) { +function CVLEnforceCollateralTransfer(address collateral, uint256 amount, address from, address receiver) { + env e; if (collateral == ETokenA) { ETokenA.transferFromInternalHarnessed(e, from, receiver, amount); } else if (collateral == ETokenB) { @@ -144,7 +162,7 @@ rule accountsStayHealthy_strategy (method f) filtered { f -> env e; calldataarg args; address account; - address[] collaterals = evc.getCollaterals(e, account); + address[] collaterals = evc.getCollaterals(account); require collaterals.length <= 2; // loop bound require oracleAddress != 0; // not sure the following 4 are really needed @@ -152,6 +170,7 @@ rule accountsStayHealthy_strategy (method f) filtered { f -> require account != oracleAddress; require account != evc; require account != unitOfAccount; + require evc.areChecksDeferred(); require LTVConfigAssumptions(e, getLTVConfig(e, ETokenA)); require LTVConfigAssumptions(e, getLTVConfig(e, ETokenB)); diff --git a/certora/specs/LiquidateHealthStatus.spec b/certora/specs/LiquidateHealthStatus.spec index e89e699c..821e0116 100644 --- a/certora/specs/LiquidateHealthStatus.spec +++ b/certora/specs/LiquidateHealthStatus.spec @@ -7,6 +7,9 @@ using DummyETokenB as ETokenB; // Allows for possibility of multiple methods { function checkAccountMagicValue() external returns (bytes4) envfree; + function checkAccountMagicValueMemory() external returns (bytes memory) envfree; + function checkVaultMagicValueMemory() external returns (bytes memory) envfree; + function EVCHarness.areChecksDeferred() external returns (bool) envfree; // healthStatusCheck reverts unless this is true. We assume it's true // approximate the real situation where these checks get triggered // by the EVC before which this flag will be set. @@ -29,19 +32,19 @@ methods { function _.requireAccountAndVaultStatusCheck(address) external => DISPATCHER(true); // Summaries - function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLSafeTransferFrom(e, token, from, to, value) expect void; + function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal => CVLSafeTransferFrom(token, from, to, value) expect void; function _.enforceCollateralTransfer(address collateral, uint256 amount, - address from, address receiver) internal with (env e) => - CVLEnforceCollateralTransfer(e, collateral, amount, from, receiver) expect void; + address from, address receiver) internal => + CVLEnforceCollateralTransfer(collateral, amount, from, receiver) expect void; // To deal with changes between LTV values: // function _.getLTV(address collateral, bool liquidation) internal => CVLGetLTV(collateral, liquidation) expect (BaseHarness.ConfigAmount); // We can't handle the low-level call in // EthereumVaultConnector.checkAccountStatusInternal // and so reroute it to RiskManager's status check with this summary. - function EthereumVaultConnector.checkAccountStatusInternal(address account) internal returns (bool, bytes memory) with (env e) => - CVLCheckAccountStatusInternal(e, account); - function EthereumVaultConnector.checkVaultStatusInternal(address vault) internal returns (bool, bytes memory) with(env e) => - CVLCheckVaultStatusInternal(e); + function EthereumVaultConnector.checkAccountStatusInternal(address account) internal returns (bool, bytes memory) => + CVLCheckAccountStatusInternal(account); + function EthereumVaultConnector.checkVaultStatusInternal(address vault) internal returns (bool, bytes memory) => + CVLCheckVaultStatusInternal(); function _.EVCRequireStatusChecks(address account) internal => EVCRequireStatusChecksCVL(account) expect void; @@ -65,9 +68,13 @@ function CVLCheckAccountStatusInternalBool(env e, address account) returns bool return !lastReverted; } -function CVLCheckAccountStatusInternal(env e, address account) returns (bool, bytes) { - return (CVLCheckAccountStatusInternalBool(e, account), - checkAccountMagicValueMemory(e)); +function CVLCheckAccountStatusInternal(address account) returns (bool, bytes) { + // We need a new env for the first function. + // Since the vault calls the EVC, otherwise msg.sender + // would become the vault unless we declare a fresh environment. + env eEVC; + return (CVLCheckAccountStatusInternalBool(eEVC, account), + checkAccountMagicValueMemory()); } function CVLCheckVaultStatusInternalBool(env e) returns bool { @@ -75,16 +82,25 @@ function CVLCheckVaultStatusInternalBool(env e) returns bool { return !lastReverted; } -function CVLCheckVaultStatusInternal(env e) returns (bool, bytes) { - return (CVLCheckVaultStatusInternalBool(e), - checkVaultMagicValueMemory(e)); +function CVLCheckVaultStatusInternal() returns (bool, bytes) { + // We need a new env for the first function. + // Since the vault calls the EVC, otherwise msg.sender + // would become the vault unless we declare a fresh environment. + env eEVC; + return (CVLCheckVaultStatusInternalBool(eEVC), + checkVaultMagicValueMemory()); } function CVLAreChecksInProgress() returns bool { return true; } -function CVLSafeTransferFrom(env e, address token, address from, address to, uint256 value) { +function CVLSafeTransferFrom(address token, address from, address to, uint256 value) { + // We need a new env since this will + // be a call from the vault to the ERC20 rather than a call + // from the original message sender to the ERC20. + // would become the vault unless we declare a fresh environment. + env e; if (token == ERC20a) { ERC20a.transferFrom(e, from, to, value); } else if (token == ETokenA) { @@ -114,7 +130,8 @@ function CVLSafeTransferFrom(env e, address token, address from, address to, uin // Because calling to requireAccountStatusCheck on EVC is expensive // for the prover, instead assign which account gets checked to a ghost persistent ghost address collateralTransferCheckedAccount; -function CVLEnforceCollateralTransfer(env e, address collateral, uint256 amount, address from, address receiver) { +function CVLEnforceCollateralTransfer(address collateral, uint256 amount, address from, address receiver) { + env e; collateralTransferCheckedAccount = from; if (collateral == ETokenA) { ETokenA.transferFromInternalHarnessed(e, from, receiver, amount); @@ -159,6 +176,7 @@ rule liquidateAccountsStayHealthy_liquidator_no_debt_socialization { require account != oracleAddress; require account != evc; require account != unitOfAccount; + require evc.areChecksDeferred(); require LTVConfigAssumptions(e, getLTVConfig(e, ETokenA)); require LTVConfigAssumptions(e, getLTVConfig(e, ETokenB)); @@ -222,6 +240,7 @@ rule liquidateAccountsStayHealthy_liquidator_with_debt_socialization { require account != oracleAddress; require account != evc; require account != unitOfAccount; + require evc.areChecksDeferred(); require LTVConfigAssumptions(e, getLTVConfig(e, ETokenA)); require LTVConfigAssumptions(e, getLTVConfig(e, ETokenB)); @@ -284,7 +303,8 @@ rule liquidateAccountsStayHealthy_not_violator { require account != erc20; require account != oracleAddress; require account != evc; - require account != unitOfAccount ; + require account != unitOfAccount; + require evc.areChecksDeferred(); require LTVConfigAssumptions(e, getLTVConfig(e, ETokenA)); require LTVConfigAssumptions(e, getLTVConfig(e, ETokenB)); @@ -348,7 +368,8 @@ rule liquidateAccountsStayHealthy_account_cur_contract { require account != erc20; require account != oracleAddress; require account != evc; - require account != unitOfAccount ; + require account != unitOfAccount; + require evc.areChecksDeferred(); require LTVConfigAssumptions(e, getLTVConfig(e, ETokenA)); require LTVConfigAssumptions(e, getLTVConfig(e, ETokenB)); From b04e9daf0d7840b52e366a3d09324d211b45a62d Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 12 Aug 2024 13:53:33 +0100 Subject: [PATCH 147/152] Various PR fixes --- certora/harness/ERC4626Harness.sol | 2 - certora/harness/TokenHarness.sol | 4 +- certora/harness/modules/GovernanceHarness.sol | 2 +- certora/scripts/runHealthStatusAllModules.py | 42 +++++++++---------- certora/specs/Governance.spec | 4 +- certora/specs/Liquidation.spec | 1 - certora/specs/LoadVaultSummary.spec | 6 --- certora/specs/RiskManager.spec | 28 +++++++++++++ certora/specs/VaultERC4626.spec | 9 ++-- 9 files changed, 57 insertions(+), 41 deletions(-) diff --git a/certora/harness/ERC4626Harness.sol b/certora/harness/ERC4626Harness.sol index c2f1f962..50874401 100644 --- a/certora/harness/ERC4626Harness.sol +++ b/certora/harness/ERC4626Harness.sol @@ -14,8 +14,6 @@ contract ERC4626Harness is VaultModule, TokenModule, AbstractBaseHarness { function userAssets(address user) public view returns (uint256) { // harnessed // The assets in the underlying asset contract (not in the vault) return IERC20(asset()).balanceOf(user); - // The assets stored in the vault for a user. - // return vaultStorage.users[user].getBalance().toAssetsDown(loadVault()).toUint(); } function updateVault() internal override returns (VaultCache memory vaultCache) { diff --git a/certora/harness/TokenHarness.sol b/certora/harness/TokenHarness.sol index 71ee5f53..f3d5dbae 100644 --- a/certora/harness/TokenHarness.sol +++ b/certora/harness/TokenHarness.sol @@ -11,9 +11,9 @@ contract TokenHarness is TokenModule { constructor(Integrations memory integrations) Base(integrations) {} function transferFromInternalHarnessed(address from, address to, uint256 amount) public returns (bool) { - // This is similar to the body of Token.transferFrom + // This is similar to the body of Token.transferFromInternal // when it gets its arguments from Token.transfer. - // It is not harnessed directly since Token.transferFrom is private + // It is not harnessed directly since Token.transferFromInternal is private // and we want to avoid munging. // This is used for the enforceCollateralTransfer function Shares shares = amount.toShares(); diff --git a/certora/harness/modules/GovernanceHarness.sol b/certora/harness/modules/GovernanceHarness.sol index ce51cc5b..e5d2c08d 100644 --- a/certora/harness/modules/GovernanceHarness.sol +++ b/certora/harness/modules/GovernanceHarness.sol @@ -13,7 +13,7 @@ contract GovernanceHarness is Governance, AbstractBaseHarness, RiskManagerModule (balance, ) = user.getBalanceAndBalanceForwarder(); } - function getGovernorReciver() external view returns (address governorReceiver){ + function getGovernorReceiver() external view returns (address governorReceiver){ governorReceiver = vaultStorage.feeReceiver; } diff --git a/certora/scripts/runHealthStatusAllModules.py b/certora/scripts/runHealthStatusAllModules.py index 9f0a71fc..4c44e974 100644 --- a/certora/scripts/runHealthStatusAllModules.py +++ b/certora/scripts/runHealthStatusAllModules.py @@ -25,27 +25,27 @@ print(f"runing {command}") subprocess.run(command, shell=True) -# # List includes all public non-view methods -# gov_separate_methods = [ -# "convertFees()", -# "setGovernorAdmin(address)", -# "setFeeReceiver(address)", -# "setLTV(address,uint16,uint16,uint32)", -# "clearLTV(address)", -# "setMaxLiquidationDiscount(uint16)", -# "setLiquidationCoolOffTime(uint16)", -# "setInterestRateModel(address)", -# "setHookConfig(address,uint32)", -# "setConfigFlags(uint32)", -# "setCaps(uint16,uint16)", -# "setInterestFee(uint16)" -# ] -# -# for method in gov_separate_methods: -# script = f"certora/conf/healthStatus/GovernanceHealthStatus.conf" -# command = f"certoraRun {script} --msg \"Governance.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" -# print(f"runing {command}") -# subprocess.run(command, shell=True) +# List includes all public non-view methods +gov_separate_methods = [ + "convertFees()", + "setGovernorAdmin(address)", + "setFeeReceiver(address)", + "setLTV(address,uint16,uint16,uint32)", + "clearLTV(address)", + "setMaxLiquidationDiscount(uint16)", + "setLiquidationCoolOffTime(uint16)", + "setInterestRateModel(address)", + "setHookConfig(address,uint32)", + "setConfigFlags(uint32)", + "setCaps(uint16,uint16)", + "setInterestFee(uint16)" +] + +for method in gov_separate_methods: + script = f"certora/conf/healthStatus/GovernanceHealthStatus.conf" + command = f"certoraRun {script} --msg \"Governance.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) borrow_separate_methods = [ "borrow(uint256,address)", diff --git a/certora/specs/Governance.spec b/certora/specs/Governance.spec index dfd49d1e..39d47ea3 100644 --- a/certora/specs/Governance.spec +++ b/certora/specs/Governance.spec @@ -14,7 +14,7 @@ methods { // Harness function getAccountBalance(address) external returns (GovernanceHarness.Shares) envfree; - function getGovernorReciver() external returns (address) envfree; + function getGovernorReceiver() external returns (address) envfree; function getProtocolFeeConfig(address) external returns (address, uint16) envfree; function getTotalShares() external returns (GovernanceHarness.Shares) envfree; function getAccumulatedFees() external returns (GovernanceHarness.Shares); @@ -59,7 +59,7 @@ rule feeCollectionIncreasesProtocolGovernerAssets(env e){ protocolReceiver, protocolFee = getProtocolFeeConfig(currentContract); require protocolFee > 0; // require protocolReceiver != 0; - address governorReceiver = getGovernorReciver(); + address governorReceiver = getGovernorReceiver(); require governorReceiver != 0; // accumulated fee is not zero diff --git a/certora/specs/Liquidation.spec b/certora/specs/Liquidation.spec index 7526b73e..cc15006e 100644 --- a/certora/specs/Liquidation.spec +++ b/certora/specs/Liquidation.spec @@ -84,7 +84,6 @@ rule checkLiquidation_healthy() { // - liability vault is not enabled as the only controller of the violator // - violator account status check is deferred // - price oracle is not configured -// - price oracle is not configured rule checkLiquidation_mustRevert { env e; address liquidator; diff --git a/certora/specs/LoadVaultSummary.spec b/certora/specs/LoadVaultSummary.spec index a2d9cfa3..a6abc85b 100644 --- a/certora/specs/LoadVaultSummary.spec +++ b/certora/specs/LoadVaultSummary.spec @@ -24,12 +24,6 @@ methods { // in all the rules, only one env is ever created per rule. // The parts of the cache about interest are not relevant to the specs -// parameter is meant to be block.timestamp -persistent ghost newInterestBorrows(uint256) returns uint256; -// this should be increasing over time, but I think we do -// not even need to model this. It can just be an uninterp function -// because in the ERC4626 spec there are no rules with multiple env. - function CVLLoadVaultAssumeNoUpdate(env e) returns BaseHarness.VaultCache { BaseHarness.VaultCache vaultCache; uint48 lastUpdate = storage_lastInterestAccumulatorUpdate(); diff --git a/certora/specs/RiskManager.spec b/certora/specs/RiskManager.spec index d12e33f9..0914d225 100644 --- a/certora/specs/RiskManager.spec +++ b/certora/specs/RiskManager.spec @@ -37,6 +37,34 @@ rule ltv_borrowing_lower { } +// passing run: https://prover.certora.com/output/65266/e768bd4519db456aac70651279b9f124/?anonymousKey=d78f56247abf57df2dec09115b4700e32946a1a9 +rule ltv_liabilities_equal{ + env e; + calldataarg args; + + address account; + + + // based on loop bound + address[] collaterals = getCollateralsExt(account); + require collaterals.length == 2; + require LTVConfigAssumptions(e, getLTVConfig(collaterals[0])); + require LTVConfigAssumptions(e, getLTVConfig(collaterals[1])); + + uint256 collateralValue_liquidation; + uint256 liabilityValue_liquidation; + (collateralValue_liquidation, liabilityValue_liquidation) = accountLiquidity(e, account, true); + + uint256 collateralValue_borrowing; + uint256 liabilityValue_borrowing; + (collateralValue_borrowing, liabilityValue_borrowing) = accountLiquidity(e, account, false); + + require collateralValue_liquidation > 0; + require collateralValue_borrowing > 0; + + assert liabilityValue_liquidation == liabilityValue_borrowing; +} + // Passing rule accountLiquidityMustRevert { diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 7495f767..275f1c86 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -72,9 +72,6 @@ methods { // This is not in the scene for this config, so we just want it to be // an uninterpreted function rather than NONDET so that // we get the same value when this is called for different parts -// ghost CVLgetCurrentOnBehalfOfAccountAddr(address) returns address { -// axiom forall address x. CVLgetCurrentOnBehalfOfAccountAddr(x) != currentContract; -// } ghost address GhostOnBehalfOfAccount { axiom GhostOnBehalfOfAccount != currentContract; axiom GhostOnBehalfOfAccount != 0; @@ -295,7 +292,7 @@ rule underlyingCannotChange() { //////////////////////////////////////////////////////////////////////////////// // passing -// run: https://prover.certora.com/output/65266/1912c053cdf8485087f2c050146c64aa/?anonymousKey=a12e3d573258a4d8136a19b612448a50f80b9a21 +// run: https://prover.certora.com/output/65266/a19010e64bb8424aa513be8b75d15cdf/?anonymousKey=87c73cdf676930336269396f2dbb3cac3d78b997 rule dustFavorsTheHouse(uint assetsIn ) { env e; @@ -304,12 +301,12 @@ rule dustFavorsTheHouse(uint assetsIn ) safeAssumptions(e,e.msg.sender,e.msg.sender); uint256 totalSupplyBefore = totalSupply(e); - uint balanceBefore = currentContract.balanceOf(e, currentContract); + uint balanceBefore = userAssets(e, currentContract); uint shares = deposit(e,assetsIn, e.msg.sender); uint assetsOut = redeem(e,shares,e.msg.sender,e.msg.sender); - uint balanceAfter = currentContract.balanceOf(e, currentContract); + uint balanceAfter = userAssets(e, currentContract); assert balanceAfter >= balanceBefore; } From 4f06340d991e5c0b5d353b9be1d082520d2872e8 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 12 Aug 2024 16:30:53 +0100 Subject: [PATCH 148/152] Various fixes. New rules requested from PR --- .../harness/modules/LiquidationHarness.sol | 7 + certora/scripts/runHealthStatusAllModules.py | 125 +++++++++--------- certora/specs/LiquidateHealthStatus.spec | 18 --- certora/specs/Liquidation.spec | 28 ++++ certora/specs/RiskManager.spec | 17 +++ 5 files changed, 114 insertions(+), 81 deletions(-) diff --git a/certora/harness/modules/LiquidationHarness.sol b/certora/harness/modules/LiquidationHarness.sol index 75b31a85..baa0908e 100644 --- a/certora/harness/modules/LiquidationHarness.sol +++ b/certora/harness/modules/LiquidationHarness.sol @@ -16,6 +16,13 @@ contract LiquidationHarness is AbstractBaseHarness, Liquidation { return calculateLiquidity(loadVault(), account, getCollaterals(account), true); } + function calculateLiquidityLiquidation( + address account + ) public view returns (uint256 collateralValue, uint256 liabilityValue) { + return calculateLiquidity(loadVault(), account, getCollaterals(account), false); + } + + function calculateLiquidationExt( VaultCache memory vaultCache, address liquidator, diff --git a/certora/scripts/runHealthStatusAllModules.py b/certora/scripts/runHealthStatusAllModules.py index 4c44e974..db29fa63 100644 --- a/certora/scripts/runHealthStatusAllModules.py +++ b/certora/scripts/runHealthStatusAllModules.py @@ -7,69 +7,68 @@ help='a message for all the jobs') args = parser.parse_args() -hs_confs = [ - "BalanceForwarder", - "Borrowing", - "Governance", - "Initialize", - "Liquidation", - "Token", - "Vault", - "ETokenCollateral", - "UnderlyingToken" -] - -for conf in hs_confs: - script = f"certora/conf/healthStatus/{conf}HealthStatus.conf" - command = f"certoraRun {script} --msg \"{conf} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\"" - print(f"runing {command}") - subprocess.run(command, shell=True) - -# List includes all public non-view methods -gov_separate_methods = [ - "convertFees()", - "setGovernorAdmin(address)", - "setFeeReceiver(address)", - "setLTV(address,uint16,uint16,uint32)", - "clearLTV(address)", - "setMaxLiquidationDiscount(uint16)", - "setLiquidationCoolOffTime(uint16)", - "setInterestRateModel(address)", - "setHookConfig(address,uint32)", - "setConfigFlags(uint32)", - "setCaps(uint16,uint16)", - "setInterestFee(uint16)" -] - -for method in gov_separate_methods: - script = f"certora/conf/healthStatus/GovernanceHealthStatus.conf" - command = f"certoraRun {script} --msg \"Governance.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" - print(f"runing {command}") - subprocess.run(command, shell=True) - -borrow_separate_methods = [ - "borrow(uint256,address)", - "pullDebt(uint256,address)", - "repayWithShares(uint256,address)", - "repay(uint256,address)", -] - -for method in borrow_separate_methods: - script = f"certora/conf/healthStatus/BorrowingHealthStatus.conf" - command = f"certoraRun {script} --msg \"Borrow.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" - print(f"runing {command}") - subprocess.run(command, shell=True) - -vault_separate_methods = [ - "redeem(uint256,address,address)", - "withdraw(uint256,address,address)" -] - -for method in vault_separate_methods: - script = f"certora/conf/healthStatus/VaultHealthStatus.conf" - command = f"certoraRun {script} --msg \"Vault.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" - print(f"runing {command}") - subprocess.run(command, shell=True) +# hs_confs = [ +# "BalanceForwarder", +# "Borrowing", +# "Governance", +# "Initialize", +# "Liquidation", +# "Token", +# "Vault", +# "ETokenCollateral", +# "UnderlyingToken" +# ] +# +# for conf in hs_confs: +# script = f"certora/conf/healthStatus/{conf}HealthStatus.conf" +# command = f"certoraRun {script} --msg \"{conf} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\"" +# print(f"runing {command}") +# subprocess.run(command, shell=True) +# +# # List includes all public non-view methods aside from setLTV, clearLTV which +# # are out of scope for the holy grail rule. +# gov_separate_methods = [ +# "convertFees()", +# "setGovernorAdmin(address)", +# "setFeeReceiver(address)", +# "setMaxLiquidationDiscount(uint16)", +# "setLiquidationCoolOffTime(uint16)", +# "setInterestRateModel(address)", +# "setHookConfig(address,uint32)", +# "setConfigFlags(uint32)", +# "setCaps(uint16,uint16)", +# "setInterestFee(uint16)" +# ] +# +# for method in gov_separate_methods: +# script = f"certora/conf/healthStatus/GovernanceHealthStatus.conf" +# command = f"certoraRun {script} --msg \"Governance.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" +# print(f"runing {command}") +# subprocess.run(command, shell=True) +# +# borrow_separate_methods = [ +# "borrow(uint256,address)", +# "pullDebt(uint256,address)", +# "repayWithShares(uint256,address)", +# "repay(uint256,address)", +# ] +# +# for method in borrow_separate_methods: +# script = f"certora/conf/healthStatus/BorrowingHealthStatus.conf" +# command = f"certoraRun {script} --msg \"Borrow.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" +# print(f"runing {command}") +# subprocess.run(command, shell=True) +# +# vault_separate_methods = [ +# "redeem(uint256,address,address)", +# "withdraw(uint256,address,address)" +# ] +# +# for method in vault_separate_methods: +# script = f"certora/conf/healthStatus/VaultHealthStatus.conf" +# command = f"certoraRun {script} --msg \"Vault.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" +# print(f"runing {command}") +# subprocess.run(command, shell=True) liquidate_cases = [ "liquidateAccountsStayHealthy_liquidator_no_debt_socialization", diff --git a/certora/specs/LiquidateHealthStatus.spec b/certora/specs/LiquidateHealthStatus.spec index 821e0116..53770e5e 100644 --- a/certora/specs/LiquidateHealthStatus.spec +++ b/certora/specs/LiquidateHealthStatus.spec @@ -129,10 +129,8 @@ function CVLSafeTransferFrom(address token, address from, address to, uint256 va */ // Because calling to requireAccountStatusCheck on EVC is expensive // for the prover, instead assign which account gets checked to a ghost -persistent ghost address collateralTransferCheckedAccount; function CVLEnforceCollateralTransfer(address collateral, uint256 amount, address from, address receiver) { env e; - collateralTransferCheckedAccount = from; if (collateral == ETokenA) { ETokenA.transferFromInternalHarnessed(e, from, receiver, amount); } else if (collateral == ETokenB) { @@ -198,7 +196,6 @@ rule liquidateAccountsStayHealthy_liquidator_no_debt_socialization { // initialize checked accounts to 0 require accountToCheckGhost == 0; // account checked in initialize - require collateralTransferCheckedAccount == 0; // account eq liquidator case require collateral == ETokenA || collateral == ETokenB; @@ -215,9 +212,6 @@ rule liquidateAccountsStayHealthy_liquidator_no_debt_socialization { if(accountToCheckGhost != 0) { currentContract.checkAccountStatus(e, accountToCheckGhost, collaterals); } - if(collateralTransferCheckedAccount != 0) { - currentContract.checkAccountStatus(e, collateralTransferCheckedAccount, collaterals); - } bool healthyAfter = checkLiquidityReturning(e, account, collaterals); assert healthyBefore => healthyAfter; @@ -262,7 +256,6 @@ rule liquidateAccountsStayHealthy_liquidator_with_debt_socialization { // initialize checked accounts to 0 require accountToCheckGhost == 0; // account checked in initialize - require collateralTransferCheckedAccount == 0; // account eq liquidator case require collateral == collaterals[0] || collateral == collaterals[1]; @@ -279,9 +272,6 @@ rule liquidateAccountsStayHealthy_liquidator_with_debt_socialization { if(accountToCheckGhost != 0) { currentContract.checkAccountStatus(e, accountToCheckGhost, collaterals); } - if(collateralTransferCheckedAccount != 0) { - currentContract.checkAccountStatus(e, collateralTransferCheckedAccount, collaterals); - } bool healthyAfter = checkLiquidityReturning(e, account, collaterals); assert healthyBefore => healthyAfter; @@ -325,7 +315,6 @@ rule liquidateAccountsStayHealthy_not_violator { // initialize checked accounts to 0 require accountToCheckGhost == 0; // account checked in initialize - require collateralTransferCheckedAccount == 0; // account NE violator case require account != violator; @@ -347,9 +336,6 @@ rule liquidateAccountsStayHealthy_not_violator { if(accountToCheckGhost != 0) { currentContract.checkAccountStatus(e, accountToCheckGhost, collaterals); } - if(collateralTransferCheckedAccount != 0) { - currentContract.checkAccountStatus(e, collateralTransferCheckedAccount, collaterals); - } bool healthyAfter = checkLiquidityReturning(e, account, collaterals); assert healthyBefore => healthyAfter; @@ -393,7 +379,6 @@ rule liquidateAccountsStayHealthy_account_cur_contract { // initialize checked accounts to 0 require accountToCheckGhost == 0; // account checked in initialize - require collateralTransferCheckedAccount == 0; bool healthyBefore = checkLiquidityReturning(e, account, collaterals); currentContract.liquidate(e, violator, collateral, repayAssets, minYieldBalance); @@ -410,9 +395,6 @@ rule liquidateAccountsStayHealthy_account_cur_contract { if(accountToCheckGhost != 0) { currentContract.checkAccountStatus(e, accountToCheckGhost, collaterals); } - if(collateralTransferCheckedAccount != 0) { - currentContract.checkAccountStatus(e, collateralTransferCheckedAccount, collaterals); - } bool healthyAfter = checkLiquidityReturning(e, account, collaterals); assert healthyBefore => healthyAfter; diff --git a/certora/specs/Liquidation.spec b/certora/specs/Liquidation.spec index cc15006e..32a1fe96 100644 --- a/certora/specs/Liquidation.spec +++ b/certora/specs/Liquidation.spec @@ -76,6 +76,34 @@ rule checkLiquidation_healthy() { assert maxYield == 0; } +rule checkLiquidation_healthy_reverts() { + env e; + address account; + require oracleAddress != 0; + + uint256 liquidityCollateralValue; + uint256 liquidityLiabilityValue; + address[] collaterals = getCollateralsExt(account); + require collaterals.length == 2; // loop unrolling bound + (liquidityCollateralValue, liquidityLiabilityValue) = + calculateLiquidityLiquidation(e, account); + + // returns true if there is no liability + require liquidityLiabilityValue > 0; + + // calculateLiquidity and checkLiquidity are only + // the same if the unitOfAccount is the same + // as the underlying asset -- otherwise the + // value of the unitOfAccount could change the value + // of the liability value returned by getLiabilityValue + require unitOfAccount == erc20; + + // checkLiquidityReturning must return FALSE if collateral is not + // greater than liability. + assert checkLiquidityReturning(e, account, collaterals) <=> + (liquidityCollateralValue > liquidityLiabilityValue); +} + // passing // checkLiquidation must revert if: // - violator is the same account as liquidator diff --git a/certora/specs/RiskManager.spec b/certora/specs/RiskManager.spec index 0914d225..b643bc77 100644 --- a/certora/specs/RiskManager.spec +++ b/certora/specs/RiskManager.spec @@ -65,6 +65,23 @@ rule ltv_liabilities_equal{ assert liabilityValue_liquidation == liabilityValue_borrowing; } +rule checkLiquidityReturningSameAsOriginal { + env e; + address account; + address[] collaterals = getCollateralsExt(account); + // rule out irrelevant reverts in calculateLiquidityExternal + // which are also ruled out by the EVC call/batch interface + // and the setup for the holy grail rule + require e.msg.sender == evc; + require evc.areChecksInProgress(e); + + require collaterals.length <= 2; // loop bound + bool ret = checkLiquidityReturning(e, account, collaterals); + checkAccountStatus@withrevert(e, account, collaterals); + bool originalReverted = lastReverted; + assert ret <=> !originalReverted; +} + // Passing rule accountLiquidityMustRevert { From ff0705c5f9a81706baa4a02b68c01839b59b5e32 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 12 Aug 2024 16:49:29 +0100 Subject: [PATCH 149/152] env fix in Governance. passing run link in checkLiquidation_healthy_reverts --- certora/specs/Governance.spec | 15 +++++++++++---- certora/specs/Liquidation.spec | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/certora/specs/Governance.spec b/certora/specs/Governance.spec index 39d47ea3..88e50620 100644 --- a/certora/specs/Governance.spec +++ b/certora/specs/Governance.spec @@ -12,6 +12,9 @@ methods { function _.transfer(address,uint256) external => DISPATCHER(true); function _.transferFrom(address,address,uint256) external => DISPATCHER(true); + function checkAccountMagicValueMemory() external returns (bytes memory) envfree; + function checkVaultMagicValueMemory() external returns (bytes memory) envfree; + // Harness function getAccountBalance(address) external returns (GovernanceHarness.Shares) envfree; function getGovernorReceiver() external returns (address) envfree; @@ -28,8 +31,8 @@ methods { // We can't handle the low-level call in // EthereumVaultConnector.checkAccountStatusInternal // and so reroute it to RiskManager's status check with this summary. - function EthereumVaultConnector.checkVaultStatusInternal(address vault) internal returns (bool, bytes memory) with(env e) => - CVLCheckVaultStatusInternal(e); + function EthereumVaultConnector.checkVaultStatusInternal(address vault) internal returns (bool, bytes memory) => + CVLCheckVaultStatusInternal(); function _.invokeHookTarget(address caller) internal => NONDET; @@ -43,9 +46,13 @@ function CVLCheckVaultStatusInternalBool(env e) returns bool { return !lastReverted; } -function CVLCheckVaultStatusInternal(env e) returns (bool, bytes) { +function CVLCheckVaultStatusInternal() returns (bool, bytes) { + // We need a new env for the first function. + // Since the vault calls the EVC, otherwise msg.sender + // would become the vault unless we declare a fresh environment. + env e; return (CVLCheckVaultStatusInternalBool(e), - checkVaultMagicValueMemory(e)); + checkVaultMagicValueMemory()); } diff --git a/certora/specs/Liquidation.spec b/certora/specs/Liquidation.spec index 32a1fe96..35fa1e4d 100644 --- a/certora/specs/Liquidation.spec +++ b/certora/specs/Liquidation.spec @@ -76,6 +76,7 @@ rule checkLiquidation_healthy() { assert maxYield == 0; } +// passing run: https://prover.certora.com/output/65266/ed9699a14a114c0dbad76526a55ad493/?anonymousKey=f1f0a74c2c72ede7ce77f50fbf66541e8c4f03d7 rule checkLiquidation_healthy_reverts() { env e; address account; From 593665ea486f2a3ef7aaf8ef47d61756ab5bb07b Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Mon, 12 Aug 2024 17:34:32 +0100 Subject: [PATCH 150/152] More governance fixes --- certora/specs/Governance.spec | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/certora/specs/Governance.spec b/certora/specs/Governance.spec index 88e50620..f05935a0 100644 --- a/certora/specs/Governance.spec +++ b/certora/specs/Governance.spec @@ -37,6 +37,8 @@ methods { function _.invokeHookTarget(address caller) internal => NONDET; function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; + + function _.computeInterestRate(BaseHarness.VaultCache memory) internal => CONSTANT; } @@ -88,23 +90,6 @@ rule feeCollectionIncreasesProtocolGovernerAssets(env e){ "collecting fees should icnrease the shares of the governor and protocol"; } -// Collecting fees should not change total shares -// STATUS: PASSING -// https://prover.certora.com/output/65266/9207ef71046343e993e83f9dfa761eb1?anonymousKey=401a193cacbcbc774185473b0242384e3e8c5b4d -rule collectingFeeDoesntChangeTotalShares(env e){ - - uint112 totalShares_before = getTotalShares(); - // requiring that no fee accumulation happens to increase totalShares - require getLastAccumulated() == e.block.timestamp; - - convertFees(e); - - uint112 totalShares_after = getTotalShares(); - - assert totalShares_after == totalShares_before,"fee collection should not change total shares"; - -} - // These are assumed elsewhere in the specs // Pasing. Run link: https://prover.certora.com/output/65266/c078d73b9aaf41b69de58a059ec9c0ea?anonymousKey=3c865aa300106c0b53d38a8dc479dc0668774e48 rule LTVConfigProperties { From 1338b309a3da816eb82315fbb5f56fd05d293508 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 13 Aug 2024 10:17:51 +0100 Subject: [PATCH 151/152] Mainly update runscript --- certora/scripts/runHealthStatusAllModules.py | 169 +++++++++++-------- certora/specs/VaultERC4626.spec | 9 +- 2 files changed, 107 insertions(+), 71 deletions(-) diff --git a/certora/scripts/runHealthStatusAllModules.py b/certora/scripts/runHealthStatusAllModules.py index db29fa63..78a7c9ad 100644 --- a/certora/scripts/runHealthStatusAllModules.py +++ b/certora/scripts/runHealthStatusAllModules.py @@ -7,68 +7,95 @@ help='a message for all the jobs') args = parser.parse_args() -# hs_confs = [ -# "BalanceForwarder", -# "Borrowing", -# "Governance", -# "Initialize", -# "Liquidation", -# "Token", -# "Vault", -# "ETokenCollateral", -# "UnderlyingToken" -# ] -# -# for conf in hs_confs: -# script = f"certora/conf/healthStatus/{conf}HealthStatus.conf" -# command = f"certoraRun {script} --msg \"{conf} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\"" -# print(f"runing {command}") -# subprocess.run(command, shell=True) -# -# # List includes all public non-view methods aside from setLTV, clearLTV which -# # are out of scope for the holy grail rule. -# gov_separate_methods = [ -# "convertFees()", -# "setGovernorAdmin(address)", -# "setFeeReceiver(address)", -# "setMaxLiquidationDiscount(uint16)", -# "setLiquidationCoolOffTime(uint16)", -# "setInterestRateModel(address)", -# "setHookConfig(address,uint32)", -# "setConfigFlags(uint32)", -# "setCaps(uint16,uint16)", -# "setInterestFee(uint16)" -# ] -# -# for method in gov_separate_methods: -# script = f"certora/conf/healthStatus/GovernanceHealthStatus.conf" -# command = f"certoraRun {script} --msg \"Governance.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" -# print(f"runing {command}") -# subprocess.run(command, shell=True) -# -# borrow_separate_methods = [ -# "borrow(uint256,address)", -# "pullDebt(uint256,address)", -# "repayWithShares(uint256,address)", -# "repay(uint256,address)", -# ] -# -# for method in borrow_separate_methods: -# script = f"certora/conf/healthStatus/BorrowingHealthStatus.conf" -# command = f"certoraRun {script} --msg \"Borrow.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" -# print(f"runing {command}") -# subprocess.run(command, shell=True) -# -# vault_separate_methods = [ -# "redeem(uint256,address,address)", -# "withdraw(uint256,address,address)" -# ] -# -# for method in vault_separate_methods: -# script = f"certora/conf/healthStatus/VaultHealthStatus.conf" -# command = f"certoraRun {script} --msg \"Vault.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" -# print(f"runing {command}") -# subprocess.run(command, shell=True) +hs_confs = [ + "BalanceForwarder", + "Borrowing", + "Governance", + "Initialize", + "Liquidation", + "Token", + "Vault", + "ETokenCollateral", + "UnderlyingToken" +] + +def runModules(): + for conf in hs_confs: + script = f"certora/conf/healthStatus/{conf}HealthStatus.conf" + command = f"certoraRun {script} --msg \"{conf} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\"" + print(f"runing {command}") + subprocess.run(command, shell=True) + +# List includes all public non-view methods aside from setLTV, clearLTV which +# are out of scope for the holy grail rule. +gov_separate_methods = [ + "convertFees()", + "setGovernorAdmin(address)", + "setFeeReceiver(address)", + "setMaxLiquidationDiscount(uint16)", + "setLiquidationCoolOffTime(uint16)", + "setInterestRateModel(address)", + "setHookConfig(address,uint32)", + "setConfigFlags(uint32)", + "setCaps(uint16,uint16)", + "setInterestFee(uint16)" +] + +def runGovSeparately(): + for method in gov_separate_methods: + script = f"certora/conf/healthStatus/GovernanceHealthStatus.conf" + command = f"certoraRun {script} --msg \"Governance.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) + +# List includes all public non-view methods +token_separate_methods = [ + "transfer(address,uint256)", + "transferFromMax(address,address)", + "transferFrom(address,address,uint256)", + "approve(address,uint256)" +] + +def runTokenSeparately(): + for method in token_separate_methods: + script = f"certora/conf/healthStatus/TokenHealthStatus.conf" + command = f"certoraRun {script} --msg \"Token.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) + +borrow_separate_methods = [ + "borrow(uint256,address)", + "pullDebt(uint256,address)", + "repayWithShares(uint256,address)", + "repay(uint256,address)", +] + +def runBorrowSeparately(): + for method in borrow_separate_methods: + script = f"certora/conf/healthStatus/BorrowingHealthStatus.conf" + command = f"certoraRun {script} --msg \"Borrow.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) + +# Includes all public non-view methods of the vault +vault_separate_methods = [ + # Initially just the first two timed out unless run separately, + # but now all will run into memory issues unless we run separately + # "redeem(uint256,address,address)", + # "withdraw(uint256,address,address)", + "deposit(uint256,address)", + "mint(uint256,address)", + "withdraw(uint256,address,address)", + "redeem(uint256,address,address)", + "skim(uint256,address)", +] + +def runVaultSeparately(): + for method in vault_separate_methods: + script = f"certora/conf/healthStatus/VaultHealthStatus.conf" + command = f"certoraRun {script} --msg \"Vault.{method} : {args.batchMsg}\" --rule \"accountsStayHealthy_strategy\" --method \"{method}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) liquidate_cases = [ "liquidateAccountsStayHealthy_liquidator_no_debt_socialization", @@ -77,8 +104,16 @@ "liquidateAccountsStayHealthy_account_cur_contract" ] -for rule in liquidate_cases: - script = f"certora/conf/healthStatus/LiquidateHealthStatus.conf" - command = f"certoraRun {script} --msg \"Liquidate case: {rule} : {args.batchMsg}\" --rule \"{rule}\"" - print(f"runing {command}") - subprocess.run(command, shell=True) \ No newline at end of file +def runLiquidateCases(): + for rule in liquidate_cases: + script = f"certora/conf/healthStatus/LiquidateHealthStatus.conf" + command = f"certoraRun {script} --msg \"Liquidate case: {rule} : {args.batchMsg}\" --rule \"{rule}\"" + print(f"runing {command}") + subprocess.run(command, shell=True) + +runModules() +runGovSeparately() +runTokenSeparately() +runBorrowSeparately() +runVaultSeparately() +runLiquidateCases() \ No newline at end of file diff --git a/certora/specs/VaultERC4626.spec b/certora/specs/VaultERC4626.spec index 275f1c86..873ed88a 100644 --- a/certora/specs/VaultERC4626.spec +++ b/certora/specs/VaultERC4626.spec @@ -60,7 +60,7 @@ methods { function _.trySafeTransferFrom(address token, address from, address to, uint256 value) internal with (env e) => CVLTrySafeTransferFrom(e, token,from, to, value) expect (bool, bytes memory); // safeTransferFrom is summarized as transferFrom // from DummyERC20a to avoid dealing with the low-level `call` - function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal with (env e)=> CVLSafeTransferFrom(e, token, from, to, value) expect void; + function _.safeTransferFrom(address token, address from, address to, uint256 value, address permit2) internal => CVLSafeTransferFrom(token, from, to, value) expect void; function _.tryBalanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) internal => NONDET; function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; // This is NONDET to help avoid timeouts. It should be safe @@ -84,7 +84,8 @@ function CVLgetCurrentOnBehalfOfAccount(address addr) returns (address, bool) { persistent ghost CVLGetAccountOwner(address) returns address; // Summarize trySafeTransferFrom as DummyERC20 transferFrom -function CVLSafeTransferFrom(env e, address token, address from, address to, uint256 value) { +function CVLSafeTransferFrom(address token, address from, address to, uint256 value) { + env e; ERC20a.transferFrom(e, from, to, value); } @@ -192,7 +193,7 @@ rule zeroDepositZeroShares(uint assets, address receiver) uint shares = deposit(e,assets, receiver); // In this Vault, max_uint256 as an argument will transfer all assets // to the vault. This precondition rules out the case where - // the depositor calls deposit with a blance of 0 in the underlying + // the depositor calls deposit with a balance of 0 in the underlying // asset and gives max_uint256 as the shares. require assets < max_uint256; @@ -223,7 +224,7 @@ invariant noAssetsIfNoSupply(env e) } invariant noSupplyIfNoAssets(env e) - noSupplyIfNoAssetsDef(e) // see defition in "helpers and miscellaneous" section + noSupplyIfNoAssetsDef(e) // see definition in "helpers and miscellaneous" section { preserved { safeAssumptions(e, _, e.msg.sender); From ad855db7db173d980e2eceedb4b6b69231c16c58 Mon Sep 17 00:00:00 2001 From: Andrew Ferraiuolo Date: Tue, 13 Aug 2024 11:57:09 +0100 Subject: [PATCH 152/152] add summary to get new RiskManager rule passing --- certora/specs/RiskManager.spec | 3 +++ 1 file changed, 3 insertions(+) diff --git a/certora/specs/RiskManager.spec b/certora/specs/RiskManager.spec index b643bc77..b0d75933 100644 --- a/certora/specs/RiskManager.spec +++ b/certora/specs/RiskManager.spec @@ -1,4 +1,5 @@ import "Base.spec"; +import "./LoadVaultSummary.spec"; // run: https://prover.certora.com/output/65266/4d1ba56cfd3c4aefbe2661e07fd5c95c/?anonymousKey=800abae52d40b2758c3f1f8c8a42ff82025533cd @@ -65,6 +66,8 @@ rule ltv_liabilities_equal{ assert liabilityValue_liquidation == liabilityValue_borrowing; } +// passing +// run: https://prover.certora.com/output/40726/f67c06400ebc412c88740b7efe675bc4/?anonymousKey=7101510a9ac7abec41dab0ee4f659219da5b98b9 rule checkLiquidityReturningSameAsOriginal { env e; address account;