Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No min timelock at initialisation #7

Merged
merged 25 commits into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6d4767f
feat: min timelock zero
MathisGD Aug 9, 2024
17d610b
fix: remove useless constant
MathisGD Aug 9, 2024
cdc85d0
docs: adapt docs
MathisGD Aug 9, 2024
0d8cdce
test: adapt verif
MathisGD Aug 9, 2024
ab36b5d
test: adapt tests
MathisGD Aug 9, 2024
b387028
refactor: minor refactor
MathisGD Aug 11, 2024
4f903ae
docs: minor change
MathisGD Aug 11, 2024
b06cbdc
test: fix noBadPendingCap verif
MathisGD Aug 12, 2024
5e71fa8
test: fix canForceRemoveMarket verif
MathisGD Aug 12, 2024
a740591
test: fix PendingValues verif
MathisGD Aug 12, 2024
54bac05
test: remove now useless assumptions
MathisGD Aug 12, 2024
ca1d81f
test: readd useful assumption in Liveness
MathisGD Aug 12, 2024
7632c38
test: readd useful assumptions
MathisGD Aug 12, 2024
aa338bb
Merge branch 'feat/no-share-price-decrease-2' into feat/other-features
MathisGD Aug 12, 2024
508ca77
test: readd assumption in smallerPendingTimelock
MathisGD Aug 12, 2024
bf250ca
test: last fixes PendingValues
MathisGD Aug 12, 2024
2b20e82
test: fix noBadPendingTimelock
MathisGD Aug 12, 2024
253a77f
test: minor improvement
MathisGD Aug 13, 2024
6e695b0
Merge remote-tracking branch 'private/feat/no-share-price-decrease-2'…
MathisGD Aug 13, 2024
e18ebc3
Merge branch 'feat/no-share-price-decrease-2' into feat/other-features
MathisGD Aug 15, 2024
697c5aa
feat: initial timelock can be 0
adhusson Aug 16, 2024
ca9e6d5
test: adapt verif to new timelock zero spec
MathisGD Aug 17, 2024
1be9c85
Merge pull request #24 from morpho-org/test/adapt-verif
MathisGD Aug 17, 2024
f07d3a4
Merge pull request #19 from morpho-org/feat/initial-timelock-can-be-zero
MathisGD Aug 17, 2024
d42180e
test: minor changes verif
MathisGD Aug 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Those rewards can be transferred to the `skimRecipient`.
The vault's owner has the choice to distribute back these rewards to vault depositors however they want.
For more information about this use case, see the [Rewards](#rewards) section.

All actions that may be against users' interests (e.g. enabling a market with a high exposure) are subject to a timelock of minimum 24 hours.
All actions that may be against users' interests (e.g. enabling a market with a high exposure) can be subject to a timelock of up to two weeks.
The `owner`, or the `guardian` if set, can revoke the action during the timelock.
After the timelock, the action can be executed by anyone.

Expand Down
4 changes: 0 additions & 4 deletions certora/helpers/MetaMorphoHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ contract MetaMorphoHarness is MetaMorpho {
return pendingCap[id];
}

function minTimelock() external pure returns (uint256) {
return ConstantsLib.MIN_TIMELOCK;
}

function maxTimelock() external pure returns (uint256) {
return ConstantsLib.MAX_TIMELOCK;
}
Expand Down
2 changes: 2 additions & 0 deletions certora/specs/Liveness.spec
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ rule canForceRemoveMarket(MetaMorphoHarness.MarketParams marketParams) {
requireInvariant timelockInRange();
// Safe require as it corresponds to some time very far into the future.
require e3.block.timestamp < 2^63;
// Safe require as it corresponds to some time very far into the past.
require e3.block.timestamp > 0;
submitMarketRemoval@withrevert(e3, marketParams);
assert !lastReverted;

Expand Down
16 changes: 12 additions & 4 deletions certora/specs/PendingValues.spec
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "Range.spec";
function hasNoBadPendingTimelock() returns bool {
MetaMorphoHarness.PendingUint192 pendingTimelock = pendingTimelock_();

return pendingTimelock.validAt == 0 <=> pendingTimelock.value == 0;
return pendingTimelock.validAt == 0 => pendingTimelock.value == 0;
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
}

// Check that having no pending timelock value is equivalent to having its valid timestamp at 0.
Expand All @@ -15,23 +15,27 @@ invariant noBadPendingTimelock()
requireInvariant timelockInRange();
// Safe require as it corresponds to some time very far into the future.
require e.block.timestamp < 2^63;
// Safe require as it corresponds to some time very far into the past.
require e.block.timestamp > 0;
}
}

// Check that the pending timelock value is always strictly smaller than the current timelock value.
invariant smallerPendingTimelock()
assert_uint256(pendingTimelock_().value) < timelock()
assert_uint256(pendingTimelock_().value) < timelock() || timelock() == 0
{
preserved {
preserved with (env e) {
requireInvariant pendingTimelockInRange();
requireInvariant timelockInRange();
// Safe require as it corresponds to some time very far into the past.
require e.block.timestamp > 0;
}
}

function hasNoBadPendingCap(MetaMorphoHarness.Id id) returns bool {
MetaMorphoHarness.PendingUint192 pendingCap = pendingCap_(id);

return pendingCap.validAt == 0 <=> pendingCap.value == 0;
return pendingCap.validAt == 0 => pendingCap.value == 0;
}

// Check that having no pending cap value is equivalent to having its valid timestamp at 0.
Expand All @@ -42,6 +46,8 @@ invariant noBadPendingCap(MetaMorphoHarness.Id id)
requireInvariant timelockInRange();
// Safe require as it corresponds to some time very far into the future.
require e.block.timestamp < 2^63;
// Safe require as it corresponds to some time very far into the past.
require e.block.timestamp > 0;
}
}

Expand Down Expand Up @@ -71,6 +77,8 @@ invariant noBadPendingGuardian()
requireInvariant timelockInRange();
// Safe require as it corresponds to some time very far into the future.
require e.block.timestamp < 2^63;
// Safe require as it corresponds to some time very far into the past.
require e.block.timestamp > 0;
}
}

Expand Down
10 changes: 4 additions & 6 deletions certora/specs/Range.spec
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ methods {
function isAllocator(address) external returns(bool) envfree;
function skimRecipient() external returns(address) envfree;

function minTimelock() external returns(uint256) envfree;
function maxTimelock() external returns(uint256) envfree;
function maxQueueLength() external returns(uint256) envfree;
function maxFee() external returns(uint256) envfree;
Expand All @@ -33,17 +32,16 @@ function isPendingTimelockInRange() returns bool {
MetaMorphoHarness.PendingUint192 pendingTimelock = pendingTimelock_();

return pendingTimelock.validAt != 0 =>
assert_uint256(pendingTimelock.value) <= maxTimelock() &&
assert_uint256(pendingTimelock.value) >= minTimelock();
assert_uint256(pendingTimelock.value) <= maxTimelock();
}

// Check that the pending timelock is bounded by the min timelock and the max timelock.
// Check that the pending timelock is bounded by the max timelock.
invariant pendingTimelockInRange()
isPendingTimelockInRange();

// Check that the timelock is bounded by the min timelock and the max timelock.
// Check that the timelock is bounded by the max timelock.
invariant timelockInRange()
timelock() <= maxTimelock() && timelock() >= minTimelock()
timelock() <= maxTimelock()
{
preserved {
requireInvariant pendingTimelockInRange();
Expand Down
10 changes: 2 additions & 8 deletions src/MetaMorpho.sol
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,11 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
string memory _symbol
) ERC4626(IERC20(_asset)) ERC20Permit(_name) ERC20(_name, _symbol) Ownable(owner) {
if (morpho == address(0)) revert ErrorsLib.ZeroAddress();
if (initialTimelock > ConstantsLib.MAX_TIMELOCK) revert ErrorsLib.AboveMaxTimelock();

MORPHO = IMorpho(morpho);
DECIMALS_OFFSET = uint8(uint256(18).zeroFloorSub(IERC20Metadata(_asset).decimals()));

_checkTimelockBounds(initialTimelock);
_setTimelock(initialTimelock);

IERC20(_asset).forceApprove(morpho, type(uint256).max);
Expand Down Expand Up @@ -218,7 +218,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
function submitTimelock(uint256 newTimelock) external onlyOwner {
if (newTimelock == timelock) revert ErrorsLib.AlreadySet();
if (pendingTimelock.validAt != 0) revert ErrorsLib.AlreadyPending();
_checkTimelockBounds(newTimelock);
if (newTimelock > ConstantsLib.MAX_TIMELOCK) revert ErrorsLib.AboveMaxTimelock();

if (newTimelock > timelock) {
_setTimelock(newTimelock);
Expand Down Expand Up @@ -704,12 +704,6 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
assets = shares.toAssetsDown(market.totalSupplyAssets, market.totalSupplyShares);
}

/// @dev Reverts if `newTimelock` is not within the bounds.
function _checkTimelockBounds(uint256 newTimelock) internal pure {
if (newTimelock > ConstantsLib.MAX_TIMELOCK) revert ErrorsLib.AboveMaxTimelock();
if (newTimelock < ConstantsLib.MIN_TIMELOCK) revert ErrorsLib.BelowMinTimelock();
}

/// @dev Sets `timelock` to `newTimelock`.
function _setTimelock(uint256 newTimelock) internal {
timelock = newTimelock;
Expand Down
3 changes: 0 additions & 3 deletions src/libraries/ConstantsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ library ConstantsLib {
/// @dev The maximum delay of a timelock.
uint256 internal constant MAX_TIMELOCK = 2 weeks;
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

/// @dev The minimum delay of a timelock.
uint256 internal constant MIN_TIMELOCK = 1 days;

/// @dev The maximum number of markets in the supply/withdraw queue.
uint256 internal constant MAX_QUEUE_LENGTH = 30;

Expand Down
3 changes: 0 additions & 3 deletions src/libraries/ErrorsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,6 @@ library ErrorsLib {
/// @notice Thrown when the submitted timelock is above the max timelock.
error AboveMaxTimelock();

/// @notice Thrown when the submitted timelock is below the min timelock.
error BelowMinTimelock();

/// @notice Thrown when the timelock is not elapsed.
error TimelockNotElapsed();

Expand Down
6 changes: 3 additions & 3 deletions test/forge/DeploymentTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "./helpers/IntegrationTest.sol";
contract DeploymentTest is IntegrationTest {
function testDeployMetaMorphoAddresssZero() public {
vm.expectRevert(ErrorsLib.ZeroAddress.selector);
createMetaMorpho(OWNER, address(0), ConstantsLib.MIN_TIMELOCK, address(loanToken), "MetaMorpho Vault", "MMV");
createMetaMorpho(OWNER, address(0), 1 days, address(loanToken), "MetaMorpho Vault", "MMV");
}

function testDeployMetaMorphoNotToken(address notToken) public {
Expand All @@ -15,7 +15,7 @@ contract DeploymentTest is IntegrationTest {
vm.assume(address(notToken) != address(vault));

vm.expectRevert();
createMetaMorpho(OWNER, address(morpho), ConstantsLib.MIN_TIMELOCK, notToken, "MetaMorpho Vault", "MMV");
createMetaMorpho(OWNER, address(morpho), 1 days, notToken, "MetaMorpho Vault", "MMV");
}

function testDeployMetaMorpho(
Expand All @@ -27,7 +27,7 @@ contract DeploymentTest is IntegrationTest {
) public {
assumeNotZeroAddress(owner);
assumeNotZeroAddress(morpho);
initialTimelock = bound(initialTimelock, ConstantsLib.MIN_TIMELOCK, ConstantsLib.MAX_TIMELOCK);
initialTimelock = bound(initialTimelock, 0 days, ConstantsLib.MAX_TIMELOCK);

IMetaMorpho newVault = createMetaMorpho(owner, morpho, initialTimelock, address(loanToken), name, symbol);

Expand Down
4 changes: 2 additions & 2 deletions test/forge/GuardianTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ contract GuardianTest is IntegrationTest {
}

function testGuardianRevokePendingTimelockDecreased(uint256 timelock, uint256 elapsed) public {
timelock = bound(timelock, ConstantsLib.MIN_TIMELOCK, TIMELOCK - 1);
timelock = bound(timelock, 0, TIMELOCK - 1);
elapsed = bound(elapsed, 0, TIMELOCK - 1);

vm.prank(OWNER);
Expand All @@ -48,7 +48,7 @@ contract GuardianTest is IntegrationTest {
}

function testOwnerRevokePendingTimelockDecreased(uint256 timelock, uint256 elapsed) public {
timelock = bound(timelock, ConstantsLib.MIN_TIMELOCK, TIMELOCK - 1);
timelock = bound(timelock, 0, TIMELOCK - 1);
elapsed = bound(elapsed, 0, TIMELOCK - 1);

vm.prank(OWNER);
Expand Down
2 changes: 1 addition & 1 deletion test/forge/MetaMorphoFactoryTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ contract MetaMorphoFactoryTest is IntegrationTest {
bytes32 salt
) public {
vm.assume(address(initialOwner) != address(0));
initialTimelock = bound(initialTimelock, ConstantsLib.MIN_TIMELOCK, ConstantsLib.MAX_TIMELOCK);
initialTimelock = bound(initialTimelock, 0, ConstantsLib.MAX_TIMELOCK);

bytes32 initCodeHash = hashInitCode(
type(MetaMorpho).creationCode,
Expand Down
2 changes: 1 addition & 1 deletion test/forge/RevokeTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ contract RevokeTest is IntegrationTest {
}

function testOwnerRevokeTimelockDecreased(uint256 timelock, uint256 elapsed) public {
timelock = bound(timelock, ConstantsLib.MIN_TIMELOCK, TIMELOCK - 1);
timelock = bound(timelock, 0, TIMELOCK - 1);
elapsed = bound(elapsed, 0, TIMELOCK - 1);

vm.prank(OWNER);
Expand Down
27 changes: 6 additions & 21 deletions test/forge/TimelockTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ contract TimelockTest is IntegrationTest {
}

function testSubmitTimelockDecreased(uint256 timelock) public {
timelock = bound(timelock, ConstantsLib.MIN_TIMELOCK, TIMELOCK - 1);
timelock = bound(timelock, 0, TIMELOCK - 1);

vm.expectEmit();
emit EventsLib.SubmitTimelock(timelock);
Expand All @@ -50,7 +50,7 @@ contract TimelockTest is IntegrationTest {
}

function testSubmitTimelockAlreadyPending(uint256 timelock) public {
timelock = bound(timelock, ConstantsLib.MIN_TIMELOCK, TIMELOCK - 1);
timelock = bound(timelock, 0, TIMELOCK - 1);

vm.prank(OWNER);
vault.submitTimelock(timelock);
Expand All @@ -72,13 +72,6 @@ contract TimelockTest is IntegrationTest {
createMetaMorpho(OWNER, address(morpho), timelock, address(loanToken), "MetaMorpho Vault", "MMV");
}

function testDeployMetaMorphoBelowMinTimelock(uint256 timelock) public {
timelock = bound(timelock, 0, ConstantsLib.MIN_TIMELOCK - 1);

vm.expectRevert(ErrorsLib.BelowMinTimelock.selector);
createMetaMorpho(OWNER, address(morpho), timelock, address(loanToken), "MetaMorpho Vault", "MMV");
}

function testSubmitTimelockAboveMaxTimelock(uint256 timelock) public {
timelock = bound(timelock, ConstantsLib.MAX_TIMELOCK + 1, type(uint256).max);

Expand All @@ -87,14 +80,6 @@ contract TimelockTest is IntegrationTest {
vault.submitTimelock(timelock);
}

function testSubmitTimelockBelowMinTimelock(uint256 timelock) public {
timelock = bound(timelock, 0, ConstantsLib.MIN_TIMELOCK - 1);

vm.prank(OWNER);
vm.expectRevert(ErrorsLib.BelowMinTimelock.selector);
vault.submitTimelock(timelock);
}

function testSubmitTimelockAlreadySet() public {
uint256 timelock = vault.timelock();

Expand All @@ -104,7 +89,7 @@ contract TimelockTest is IntegrationTest {
}

function testAcceptTimelock(uint256 timelock) public {
timelock = bound(timelock, ConstantsLib.MIN_TIMELOCK, TIMELOCK - 1);
timelock = bound(timelock, 0, TIMELOCK - 1);

vm.prank(OWNER);
vault.submitTimelock(timelock);
Expand All @@ -129,7 +114,7 @@ contract TimelockTest is IntegrationTest {
}

function testAcceptTimelockTimelockNotElapsed(uint256 timelock, uint256 elapsed) public {
timelock = bound(timelock, ConstantsLib.MIN_TIMELOCK, TIMELOCK - 1);
timelock = bound(timelock, 0, TIMELOCK - 1);
elapsed = bound(elapsed, 1, TIMELOCK - 1);

vm.prank(OWNER);
Expand Down Expand Up @@ -242,7 +227,7 @@ contract TimelockTest is IntegrationTest {
}

function testAcceptGuardianTimelockDecreased(uint256 timelock, uint256 elapsed) public {
timelock = bound(timelock, ConstantsLib.MIN_TIMELOCK, TIMELOCK - 1);
timelock = bound(timelock, 0, TIMELOCK - 1);
elapsed = bound(elapsed, 1, TIMELOCK - 1);

vm.prank(OWNER);
Expand Down Expand Up @@ -399,7 +384,7 @@ contract TimelockTest is IntegrationTest {

function testAcceptCapIncreasedTimelockDecreased(uint256 cap, uint256 timelock, uint256 elapsed) public {
cap = bound(cap, CAP + 1, type(uint184).max);
timelock = bound(timelock, ConstantsLib.MIN_TIMELOCK, TIMELOCK - 1);
timelock = bound(timelock, 0, TIMELOCK - 1);
elapsed = bound(elapsed, 1, TIMELOCK - 1);

vm.prank(OWNER);
Expand Down
4 changes: 1 addition & 3 deletions test/forge/helpers/InternalTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import "./BaseTest.sol";
import {MetaMorpho} from "../../../src/MetaMorpho.sol";

contract InternalTest is BaseTest, MetaMorpho {
constructor()
MetaMorpho(OWNER, address(morpho), ConstantsLib.MIN_TIMELOCK, address(loanToken), "MetaMorpho Vault", "MM")
{}
constructor() MetaMorpho(OWNER, address(morpho), 1 days, address(loanToken), "MetaMorpho Vault", "MM") {}

function setUp() public virtual override {
super.setUp();
Expand Down
Loading