mirror of
https://github.com/logos-storage/logos-storage-contracts-eth.git
synced 2026-01-02 13:23:10 +00:00
Merge a7d0332a584653a516fce240008cf5c9c9f0dc52 into b5ab3869b949f651a3ed9aeb914feed7bd0dfd47
This commit is contained in:
commit
5fb0bf94a1
64
certora/README.md
Normal file
64
certora/README.md
Normal file
@ -0,0 +1,64 @@
|
||||
# Formal Verification of Vault
|
||||
|
||||
All funds in Codex are handled by the Vault contract. This is a
|
||||
small contract that separates funds for different users and checks
|
||||
that accounting is done correctly. In addition it allows the users
|
||||
to withdraw their funds after the locks expired, even if the main
|
||||
contract breaks. Thus it gives users a guarantee they can always
|
||||
access their funds.
|
||||
|
||||
This guarantee requires that the accounting the Vault itself does
|
||||
is correct. This is the goal of the verification project. It
|
||||
formally proves several properties of the Vault contract.
|
||||
|
||||
## Usage
|
||||
|
||||
Install the Certora Prover. Then run the verification with
|
||||
|
||||
certoraRun certora/confs/Vault.conf
|
||||
|
||||
## Properties
|
||||
|
||||
We check several properties for the Vault contract:
|
||||
|
||||
1. The current lock expiry time is always less or equal the lockMaximum.
|
||||
2. The available balance of each account is large enoguh to cover
|
||||
the outgoing flow until the maximum lock time.
|
||||
3. The sum of all incoming flows equals the sum of all outgoing flows.
|
||||
4. The sum of all expected funds (as defined in property 7) is always less
|
||||
than or equal to the current balance of the contract.
|
||||
5. Before a fund id is locked and flows can start, there is never an
|
||||
outgoing flow for any account in this fund.
|
||||
6. The last updated timestamp for flows in each account is never in
|
||||
the future and always on or before the lock time.
|
||||
7. The expected funds for each account is the available balance plus the
|
||||
dedicated balance plus the incoming flows minus the outgoing flows
|
||||
from the last time updated until the end of the flow (either lock
|
||||
time or freeze time). These funds are always non-negative (i.e. no
|
||||
account can be in debt to the protocol in the future due to outgoing
|
||||
flows).
|
||||
|
||||
The forth property (solvency) is the main property we need to show to
|
||||
guarantee that the funds are accounted correctly.
|
||||
|
||||
## Limitations
|
||||
|
||||
We prove the solvency invariant only for a standard ERC20 token as
|
||||
implemented in the OpenZepellin library. In particular, the contract
|
||||
assumes that transfering tokens work as expected, that no fee is taken
|
||||
by the token contract and that no unexpected balance changes can occur.
|
||||
|
||||
To prove that changing the lock time or freezing the funds does not change
|
||||
the funds required by the contract, we cannot use the Certora Prover itself
|
||||
as the underlying SMT solvers cannot natively reason about sums over
|
||||
all elements in a mapping. Instead we add this as an assumption to the
|
||||
specification and argue its correctness property manually as follows.
|
||||
|
||||
Changing the lock time or freezing the funds will change the expected
|
||||
balance because the time where the flows end changes. It will change the
|
||||
expected funds of each account by `timedelta*(incoming - outgoing)` where
|
||||
`timedelta` is the difference of the previous and the new end time of
|
||||
flows. So the sum of all expected funds is changed by
|
||||
`timedelta*(sum of incoming - sum of outgoing)`. This is zero because
|
||||
of Property 3.
|
||||
|
||||
@ -19,4 +19,5 @@
|
||||
"loop_iter": "3",
|
||||
"optimistic_hashing": true,
|
||||
"hashing_length_bound": "512",
|
||||
"solc": "solc8.28",
|
||||
}
|
||||
|
||||
45
certora/confs/Vault.conf
Normal file
45
certora/confs/Vault.conf
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"files": [
|
||||
"certora/harness/VaultHarness.sol",
|
||||
"certora/helpers/ERC20A.sol",
|
||||
],
|
||||
"parametric_contracts": ["VaultHarness"],
|
||||
"link" : [
|
||||
"VaultHarness:_token=ERC20A",
|
||||
],
|
||||
"packages": [
|
||||
"@openzeppelin/=node_modules/@openzeppelin/",
|
||||
],
|
||||
//"msg": "Verifying Vault",
|
||||
"multi_assert_check": true,
|
||||
"rule_sanity": "basic",
|
||||
"verify": "VaultHarness:certora/specs/Vault.spec",
|
||||
"loop_iter": "3",
|
||||
"build_cache": true,
|
||||
"solc": "solc8.28",
|
||||
"prover_version": "master", // remove with next Certora release
|
||||
"mutations": {
|
||||
"manual_mutants": [
|
||||
{
|
||||
"file_to_mutate": "contracts/Vault.sol",
|
||||
"mutants_location": "certora/mutations/Vault"
|
||||
},
|
||||
{
|
||||
"file_to_mutate": "contracts/Timestamps.sol",
|
||||
"mutants_location": "certora/mutations/Timestamps"
|
||||
},
|
||||
{
|
||||
"file_to_mutate": "contracts/vault/Accounts.sol",
|
||||
"mutants_location": "certora/mutations/Accounts"
|
||||
},
|
||||
{
|
||||
"file_to_mutate": "contracts/vault/Funds.sol",
|
||||
"mutants_location": "certora/mutations/Funds"
|
||||
},
|
||||
{
|
||||
"file_to_mutate": "contracts/vault/VaultBase.sol",
|
||||
"mutants_location": "certora/mutations/VaultBase"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
22
certora/harness/VaultHarness.sol
Normal file
22
certora/harness/VaultHarness.sol
Normal file
@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import "../../contracts/Vault.sol";
|
||||
|
||||
contract VaultHarness is Vault {
|
||||
constructor(IERC20 token) Vault(token) {}
|
||||
|
||||
function publicStatus(
|
||||
Controller controller,
|
||||
FundId fundId
|
||||
) public view returns (FundStatus) {
|
||||
return _getFundStatus(controller, fundId);
|
||||
}
|
||||
|
||||
function unwrapTimestamp(
|
||||
Timestamp timestamp
|
||||
) public pure returns (uint40) {
|
||||
return Timestamp.unwrap(timestamp);
|
||||
}
|
||||
|
||||
}
|
||||
13
certora/mutations/001_accumulateFlows_off_by_one.patch
Normal file
13
certora/mutations/001_accumulateFlows_off_by_one.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/Accounts.sol b/contracts/vault/Accounts.sol
|
||||
index 3066e7d..c93554d 100644
|
||||
--- a/contracts/vault/Accounts.sol
|
||||
+++ b/contracts/vault/Accounts.sol
|
||||
@@ -81,7 +81,7 @@ library Accounts {
|
||||
Timestamp timestamp
|
||||
) internal pure {
|
||||
Duration duration = account.flow.updated.until(timestamp);
|
||||
- account.balance.available -= account.flow.outgoing.accumulate(duration);
|
||||
+ account.balance.available -= account.flow.outgoing.accumulate(duration) + 1;
|
||||
account.balance.designated += account.flow.incoming.accumulate(duration);
|
||||
account.flow.updated = timestamp;
|
||||
}
|
||||
13
certora/mutations/002_flowOut_rate_calculation.patch
Normal file
13
certora/mutations/002_flowOut_rate_calculation.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/Accounts.sol b/contracts/vault/Accounts.sol
|
||||
index 3066e7d..7ddae11 100644
|
||||
--- a/contracts/vault/Accounts.sol
|
||||
+++ b/contracts/vault/Accounts.sol
|
||||
@@ -102,7 +102,7 @@ library Accounts {
|
||||
if (rate <= account.flow.incoming) {
|
||||
account.flow.incoming = account.flow.incoming - rate;
|
||||
} else {
|
||||
- account.flow.outgoing = account.flow.outgoing + rate;
|
||||
+ account.flow.outgoing = account.flow.outgoing + rate + TokensPerSecond.wrap(1);
|
||||
account.flow.outgoing = account.flow.outgoing - account.flow.incoming;
|
||||
account.flow.incoming = TokensPerSecond.wrap(0);
|
||||
}
|
||||
13
certora/mutations/003_status_boundary_condition.patch
Normal file
13
certora/mutations/003_status_boundary_condition.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/Funds.sol b/contracts/vault/Funds.sol
|
||||
index 471c2d9..1b0e7b9 100644
|
||||
--- a/contracts/vault/Funds.sol
|
||||
+++ b/contracts/vault/Funds.sol
|
||||
@@ -37,7 +37,7 @@ enum FundStatus {
|
||||
|
||||
library Funds {
|
||||
function status(Fund memory fund) internal view returns (FundStatus) {
|
||||
- if (Timestamps.currentTime() < fund.lockExpiry) {
|
||||
+ if (Timestamps.currentTime() <= fund.lockExpiry) {
|
||||
if (fund.frozenAt != Timestamp.wrap(0)) {
|
||||
return FundStatus.Frozen;
|
||||
}
|
||||
13
certora/mutations/004_isSolventAt_boundary_condition.patch
Normal file
13
certora/mutations/004_isSolventAt_boundary_condition.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/Accounts.sol b/contracts/vault/Accounts.sol
|
||||
index 3066e7d..c4db427 100644
|
||||
--- a/contracts/vault/Accounts.sol
|
||||
+++ b/contracts/vault/Accounts.sol
|
||||
@@ -69,7 +69,7 @@ library Accounts {
|
||||
) internal pure returns (bool) {
|
||||
Duration duration = account.flow.updated.until(timestamp);
|
||||
uint128 outgoing = account.flow.outgoing.accumulate(duration);
|
||||
- return outgoing <= account.balance.available;
|
||||
+ return outgoing < account.balance.available;
|
||||
}
|
||||
|
||||
/// Updates the available and designated balances by accumulating the
|
||||
13
certora/mutations/005_flowIn_future_timestamp.patch
Normal file
13
certora/mutations/005_flowIn_future_timestamp.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/Accounts.sol b/contracts/vault/Accounts.sol
|
||||
index 3066e7d..1d2ff07 100644
|
||||
--- a/contracts/vault/Accounts.sol
|
||||
+++ b/contracts/vault/Accounts.sol
|
||||
@@ -89,7 +89,7 @@ library Accounts {
|
||||
/// Starts an incoming flow of tokens at the specified rate. If there already
|
||||
/// is a flow of incoming tokens, then its rate is increased accordingly.
|
||||
function flowIn(Account memory account, TokensPerSecond rate) internal view {
|
||||
- account.accumulateFlows(Timestamps.currentTime());
|
||||
+ account.accumulateFlows(Timestamps.currentTime().add(Duration.wrap(1)));
|
||||
account.flow.incoming = account.flow.incoming + rate;
|
||||
}
|
||||
|
||||
13
certora/mutations/006_flow_prelock_bypass.patch
Normal file
13
certora/mutations/006_flow_prelock_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol
|
||||
index be21481..22deafc 100644
|
||||
--- a/contracts/vault/VaultBase.sol
|
||||
+++ b/contracts/vault/VaultBase.sol
|
||||
@@ -189,7 +189,7 @@ abstract contract VaultBase {
|
||||
TokensPerSecond rate
|
||||
) internal {
|
||||
Fund memory fund = _funds[controller][fundId];
|
||||
- require(fund.status() == FundStatus.Locked, VaultFundNotLocked());
|
||||
+ require(fund.status() == FundStatus.Locked || fund.status() == FundStatus.Inactive, VaultFundNotLocked());
|
||||
|
||||
Account memory sender = _accounts[controller][fundId][from];
|
||||
sender.flowOut(rate);
|
||||
13
certora/mutations/007_checkAccountInvariant_bypass.patch
Normal file
13
certora/mutations/007_checkAccountInvariant_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol
|
||||
index be21481..c90a675 100644
|
||||
--- a/contracts/vault/VaultBase.sol
|
||||
+++ b/contracts/vault/VaultBase.sol
|
||||
@@ -268,7 +268,7 @@ abstract contract VaultBase {
|
||||
Account memory account,
|
||||
Fund memory fund
|
||||
) private pure {
|
||||
- require(account.isSolventAt(fund.lockMaximum), VaultInsufficientBalance());
|
||||
+ // require(account.isSolventAt(fund.lockMaximum), VaultInsufficientBalance());
|
||||
}
|
||||
|
||||
error VaultInsufficientBalance();
|
||||
13
certora/mutations/008_deposit_storage_corruption.patch
Normal file
13
certora/mutations/008_deposit_storage_corruption.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol
|
||||
index be21481..9e22028 100644
|
||||
--- a/contracts/vault/VaultBase.sol
|
||||
+++ b/contracts/vault/VaultBase.sol
|
||||
@@ -130,7 +130,7 @@ abstract contract VaultBase {
|
||||
Fund storage fund = _funds[controller][fundId];
|
||||
require(fund.status() == FundStatus.Locked, VaultFundNotLocked());
|
||||
|
||||
- Account storage account = _accounts[controller][fundId][accountId];
|
||||
+ Account memory account = _accounts[controller][fundId][accountId];
|
||||
|
||||
account.balance.available += amount;
|
||||
|
||||
13
certora/mutations/009_transfer_balance_check_removal.patch
Normal file
13
certora/mutations/009_transfer_balance_check_removal.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol
|
||||
index be21481..94fca9a 100644
|
||||
--- a/contracts/vault/VaultBase.sol
|
||||
+++ b/contracts/vault/VaultBase.sol
|
||||
@@ -171,7 +171,7 @@ abstract contract VaultBase {
|
||||
require(fund.status() == FundStatus.Locked, VaultFundNotLocked());
|
||||
|
||||
Account memory sender = _accounts[controller][fundId][from];
|
||||
- require(amount <= sender.balance.available, VaultInsufficientBalance());
|
||||
+ // require(amount <= sender.balance.available, VaultInsufficientBalance());
|
||||
|
||||
sender.balance.available -= amount;
|
||||
_checkAccountInvariant(sender, fund);
|
||||
13
certora/mutations/010_burnAccount_logic_inversion.patch
Normal file
13
certora/mutations/010_burnAccount_logic_inversion.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol
|
||||
index be21481..f22b4ed 100644
|
||||
--- a/contracts/vault/VaultBase.sol
|
||||
+++ b/contracts/vault/VaultBase.sol
|
||||
@@ -227,7 +227,7 @@ abstract contract VaultBase {
|
||||
require(fund.status() == FundStatus.Locked, VaultFundNotLocked());
|
||||
|
||||
Account memory account = _accounts[controller][fundId][accountId];
|
||||
- require(account.flow.incoming == account.flow.outgoing, VaultFlowNotZero());
|
||||
+ require(!(account.flow.incoming == account.flow.outgoing), VaultFlowNotZero());
|
||||
uint128 amount = account.balance.available + account.balance.designated;
|
||||
|
||||
delete _accounts[controller][fundId][accountId];
|
||||
17
certora/mutations/011_withdraw_reentrancy_ordering.patch
Normal file
17
certora/mutations/011_withdraw_reentrancy_ordering.patch
Normal file
@ -0,0 +1,17 @@
|
||||
diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol
|
||||
index be21481..ddfaad7 100644
|
||||
--- a/contracts/vault/VaultBase.sol
|
||||
+++ b/contracts/vault/VaultBase.sol
|
||||
@@ -254,10 +254,10 @@ abstract contract VaultBase {
|
||||
account.accumulateFlows(fund.flowEnd());
|
||||
uint128 amount = account.balance.available + account.balance.designated;
|
||||
|
||||
- delete _accounts[controller][fundId][accountId];
|
||||
-
|
||||
(address owner, ) = Accounts.decodeId(accountId);
|
||||
_token.safeTransfer(owner, amount);
|
||||
+
|
||||
+ delete _accounts[controller][fundId][accountId];
|
||||
}
|
||||
|
||||
function _checkLockInvariant(Fund memory fund) private pure {
|
||||
13
certora/mutations/012_withdraw_unsafe_transfer.patch
Normal file
13
certora/mutations/012_withdraw_unsafe_transfer.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol
|
||||
index be21481..f3eef7d 100644
|
||||
--- a/contracts/vault/VaultBase.sol
|
||||
+++ b/contracts/vault/VaultBase.sol
|
||||
@@ -257,7 +257,7 @@ abstract contract VaultBase {
|
||||
delete _accounts[controller][fundId][accountId];
|
||||
|
||||
(address owner, ) = Accounts.decodeId(accountId);
|
||||
- _token.safeTransfer(owner, amount);
|
||||
+ _token.transfer(owner, amount);
|
||||
}
|
||||
|
||||
function _checkLockInvariant(Fund memory fund) private pure {
|
||||
13
certora/mutations/013_deposit_unsafe_transferFrom.patch
Normal file
13
certora/mutations/013_deposit_unsafe_transferFrom.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol
|
||||
index be21481..30c52fd 100644
|
||||
--- a/contracts/vault/VaultBase.sol
|
||||
+++ b/contracts/vault/VaultBase.sol
|
||||
@@ -134,7 +134,7 @@ abstract contract VaultBase {
|
||||
|
||||
account.balance.available += amount;
|
||||
|
||||
- _token.safeTransferFrom(
|
||||
+ _token.transferFrom(
|
||||
Controller.unwrap(controller),
|
||||
address(this),
|
||||
amount
|
||||
40
certora/mutations/014_all_unsafe_token_transfers.patch
Normal file
40
certora/mutations/014_all_unsafe_token_transfers.patch
Normal file
@ -0,0 +1,40 @@
|
||||
diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol
|
||||
index be21481..5efdc8e 100644
|
||||
--- a/contracts/vault/VaultBase.sol
|
||||
+++ b/contracts/vault/VaultBase.sol
|
||||
@@ -134,7 +134,7 @@ abstract contract VaultBase {
|
||||
|
||||
account.balance.available += amount;
|
||||
|
||||
- _token.safeTransferFrom(
|
||||
+ _token.transferFrom(
|
||||
Controller.unwrap(controller),
|
||||
address(this),
|
||||
amount
|
||||
@@ -215,7 +215,7 @@ abstract contract VaultBase {
|
||||
|
||||
account.balance.designated -= amount;
|
||||
|
||||
- _token.safeTransfer(address(0xdead), amount);
|
||||
+ _token.transfer(address(0xdead), amount);
|
||||
}
|
||||
|
||||
function _burnAccount(
|
||||
@@ -232,7 +232,7 @@ abstract contract VaultBase {
|
||||
|
||||
delete _accounts[controller][fundId][accountId];
|
||||
|
||||
- _token.safeTransfer(address(0xdead), amount);
|
||||
+ _token.transfer(address(0xdead), amount);
|
||||
}
|
||||
|
||||
function _freezeFund(Controller controller, FundId fundId) internal {
|
||||
@@ -257,7 +257,7 @@ abstract contract VaultBase {
|
||||
delete _accounts[controller][fundId][accountId];
|
||||
|
||||
(address owner, ) = Accounts.decodeId(accountId);
|
||||
- _token.safeTransfer(owner, amount);
|
||||
+ _token.transfer(owner, amount);
|
||||
}
|
||||
|
||||
function _checkLockInvariant(Fund memory fund) private pure {
|
||||
14
certora/mutations/015_transfer_authorization_bypass.patch
Normal file
14
certora/mutations/015_transfer_authorization_bypass.patch
Normal file
@ -0,0 +1,14 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..5dd18c8 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -164,7 +164,8 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
AccountId to,
|
||||
uint128 amount
|
||||
) public whenNotPaused {
|
||||
- Controller controller = Controller.wrap(msg.sender);
|
||||
+ (address holder, ) = Accounts.decodeId(from);
|
||||
+ Controller controller = Controller.wrap(holder);
|
||||
_transfer(controller, fundId, from, to, amount);
|
||||
}
|
||||
|
||||
18
certora/mutations/016_pause_functions_deleted.patch
Normal file
18
certora/mutations/016_pause_functions_deleted.patch
Normal file
@ -0,0 +1,18 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..185ed83 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -240,13 +240,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
_withdraw(controller, fund, accountId);
|
||||
}
|
||||
|
||||
- function pause() public onlyOwner {
|
||||
- _pause();
|
||||
- }
|
||||
|
||||
- function unpause() public onlyOwner {
|
||||
- _unpause();
|
||||
- }
|
||||
|
||||
error VaultOnlyAccountHolder();
|
||||
}
|
||||
13
certora/mutations/017_accountId_encoding_corruption.patch
Normal file
13
certora/mutations/017_accountId_encoding_corruption.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/Accounts.sol b/contracts/vault/Accounts.sol
|
||||
index 3066e7d..ae138ae 100644
|
||||
--- a/contracts/vault/Accounts.sol
|
||||
+++ b/contracts/vault/Accounts.sol
|
||||
@@ -50,7 +50,7 @@ library Accounts {
|
||||
) internal pure returns (AccountId) {
|
||||
bytes32 left = bytes32(bytes20(holder));
|
||||
bytes32 right = bytes32(uint256(uint96(discriminator)));
|
||||
- return AccountId.wrap(left | right);
|
||||
+ return AccountId.wrap(left & right);
|
||||
}
|
||||
|
||||
/// Extracts the account holder and the discriminator from the the account id
|
||||
@ -0,0 +1,12 @@
|
||||
diff --git a/contracts/Timestamps.sol b/contracts/Timestamps.sol
|
||||
index 945eced..3132942 100644
|
||||
--- a/contracts/Timestamps.sol
|
||||
+++ b/contracts/Timestamps.sol
|
||||
@@ -65,6 +65,6 @@ library Timestamps {
|
||||
Timestamp start,
|
||||
Timestamp end
|
||||
) internal pure returns (Duration) {
|
||||
- return Duration.wrap(Timestamp.unwrap(end) - Timestamp.unwrap(start));
|
||||
+ return Duration.wrap(Timestamp.unwrap(start) - Timestamp.unwrap(end));
|
||||
}
|
||||
}
|
||||
13
certora/mutations/019_period_calculation_round_up.patch
Normal file
13
certora/mutations/019_period_calculation_round_up.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Periods.sol b/contracts/Periods.sol
|
||||
index 189fb02..a54c439 100644
|
||||
--- a/contracts/Periods.sol
|
||||
+++ b/contracts/Periods.sol
|
||||
@@ -20,7 +20,7 @@ contract Periods {
|
||||
function _periodOf(Timestamp timestamp) internal view returns (Period) {
|
||||
return
|
||||
Period.wrap(
|
||||
- Timestamp.unwrap(timestamp) / Duration.unwrap(_secondsPerPeriod)
|
||||
+ (Timestamp.unwrap(timestamp) + Duration.unwrap(_secondsPerPeriod) - 1) / Duration.unwrap(_secondsPerPeriod)
|
||||
);
|
||||
}
|
||||
|
||||
13
certora/mutations/020_withdraw_pause_bypass.patch
Normal file
13
certora/mutations/020_withdraw_pause_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..8397373 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -221,7 +221,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
/// ⚠️ The account holder can also withdraw itself, so when designing a smart
|
||||
/// contract that controls funds in the vault, don't assume that only this
|
||||
/// smart contract can initiate a withdrawal ⚠️
|
||||
- function withdraw(FundId fund, AccountId accountId) public whenNotPaused {
|
||||
+ function withdraw(FundId fund, AccountId accountId) public {
|
||||
Controller controller = Controller.wrap(msg.sender);
|
||||
_withdraw(controller, fund, accountId);
|
||||
}
|
||||
13
certora/mutations/021_deposit_pause_bypass.patch
Normal file
13
certora/mutations/021_deposit_pause_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..ac8b1cd 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -138,7 +138,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
FundId fundId,
|
||||
AccountId accountId,
|
||||
uint128 amount
|
||||
- ) public whenNotPaused {
|
||||
+ ) public {
|
||||
Controller controller = Controller.wrap(msg.sender);
|
||||
_deposit(controller, fundId, accountId, amount);
|
||||
}
|
||||
13
certora/mutations/022_transfer_pause_bypass.patch
Normal file
13
certora/mutations/022_transfer_pause_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..42a3b9b 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -163,7 +163,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
AccountId from,
|
||||
AccountId to,
|
||||
uint128 amount
|
||||
- ) public whenNotPaused {
|
||||
+ ) public {
|
||||
Controller controller = Controller.wrap(msg.sender);
|
||||
_transfer(controller, fundId, from, to, amount);
|
||||
}
|
||||
13
certora/mutations/023_flow_pause_bypass.patch
Normal file
13
certora/mutations/023_flow_pause_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..52b7db0 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -180,7 +180,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
AccountId from,
|
||||
AccountId to,
|
||||
TokensPerSecond rate
|
||||
- ) public whenNotPaused {
|
||||
+ ) public {
|
||||
Controller controller = Controller.wrap(msg.sender);
|
||||
_flow(controller, fundId, from, to, rate);
|
||||
}
|
||||
13
certora/mutations/024_lock_pause_bypass.patch
Normal file
13
certora/mutations/024_lock_pause_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..26a17b0 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -116,7 +116,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
FundId fundId,
|
||||
Timestamp expiry,
|
||||
Timestamp maximum
|
||||
- ) public whenNotPaused {
|
||||
+ ) public {
|
||||
Controller controller = Controller.wrap(msg.sender);
|
||||
_lock(controller, fundId, expiry, maximum);
|
||||
}
|
||||
13
certora/mutations/025_extendLock_pause_bypass.patch
Normal file
13
certora/mutations/025_extendLock_pause_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..f8fcddf 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -125,7 +125,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
/// the existing expiry, but no later than the maximum timestamp that was
|
||||
/// provided when locking the fund.
|
||||
/// Only allowed when the lock has not unlocked yet.
|
||||
- function extendLock(FundId fundId, Timestamp expiry) public whenNotPaused {
|
||||
+ function extendLock(FundId fundId, Timestamp expiry) public {
|
||||
Controller controller = Controller.wrap(msg.sender);
|
||||
_extendLock(controller, fundId, expiry);
|
||||
}
|
||||
13
certora/mutations/026_designate_pause_bypass.patch
Normal file
13
certora/mutations/026_designate_pause_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..15af8af 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -151,7 +151,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
FundId fundId,
|
||||
AccountId accountId,
|
||||
uint128 amount
|
||||
- ) public whenNotPaused {
|
||||
+ ) public {
|
||||
Controller controller = Controller.wrap(msg.sender);
|
||||
_designate(controller, fundId, accountId, amount);
|
||||
}
|
||||
13
certora/mutations/027_burnDesignated_pause_bypass.patch
Normal file
13
certora/mutations/027_burnDesignated_pause_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..c9b39f2 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -191,7 +191,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
FundId fundId,
|
||||
AccountId accountId,
|
||||
uint128 amount
|
||||
- ) public whenNotPaused {
|
||||
+ ) public {
|
||||
Controller controller = Controller.wrap(msg.sender);
|
||||
_burnDesignated(controller, fundId, accountId, amount);
|
||||
}
|
||||
13
certora/mutations/028_burnAccount_pause_bypass.patch
Normal file
13
certora/mutations/028_burnAccount_pause_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..de9c8d2 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -202,7 +202,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
function burnAccount(
|
||||
FundId fundId,
|
||||
AccountId accountId
|
||||
- ) public whenNotPaused {
|
||||
+ ) public {
|
||||
Controller controller = Controller.wrap(msg.sender);
|
||||
_burnAccount(controller, fundId, accountId);
|
||||
}
|
||||
13
certora/mutations/029_freezeFund_pause_bypass.patch
Normal file
13
certora/mutations/029_freezeFund_pause_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..4037209 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -210,7 +210,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
/// Freezes a fund. Stops all tokens flows and disallows any operations on the
|
||||
/// fund until it unlocks.
|
||||
/// Only allowed when the fund is locked.
|
||||
- function freezeFund(FundId fundId) public whenNotPaused {
|
||||
+ function freezeFund(FundId fundId) public {
|
||||
Controller controller = Controller.wrap(msg.sender);
|
||||
_freezeFund(controller, fundId);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..0d76718 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -234,7 +234,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
Controller controller,
|
||||
FundId fund,
|
||||
AccountId accountId
|
||||
- ) public {
|
||||
+ ) public whenNotPaused {
|
||||
(address holder, ) = Accounts.decodeId(accountId);
|
||||
require(msg.sender == holder, VaultOnlyAccountHolder());
|
||||
_withdraw(controller, fund, accountId);
|
||||
13
certora/mutations/031_pause_access_control_bypass.patch
Normal file
13
certora/mutations/031_pause_access_control_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..c98627e 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -240,7 +240,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
_withdraw(controller, fund, accountId);
|
||||
}
|
||||
|
||||
- function pause() public onlyOwner {
|
||||
+ function pause() public {
|
||||
_pause();
|
||||
}
|
||||
|
||||
13
certora/mutations/032_unpause_access_control_bypass.patch
Normal file
13
certora/mutations/032_unpause_access_control_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..6b80271 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -244,7 +244,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
_pause();
|
||||
}
|
||||
|
||||
- function unpause() public onlyOwner {
|
||||
+ function unpause() public {
|
||||
_unpause();
|
||||
}
|
||||
|
||||
13
certora/mutations/033_withdrawByRecipient_auth_bypass.patch
Normal file
13
certora/mutations/033_withdrawByRecipient_auth_bypass.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Vault.sol b/contracts/Vault.sol
|
||||
index 8433a08..e7e012d 100644
|
||||
--- a/contracts/Vault.sol
|
||||
+++ b/contracts/Vault.sol
|
||||
@@ -236,7 +236,7 @@ contract Vault is VaultBase, Pausable, Ownable {
|
||||
AccountId accountId
|
||||
) public {
|
||||
(address holder, ) = Accounts.decodeId(accountId);
|
||||
- require(msg.sender == holder, VaultOnlyAccountHolder());
|
||||
+ // require(msg.sender == holder, VaultOnlyAccountHolder());
|
||||
_withdraw(controller, fund, accountId);
|
||||
}
|
||||
|
||||
13
certora/mutations/034_nextPeriod_advancement_failure.patch
Normal file
13
certora/mutations/034_nextPeriod_advancement_failure.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/Periods.sol b/contracts/Periods.sol
|
||||
index 189fb02..817d09c 100644
|
||||
--- a/contracts/Periods.sol
|
||||
+++ b/contracts/Periods.sol
|
||||
@@ -29,7 +29,7 @@ contract Periods {
|
||||
}
|
||||
|
||||
function _nextPeriod(Period period) internal pure returns (Period) {
|
||||
- return Period.wrap(Period.unwrap(period) + 1);
|
||||
+ return Period.wrap(Period.unwrap(period));
|
||||
}
|
||||
|
||||
function _periodStart(Period period) internal view returns (Timestamp) {
|
||||
13
certora/mutations/035_withdraw_account_not_deleted.patch
Normal file
13
certora/mutations/035_withdraw_account_not_deleted.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol
|
||||
index be21481..ed1fb5f 100644
|
||||
--- a/contracts/vault/VaultBase.sol
|
||||
+++ b/contracts/vault/VaultBase.sol
|
||||
@@ -254,8 +254,6 @@ abstract contract VaultBase {
|
||||
account.accumulateFlows(fund.flowEnd());
|
||||
uint128 amount = account.balance.available + account.balance.designated;
|
||||
|
||||
- delete _accounts[controller][fundId][accountId];
|
||||
-
|
||||
(address owner, ) = Accounts.decodeId(accountId);
|
||||
_token.safeTransfer(owner, amount);
|
||||
}
|
||||
13
certora/mutations/036_burnAccount_not_deleted.patch
Normal file
13
certora/mutations/036_burnAccount_not_deleted.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol
|
||||
index be21481..eb679d1 100644
|
||||
--- a/contracts/vault/VaultBase.sol
|
||||
+++ b/contracts/vault/VaultBase.sol
|
||||
@@ -230,8 +230,6 @@ abstract contract VaultBase {
|
||||
require(account.flow.incoming == account.flow.outgoing, VaultFlowNotZero());
|
||||
uint128 amount = account.balance.available + account.balance.designated;
|
||||
|
||||
- delete _accounts[controller][fundId][accountId];
|
||||
-
|
||||
_token.safeTransfer(address(0xdead), amount);
|
||||
}
|
||||
|
||||
13
certora/mutations/037_burnDesignated_no_decrement.patch
Normal file
13
certora/mutations/037_burnDesignated_no_decrement.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol
|
||||
index be21481..29725dc 100644
|
||||
--- a/contracts/vault/VaultBase.sol
|
||||
+++ b/contracts/vault/VaultBase.sol
|
||||
@@ -213,7 +213,7 @@ abstract contract VaultBase {
|
||||
Account storage account = _accounts[controller][fundId][accountId];
|
||||
require(account.balance.designated >= amount, VaultInsufficientBalance());
|
||||
|
||||
- account.balance.designated -= amount;
|
||||
+ // account.balance.designated -= amount;
|
||||
|
||||
_token.safeTransfer(address(0xdead), amount);
|
||||
}
|
||||
12
certora/mutations/038_extendLock_beyond_max.patch
Normal file
12
certora/mutations/038_extendLock_beyond_max.patch
Normal file
@ -0,0 +1,12 @@
|
||||
diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol
|
||||
index be21481..e461233 100644
|
||||
--- a/contracts/vault/VaultBase.sol
|
||||
+++ b/contracts/vault/VaultBase.sol
|
||||
@@ -117,7 +117,6 @@ abstract contract VaultBase {
|
||||
require(fund.status() == FundStatus.Locked, VaultFundNotLocked());
|
||||
require(fund.lockExpiry <= expiry, VaultInvalidExpiry());
|
||||
fund.lockExpiry = expiry;
|
||||
- _checkLockInvariant(fund);
|
||||
_funds[controller][fundId] = fund;
|
||||
}
|
||||
|
||||
13
certora/mutations/039_freezeFunds_when_withdrawing.patch
Normal file
13
certora/mutations/039_freezeFunds_when_withdrawing.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/contracts/vault/VaultBase.sol b/contracts/vault/VaultBase.sol
|
||||
index be21481..04e914f 100644
|
||||
--- a/contracts/vault/VaultBase.sol
|
||||
+++ b/contracts/vault/VaultBase.sol
|
||||
@@ -237,7 +237,7 @@ abstract contract VaultBase {
|
||||
|
||||
function _freezeFund(Controller controller, FundId fundId) internal {
|
||||
Fund storage fund = _funds[controller][fundId];
|
||||
- require(fund.status() == FundStatus.Locked, VaultFundNotLocked());
|
||||
+ require(fund.status() == FundStatus.Locked || fund.status() == FundStatus.Withdrawing, VaultFundNotLocked());
|
||||
|
||||
fund.frozenAt = Timestamps.currentTime();
|
||||
}
|
||||
1
certora/mutations/Accounts/001_accumulateFlows_off_by_one.patch
Symbolic link
1
certora/mutations/Accounts/001_accumulateFlows_off_by_one.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../001_accumulateFlows_off_by_one.patch
|
||||
1
certora/mutations/Accounts/002_flowOut_rate_calculation.patch
Symbolic link
1
certora/mutations/Accounts/002_flowOut_rate_calculation.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../002_flowOut_rate_calculation.patch
|
||||
@ -0,0 +1 @@
|
||||
../004_isSolventAt_boundary_condition.patch
|
||||
1
certora/mutations/Accounts/005_flowIn_future_timestamp.patch
Symbolic link
1
certora/mutations/Accounts/005_flowIn_future_timestamp.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../005_flowIn_future_timestamp.patch
|
||||
@ -0,0 +1 @@
|
||||
../017_accountId_encoding_corruption.patch
|
||||
1
certora/mutations/Funds/003_status_boundary_condition.patch
Symbolic link
1
certora/mutations/Funds/003_status_boundary_condition.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../003_status_boundary_condition.patch
|
||||
@ -0,0 +1 @@
|
||||
../018_duration_calculation_order_inversion.patch
|
||||
1
certora/mutations/Vault/015_transfer_authorization_bypass.patch
Symbolic link
1
certora/mutations/Vault/015_transfer_authorization_bypass.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../015_transfer_authorization_bypass.patch
|
||||
1
certora/mutations/Vault/016_pause_functions_deleted.patch
Symbolic link
1
certora/mutations/Vault/016_pause_functions_deleted.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../016_pause_functions_deleted.patch
|
||||
1
certora/mutations/Vault/020_withdraw_pause_bypass.patch
Symbolic link
1
certora/mutations/Vault/020_withdraw_pause_bypass.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../020_withdraw_pause_bypass.patch
|
||||
1
certora/mutations/Vault/021_deposit_pause_bypass.patch
Symbolic link
1
certora/mutations/Vault/021_deposit_pause_bypass.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../021_deposit_pause_bypass.patch
|
||||
1
certora/mutations/Vault/022_transfer_pause_bypass.patch
Symbolic link
1
certora/mutations/Vault/022_transfer_pause_bypass.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../022_transfer_pause_bypass.patch
|
||||
1
certora/mutations/Vault/023_flow_pause_bypass.patch
Symbolic link
1
certora/mutations/Vault/023_flow_pause_bypass.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../023_flow_pause_bypass.patch
|
||||
1
certora/mutations/Vault/024_lock_pause_bypass.patch
Symbolic link
1
certora/mutations/Vault/024_lock_pause_bypass.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../024_lock_pause_bypass.patch
|
||||
1
certora/mutations/Vault/025_extendLock_pause_bypass.patch
Symbolic link
1
certora/mutations/Vault/025_extendLock_pause_bypass.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../025_extendLock_pause_bypass.patch
|
||||
1
certora/mutations/Vault/026_designate_pause_bypass.patch
Symbolic link
1
certora/mutations/Vault/026_designate_pause_bypass.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../026_designate_pause_bypass.patch
|
||||
1
certora/mutations/Vault/027_burnDesignated_pause_bypass.patch
Symbolic link
1
certora/mutations/Vault/027_burnDesignated_pause_bypass.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../027_burnDesignated_pause_bypass.patch
|
||||
1
certora/mutations/Vault/028_burnAccount_pause_bypass.patch
Symbolic link
1
certora/mutations/Vault/028_burnAccount_pause_bypass.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../028_burnAccount_pause_bypass.patch
|
||||
1
certora/mutations/Vault/029_freezeFund_pause_bypass.patch
Symbolic link
1
certora/mutations/Vault/029_freezeFund_pause_bypass.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../029_freezeFund_pause_bypass.patch
|
||||
@ -0,0 +1 @@
|
||||
../030_add_missing_whenNotPaused_withdrawByRecipient.patch
|
||||
1
certora/mutations/Vault/031_pause_access_control_bypass.patch
Symbolic link
1
certora/mutations/Vault/031_pause_access_control_bypass.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../031_pause_access_control_bypass.patch
|
||||
1
certora/mutations/Vault/032_unpause_access_control_bypass.patch
Symbolic link
1
certora/mutations/Vault/032_unpause_access_control_bypass.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../032_unpause_access_control_bypass.patch
|
||||
@ -0,0 +1 @@
|
||||
../033_withdrawByRecipient_auth_bypass.patch
|
||||
1
certora/mutations/VaultBase/006_flow_prelock_bypass.patch
Symbolic link
1
certora/mutations/VaultBase/006_flow_prelock_bypass.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../006_flow_prelock_bypass.patch
|
||||
@ -0,0 +1 @@
|
||||
../007_checkAccountInvariant_bypass.patch
|
||||
1
certora/mutations/VaultBase/008_deposit_storage_corruption.patch
Symbolic link
1
certora/mutations/VaultBase/008_deposit_storage_corruption.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../008_deposit_storage_corruption.patch
|
||||
@ -0,0 +1 @@
|
||||
../009_transfer_balance_check_removal.patch
|
||||
@ -0,0 +1 @@
|
||||
../010_burnAccount_logic_inversion.patch
|
||||
@ -0,0 +1 @@
|
||||
../011_withdraw_reentrancy_ordering.patch
|
||||
1
certora/mutations/VaultBase/012_withdraw_unsafe_transfer.patch
Symbolic link
1
certora/mutations/VaultBase/012_withdraw_unsafe_transfer.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../012_withdraw_unsafe_transfer.patch
|
||||
@ -0,0 +1 @@
|
||||
../013_deposit_unsafe_transferFrom.patch
|
||||
1
certora/mutations/VaultBase/014_all_unsafe_token_transfers.patch
Symbolic link
1
certora/mutations/VaultBase/014_all_unsafe_token_transfers.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../014_all_unsafe_token_transfers.patch
|
||||
@ -0,0 +1 @@
|
||||
../035_withdraw_account_not_deleted.patch
|
||||
1
certora/mutations/VaultBase/036_burnAccount_not_deleted.patch
Symbolic link
1
certora/mutations/VaultBase/036_burnAccount_not_deleted.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../036_burnAccount_not_deleted.patch
|
||||
@ -0,0 +1 @@
|
||||
../037_burnDesignated_no_decrement.patch
|
||||
1
certora/mutations/VaultBase/038_extendLock_beyond_max.patch
Symbolic link
1
certora/mutations/VaultBase/038_extendLock_beyond_max.patch
Symbolic link
@ -0,0 +1 @@
|
||||
../038_extendLock_beyond_max.patch
|
||||
@ -0,0 +1 @@
|
||||
../039_freezeFunds_when_withdrawing.patch
|
||||
488
certora/specs/Vault.spec
Normal file
488
certora/specs/Vault.spec
Normal file
@ -0,0 +1,488 @@
|
||||
using ERC20A as Token;
|
||||
|
||||
methods {
|
||||
function unwrapTimestamp(VaultBase.Timestamp) external returns (uint40) envfree;
|
||||
function decodeAccountId(VaultBase.AccountId) external returns (address, bytes12) envfree;
|
||||
|
||||
function Token.totalSupply() external returns (uint256) envfree;
|
||||
}
|
||||
|
||||
// Timestamp reasoning.
|
||||
//
|
||||
// We use a ghost variable lastTimestamp to keep track of the last timestamp recorded in the contract.
|
||||
// This is used to ensure that timestamps are always increasing. We also ensure that the timestamp
|
||||
// does not exceed the uint40 range, which is sufficient for our use case (up to year 36812).
|
||||
|
||||
ghost mathint lastTimestamp {
|
||||
init_state axiom lastTimestamp > 0; // we must start with a positive timestamp (0 is used to encode "not set")
|
||||
}
|
||||
|
||||
hook TIMESTAMP uint256 time {
|
||||
require(to_mathint(time) < max_uint40, "timestamp must not exceed uint40 range (year 36812)");
|
||||
require(to_mathint(time) >= lastTimestamp, "timestamp must be increasing");
|
||||
lastTimestamp = time;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Expected Funds - needed for proving solvency.
|
||||
//
|
||||
// expectedFunds[controller][fundId][accountId] is a ghost variable that represents the expected funds for a given account in a fund.
|
||||
// It is calculated as:
|
||||
// availableBalance + designatedBalance + ((incoming - outgoing) * (flowEnd - updated))
|
||||
//
|
||||
// Here flowEnd is either frozenAt or lockExpiry, depending on whether the fund is frozen or not.
|
||||
// The variable updated is the last time the flow was updated for the account in the fund, so all flows before
|
||||
// are already considered in the availableBalance.
|
||||
//
|
||||
// We recompute expectedFunds in the store hooks whenever one of the dependencies changes.
|
||||
// To avoid negative values, we cap the expectedFunds to 0. It can only temporarily go negative and will
|
||||
// either revert (e.g. when setting outflow too high), or be corrected by another updated to a different variable.
|
||||
// We check that explicitly in the invariant expectedFundsMirror().
|
||||
|
||||
definition max(mathint a, mathint b) returns mathint = a >= b ? a : b;
|
||||
|
||||
definition flowEnd(VaultBase.Controller controller, VaultBase.FundId fundId) returns uint256
|
||||
= frozenAtMirror[controller][fundId] != 0
|
||||
? frozenAtMirror[controller][fundId]
|
||||
: lockExpiryMirror[controller][fundId];
|
||||
definition expectedFundsHelper(VaultBase.Controller controller, VaultBase.FundId fundId, VaultBase.AccountId accountId) returns mathint =
|
||||
availableBalanceMirror[controller][fundId][accountId]
|
||||
+ designatedBalanceMirror[controller][fundId][accountId]
|
||||
+ ((incomingMirror[controller][fundId][accountId]
|
||||
- outgoingMirror[controller][fundId][accountId])
|
||||
* (flowEnd(controller, fundId)
|
||||
- updatedMirror[controller][fundId][accountId]));
|
||||
definition expectedFundsDef(VaultBase.Controller controller, VaultBase.FundId fundId, VaultBase.AccountId accountId) returns mathint =
|
||||
max(expectedFundsHelper(controller, fundId, accountId), 0);
|
||||
|
||||
ghost mapping(VaultBase.Controller => mapping(VaultBase.FundId => mapping(VaultBase.AccountId => mathint))) expectedFunds {
|
||||
init_state axiom
|
||||
(forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
forall VaultBase.AccountId accountId.
|
||||
expectedFunds[controller][fundId][accountId] == 0) &&
|
||||
(usum VaultBase.AccountId accountId,
|
||||
VaultBase.Controller controller,
|
||||
VaultBase.FundId fundId.
|
||||
expectedFunds[controller][fundId][accountId]) == 0;
|
||||
}
|
||||
|
||||
definition sumOfExpectedFunds() returns mathint =
|
||||
(usum VaultBase.Controller controller,
|
||||
VaultBase.FundId fundId,
|
||||
VaultBase.AccountId accountId.
|
||||
expectedFunds[controller][fundId][accountId]);
|
||||
|
||||
|
||||
// mirror variables of balances in our dummy token.
|
||||
//
|
||||
// also prove that totalSupply equals the sum of all balances in the mirror. This is needed
|
||||
// to prevent overflows in transfer().
|
||||
|
||||
ghost mapping(address => uint256) tokenBalanceOfMirror {
|
||||
init_state axiom (forall address a. tokenBalanceOfMirror[a] == 0)
|
||||
&& (usum address a. tokenBalanceOfMirror[a]) == 0;
|
||||
}
|
||||
|
||||
hook Sload uint256 balance Token._balances[KEY address addr] {
|
||||
require(tokenBalanceOfMirror[addr] == balance, "tokenBalance mirror");
|
||||
}
|
||||
|
||||
hook Sstore Token._balances[KEY address addr] uint256 newValue (uint256 oldValue) {
|
||||
tokenBalanceOfMirror[addr] = newValue;
|
||||
}
|
||||
|
||||
invariant totalSupplyIsSumOfBalances()
|
||||
Token.totalSupply() == (usum address a. tokenBalanceOfMirror[a]);
|
||||
|
||||
|
||||
//------------------------------------------------------------//
|
||||
// Mirror variables for storage variables in VaultBase.
|
||||
//------------------------------------------------------------//
|
||||
|
||||
// mirror for lockExpiry.
|
||||
|
||||
ghost mapping(VaultBase.Controller => mapping(VaultBase.FundId => uint40)) lockExpiryMirror {
|
||||
init_state axiom
|
||||
forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
lockExpiryMirror[controller][fundId] == 0;
|
||||
}
|
||||
|
||||
hook Sload VaultBase.Timestamp defaultValue
|
||||
_funds[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId].lockExpiry
|
||||
{
|
||||
require(lockExpiryMirror[controller][fundId] == unwrapTimestamp(defaultValue), "lockExpiry mirror");
|
||||
}
|
||||
|
||||
hook Sstore _funds[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId].lockExpiry
|
||||
VaultBase.Timestamp defaultValue
|
||||
{
|
||||
lockExpiryMirror[controller][fundId] = unwrapTimestamp(defaultValue);
|
||||
|
||||
mathint oldSum = usum VaultBase.Controller c,
|
||||
VaultBase.FundId f,
|
||||
VaultBase.AccountId a.
|
||||
expectedFunds[c][f][a];
|
||||
|
||||
havoc expectedFunds assuming forall VaultBase.Controller c.
|
||||
forall VaultBase.FundId f.
|
||||
forall VaultBase.AccountId a.
|
||||
expectedFunds@new[c][f][a]
|
||||
== expectedFundsDef(c, f, a);
|
||||
|
||||
// The above update of expectedFunds changes the individual funds for each account, because the
|
||||
// flowEnd changes, but the sum of expected funds should not change, because the net flow between all funds is zero.
|
||||
// This would require advanced reasoning over sums:
|
||||
// The individual expectedFunds change by the amount
|
||||
// deltaTime * (incoming - outgoing)
|
||||
// The sum of these changes is
|
||||
// deltaTime * ((sum AccountId a. incoming[a]) - (sum AccountId a. outgoing[a]))
|
||||
// and that is zero, because of the invariant incomingEqualsOutgoing().
|
||||
//
|
||||
// This reasoning cannot be done by the certora prover and it's underlying SMT solvers. Instead, we
|
||||
// just require that this is true.
|
||||
require((usum VaultBase.Controller c,
|
||||
VaultBase.FundId f,
|
||||
VaultBase.AccountId a.
|
||||
expectedFunds[c][f][a]) == oldSum,
|
||||
"sum of expected funds should not change as net flow between all funds is zero");
|
||||
}
|
||||
|
||||
|
||||
// mirror for lockMaximum.
|
||||
|
||||
ghost mapping(VaultBase.Controller => mapping(VaultBase.FundId => uint40)) lockMaximumMirror {
|
||||
init_state axiom
|
||||
forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
lockMaximumMirror[controller][fundId] == 0;
|
||||
}
|
||||
|
||||
hook Sload VaultBase.Timestamp defaultValue
|
||||
_funds[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId].lockMaximum
|
||||
{
|
||||
require(lockMaximumMirror[controller][fundId] == unwrapTimestamp(defaultValue), "lockMaximum mirror");
|
||||
}
|
||||
|
||||
hook Sstore _funds[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId].lockMaximum
|
||||
VaultBase.Timestamp defaultValue
|
||||
{
|
||||
lockMaximumMirror[controller][fundId] = unwrapTimestamp(defaultValue);
|
||||
}
|
||||
|
||||
|
||||
// mirror for outgoing flow
|
||||
ghost mapping(VaultBase.Controller => mapping(VaultBase.FundId => mapping(VaultBase.AccountId => VaultBase.TokensPerSecond))) outgoingMirror {
|
||||
init_state axiom
|
||||
(forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
forall VaultBase.AccountId accountId.
|
||||
outgoingMirror[controller][fundId][accountId] == 0) &&
|
||||
(forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
(sum VaultBase.AccountId accountId. outgoingMirror[controller][fundId][accountId]) == 0);
|
||||
}
|
||||
|
||||
hook Sload VaultBase.TokensPerSecond defaultValue
|
||||
_accounts[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId][KEY VaultBase.AccountId accountId].flow.outgoing
|
||||
{
|
||||
require(outgoingMirror[controller][fundId][accountId] == defaultValue, "outgoing mirror");
|
||||
}
|
||||
|
||||
hook Sstore _accounts[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId][KEY VaultBase.AccountId accountId].flow.outgoing
|
||||
VaultBase.TokensPerSecond defaultValue
|
||||
{
|
||||
outgoingMirror[controller][fundId][accountId] = defaultValue;
|
||||
expectedFunds[controller][fundId][accountId] = expectedFundsDef(controller, fundId, accountId);
|
||||
}
|
||||
|
||||
// mirror for updated (last time the flow was updated for the account).
|
||||
ghost mapping(VaultBase.Controller => mapping(VaultBase.FundId => mapping(VaultBase.AccountId => uint40))) updatedMirror {
|
||||
init_state axiom
|
||||
forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
forall VaultBase.AccountId accountId.
|
||||
updatedMirror[controller][fundId][accountId] == 0;
|
||||
}
|
||||
|
||||
hook Sload VaultBase.Timestamp defaultValue
|
||||
_accounts[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId][KEY VaultBase.AccountId accountId].flow.updated
|
||||
{
|
||||
require(updatedMirror[controller][fundId][accountId] == unwrapTimestamp(defaultValue), "updated mirror");
|
||||
}
|
||||
|
||||
hook Sstore _accounts[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId][KEY VaultBase.AccountId accountId].flow.updated
|
||||
VaultBase.Timestamp defaultValue
|
||||
{
|
||||
updatedMirror[controller][fundId][accountId] = unwrapTimestamp(defaultValue);
|
||||
expectedFunds[controller][fundId][accountId] = expectedFundsDef(controller, fundId, accountId);
|
||||
}
|
||||
|
||||
|
||||
// mirror for available balance
|
||||
ghost mapping(VaultBase.Controller => mapping(VaultBase.FundId => mapping(VaultBase.AccountId => uint128))) availableBalanceMirror {
|
||||
init_state axiom
|
||||
forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
forall VaultBase.AccountId accountId.
|
||||
availableBalanceMirror[controller][fundId][accountId] == 0;
|
||||
}
|
||||
|
||||
hook Sload uint128 defaultValue
|
||||
_accounts[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId][KEY VaultBase.AccountId accountId].balance.available
|
||||
{
|
||||
require(availableBalanceMirror[controller][fundId][accountId] == defaultValue, "available balance mirror");
|
||||
requireInvariant expectedFundsMirror(controller, fundId, accountId);
|
||||
}
|
||||
|
||||
hook Sstore _accounts[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId][KEY VaultBase.AccountId accountId].balance.available
|
||||
uint128 defaultValue
|
||||
{
|
||||
availableBalanceMirror[controller][fundId][accountId] = defaultValue;
|
||||
expectedFunds[controller][fundId][accountId] = expectedFundsDef(controller, fundId, accountId);
|
||||
}
|
||||
|
||||
// mirror for incoming flow
|
||||
ghost mapping(VaultBase.Controller => mapping(VaultBase.FundId => mapping(VaultBase.AccountId => VaultBase.TokensPerSecond))) incomingMirror {
|
||||
init_state axiom
|
||||
(forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
forall VaultBase.AccountId accountId.
|
||||
incomingMirror[controller][fundId][accountId] == 0) &&
|
||||
(forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
(sum VaultBase.AccountId accountId. incomingMirror[controller][fundId][accountId]) == 0);
|
||||
}
|
||||
|
||||
hook Sload VaultBase.TokensPerSecond defaultValue
|
||||
_accounts[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId][KEY VaultBase.AccountId accountId].flow.incoming
|
||||
{
|
||||
require(incomingMirror[controller][fundId][accountId] == defaultValue, "incoming mirror");
|
||||
}
|
||||
|
||||
hook Sstore _accounts[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId][KEY VaultBase.AccountId accountId].flow.incoming
|
||||
VaultBase.TokensPerSecond defaultValue
|
||||
{
|
||||
incomingMirror[controller][fundId][accountId] = defaultValue;
|
||||
expectedFunds[controller][fundId][accountId] = expectedFundsDef(controller, fundId, accountId);
|
||||
}
|
||||
|
||||
// mirror for frozenAt
|
||||
|
||||
ghost mapping(VaultBase.Controller => mapping(VaultBase.FundId => uint40)) frozenAtMirror {
|
||||
init_state axiom
|
||||
forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
frozenAtMirror[controller][fundId] == 0;
|
||||
}
|
||||
|
||||
hook Sload VaultBase.Timestamp defaultValue
|
||||
_funds[KEY VaultBase.Controller Controller][KEY VaultBase.FundId FundId].frozenAt
|
||||
{
|
||||
require(frozenAtMirror[Controller][FundId] == unwrapTimestamp(defaultValue), "frozenAt mirror");
|
||||
}
|
||||
|
||||
hook Sstore _funds[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId].frozenAt
|
||||
VaultBase.Timestamp defaultValue
|
||||
{
|
||||
frozenAtMirror[controller][fundId] = unwrapTimestamp(defaultValue);
|
||||
|
||||
mathint oldSum = usum VaultBase.Controller c,
|
||||
VaultBase.FundId f,
|
||||
VaultBase.AccountId a.
|
||||
expectedFunds[c][f][a];
|
||||
|
||||
havoc expectedFunds assuming forall VaultBase.Controller c.
|
||||
forall VaultBase.FundId f.
|
||||
forall VaultBase.AccountId a.
|
||||
expectedFunds@new[c][f][a]
|
||||
== expectedFundsDef(c, f, a);
|
||||
|
||||
// See the comment in the store hook for lockExpiry for an explanation of why this is true.
|
||||
require((usum VaultBase.Controller c,
|
||||
VaultBase.FundId f,
|
||||
VaultBase.AccountId a.
|
||||
expectedFunds[c][f][a]) == oldSum,
|
||||
"sum of expected funds should not change as net flow between all funds is zero");
|
||||
}
|
||||
|
||||
// mirror for designated balance
|
||||
ghost mapping(VaultBase.Controller => mapping(VaultBase.FundId => mapping(VaultBase.AccountId => uint128))) designatedBalanceMirror {
|
||||
init_state axiom
|
||||
forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
forall VaultBase.AccountId accountId.
|
||||
designatedBalanceMirror[controller][fundId][accountId] == 0;
|
||||
}
|
||||
|
||||
hook Sload uint128 defaultValue
|
||||
_accounts[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId][KEY VaultBase.AccountId accountId].balance.designated
|
||||
{
|
||||
require(designatedBalanceMirror[controller][fundId][accountId] == defaultValue, "designated balance mirror");
|
||||
requireInvariant expectedFundsMirror(controller, fundId, accountId);
|
||||
}
|
||||
|
||||
hook Sstore _accounts[KEY VaultBase.Controller controller][KEY VaultBase.FundId fundId][KEY VaultBase.AccountId accountId].balance.designated
|
||||
uint128 defaultValue
|
||||
{
|
||||
designatedBalanceMirror[controller][fundId][accountId] = defaultValue;
|
||||
expectedFunds[controller][fundId][accountId] = expectedFundsDef(controller, fundId, accountId);
|
||||
}
|
||||
|
||||
// Auxiliary invariants
|
||||
|
||||
// Timestamp must always be positive (non-zero).
|
||||
invariant timestampPositive() lastTimestamp > 0;
|
||||
|
||||
|
||||
//------------------------------------------------------------//
|
||||
// Invariants of the Vault
|
||||
//------------------------------------------------------------//
|
||||
|
||||
// 1 - verified
|
||||
// lockExpiry is always less than or equal to lockMaximum.
|
||||
// (∀ controller ∈ Controller, fundId ∈ FundId:
|
||||
// fund.lockExpiry <= fund.lockMaximum
|
||||
// where fund = _funds[controller][fundId])
|
||||
invariant lockExpiryLELockMaximum()
|
||||
forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
lockExpiryMirror[controller][fundId] <= lockMaximumMirror[controller][fundId];
|
||||
|
||||
//------------------------------------------------------------//
|
||||
//------------------------------------------------------------//
|
||||
//------------------------------------------------------------//
|
||||
//------------------------------------------------------------//
|
||||
|
||||
// 2 - verified
|
||||
// the available balance of an account is large enough to cover the outgoing flow until the maximum lock time.
|
||||
// (∀ controller ∈ Controller, fundId ∈ FundId, accountId ∈ AccountId:
|
||||
// flow.outgoing * (fund.lockMaximum - flow.updated) <= balance.available
|
||||
// where fund = _funds[controller][fundId])
|
||||
// and flow = _accounts[controller][fundId][accountId].flow
|
||||
// and balance = _accounts[controller][fundId][accountId].balance
|
||||
invariant outgoingLEAvailable()
|
||||
forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
forall VaultBase.AccountId accountId.
|
||||
(outgoingMirror[controller][fundId][accountId]
|
||||
* (lockMaximumMirror[controller][fundId]
|
||||
- updatedMirror[controller][fundId][accountId]))
|
||||
<= availableBalanceMirror[controller][fundId][accountId]
|
||||
{
|
||||
preserved {
|
||||
requireInvariant noOutflowBeforeLocked();
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------//
|
||||
//------------------------------------------------------------//
|
||||
//------------------------------------------------------------//
|
||||
//------------------------------------------------------------//
|
||||
|
||||
definition statusCVL(VaultBase.Controller controller, VaultBase.FundId fundId) returns VaultBase.FundStatus =
|
||||
lastTimestamp < lockExpiryMirror[controller][fundId] ?
|
||||
(frozenAtMirror[controller][fundId] != 0 ? VaultBase.FundStatus.Frozen : VaultBase.FundStatus.Locked) :
|
||||
(lockMaximumMirror[controller][fundId] == 0 ? VaultBase.FundStatus.Inactive : VaultBase.FundStatus.Withdrawing);
|
||||
|
||||
// 3 - verified
|
||||
// the sum of incoming flows equals the sum of outgoing flows each controller and fundId.
|
||||
// (∀ controller ∈ Controller, fundId ∈ FundId:
|
||||
// (∑ accountId ∈ AccountId: accounts[accountId].flow.incoming) =
|
||||
// (∑ accountId ∈ AccountId: accounts[accountId].flow.outgoing)
|
||||
// where accounts = _accounts[controller][fundId])
|
||||
invariant incomingEqualsOutgoing()
|
||||
(forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
statusCVL(controller, fundId) != VaultBase.FundStatus.Withdrawing
|
||||
=> (sum
|
||||
VaultBase.AccountId accountId.
|
||||
outgoingMirror[controller][fundId][accountId])
|
||||
== (sum
|
||||
VaultBase.AccountId accountId.
|
||||
incomingMirror[controller][fundId][accountId]));
|
||||
|
||||
//------------------------------------------------------------//
|
||||
//------------------------------------------------------------//
|
||||
//------------------------------------------------------------//
|
||||
//------------------------------------------------------------//
|
||||
|
||||
// invariant 4 (Certora's solvency) - timeout in flow, all others varified
|
||||
// This is the solvency invariant:
|
||||
// sum of expected funds for all accounts in all funds must be less than or equal to the token balance of the contract.
|
||||
invariant solvency()
|
||||
sumOfExpectedFunds() <= to_mathint(tokenBalanceOfMirror[currentContract])
|
||||
{
|
||||
preserved {
|
||||
requireInvariant updatedLETimestampAndFlowEnd();
|
||||
requireInvariant lockExpiryLELockMaximum();
|
||||
requireInvariant outgoingLEAvailable();
|
||||
requireInvariant incomingEqualsOutgoing();
|
||||
requireInvariant noOutflowBeforeLocked();
|
||||
}
|
||||
|
||||
preserved deposit
|
||||
(VaultBase.FundId fundId,
|
||||
VaultBase.AccountId accountId, uint128 amount) with (env e) {
|
||||
requireInvariant totalSupplyIsSumOfBalances();
|
||||
requireInvariant updatedLETimestampAndFlowEnd();
|
||||
requireInvariant lockExpiryLELockMaximum();
|
||||
requireInvariant outgoingLEAvailable();
|
||||
requireInvariant incomingEqualsOutgoing();
|
||||
requireInvariant noOutflowBeforeLocked();
|
||||
require(e.msg.sender != currentContract, "deposit from vault not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------//
|
||||
//------------------------------------------------------------//
|
||||
//------------------------------------------------------------//
|
||||
//------------------------------------------------------------//
|
||||
|
||||
// 5 (needed to prove 2) - verified
|
||||
// as long as the funds are not yet locked, there must be not be any flows at all.
|
||||
// This is needed to ensure that setting the lock does not cause the flow invariant to break.
|
||||
invariant noOutflowBeforeLocked()
|
||||
forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
forall VaultBase.AccountId accountId.
|
||||
lockMaximumMirror[controller][fundId] == 0 => outgoingMirror[controller][fundId][accountId] == 0
|
||||
{
|
||||
preserved {
|
||||
requireInvariant timestampPositive();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 6 - verified
|
||||
// the last updated timestamp does never exceed the current timestamp or the end of the flow.
|
||||
// This is needed to ensure that there is no negative time for the flow calculations in expectedFundsDef.
|
||||
invariant updatedLETimestampAndFlowEnd()
|
||||
forall VaultBase.Controller controller.
|
||||
forall VaultBase.FundId fundId.
|
||||
forall VaultBase.AccountId accountId.
|
||||
updatedMirror[controller][fundId][accountId] <= flowEnd(controller, fundId)
|
||||
&& updatedMirror[controller][fundId][accountId] <= lastTimestamp
|
||||
{
|
||||
preserved {
|
||||
requireInvariant lockExpiryLELockMaximum();
|
||||
}
|
||||
}
|
||||
|
||||
// 7 - verified except for timeout in flow
|
||||
// The expectedFunds ghost variable is always equal to the expectedFundsHelper calculation.
|
||||
// This invariant is needed to prove solvency and included in the store hooks for available/designated balances.
|
||||
// The expectedFunds for a single account is calculated as:
|
||||
// availableBalance + designatedBalance + ((incoming - outgoing) * (flowEnd - updated))
|
||||
invariant expectedFundsMirror(VaultBase.Controller controller, VaultBase.FundId fundId, VaultBase.AccountId accountId)
|
||||
expectedFunds[controller][fundId][accountId] == expectedFundsHelper(controller, fundId, accountId)
|
||||
{
|
||||
preserved {
|
||||
requireInvariant lockExpiryLELockMaximum();
|
||||
requireInvariant outgoingLEAvailable();
|
||||
requireInvariant noOutflowBeforeLocked();
|
||||
requireInvariant updatedLETimestampAndFlowEnd();
|
||||
}
|
||||
}
|
||||
4
remappings.txt
Normal file
4
remappings.txt
Normal file
@ -0,0 +1,4 @@
|
||||
@ensdomains/=node_modules/@ensdomains/
|
||||
@openzeppelin/=node_modules/@openzeppelin/
|
||||
hardhat-deploy/=node_modules/hardhat-deploy/
|
||||
hardhat/=node_modules/hardhat/
|
||||
Loading…
x
Reference in New Issue
Block a user