Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Morpho Midnight
# Midnight

Morpho Midnight is a fixed-rate lending protocol based on zero-coupon obligations.
Midnight is a fixed-rate lending protocol based on zero-coupon obligations.

## Whitepaper

Expand Down
2 changes: 1 addition & 1 deletion certora/specs/CollateralBitmap.spec
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ methods {
*/
function _.price() external => PER_CALLEE_CONSTANT;
function TickLib.tickToPrice(uint256 tick) internal returns (uint256) => NONDET;
function IdLib.toId(Midnight.Obligation memory obligation, uint256 chainId, address morpho) internal returns (bytes32) => NONDET;
function IdLib.toId(Midnight.Obligation memory obligation, uint256 chainId, address midnight) internal returns (bytes32) => NONDET;

/* Simplify mulDiv reasoning for the solver. We summarize these by ghost functions, i.e.,
* arbitrary deterministic functions and axiomatize the axioms we need.
Expand Down
6 changes: 3 additions & 3 deletions certora/specs/Healthiness.spec
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ methods {
*/
function _.price() external => summaryPrice(calledContract) expect(uint256);
function TickLib.tickToPrice(uint256 tick) internal returns (uint256) => NONDET;
function IdLib.toId(Midnight.Obligation memory obligation, uint256 chainId, address morpho) internal returns (bytes32) => summaryToId(obligation, chainId, morpho);
function IdLib.toId(Midnight.Obligation memory obligation, uint256 chainId, address midnight) internal returns (bytes32) => summaryToId(obligation, chainId, midnight);

/* Summarize mulDivDown and mulDivUp to simplify the verification task.
* Use a ghost function that ensures mulDivDown/Up behaves deterministically and
Expand Down Expand Up @@ -136,9 +136,9 @@ function getGlobalObligation() returns (Midnight.Obligation) {
return obligation;
}

function summaryToId(Midnight.Obligation obligation, uint256 chainId, address morpho) returns (bytes32) {
function summaryToId(Midnight.Obligation obligation, uint256 chainId, address midnight) returns (bytes32) {
bytes32 id;
if (equalsGlobalObligation(obligation) && morpho == currentContract) {
if (equalsGlobalObligation(obligation) && midnight == currentContract) {
Comment thread
MathisGD marked this conversation as resolved.
require id == globalId, "toId() is deterministic";
} else {
require id != globalId, "toId() is injective";
Expand Down
4 changes: 2 additions & 2 deletions certora/specs/NoDivisionByZero.spec
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ function equalsGlobalObligation(Midnight.Obligation obligation) returns (bool) {
return obligation.loanToken == globalObligationLoanToken && obligation.collateralParams.length == globalObligationCollateralLength && collateralMatches(obligation, 0) && collateralMatches(obligation, 1) && collateralMatches(obligation, 2) && obligation.maturity == globalObligationMaturity && obligation.rcfThreshold == globalObligationRcfThreshold && obligation.enterGate == globalObligationEnterGate && obligation.liquidatorGate == globalObligationLiquidatorGate;
}

function summaryToId(Midnight.Obligation obligation, uint256 chainId, address morpho) returns (bytes32) {
function summaryToId(Midnight.Obligation obligation, uint256 chainId, address midnight) returns (bytes32) {
bytes32 id;
if (equalsGlobalObligation(obligation) && morpho == currentContract) {
if (equalsGlobalObligation(obligation) && midnight == currentContract) {
Comment thread
MathisGD marked this conversation as resolved.
require id == globalId, "toId() is deterministic";
} else {
require id != globalId, "toId() is injective";
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/IRatifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ pragma solidity >=0.5.0;
import {Offer} from "./IMidnight.sol";

interface IRatifier {
function onRatify(Offer memory offer, bytes32 root, bytes memory data) external returns (bytes32);
function onRatify(Offer memory offer, bytes32 root, bytes memory ratifierData) external returns (bytes32);
}
6 changes: 3 additions & 3 deletions src/periphery/TakeBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ contract TakeBundler is ITakeBundler {
"",
receiverIfTakerIsSeller,
takes[i].offer,
takes[i].sig,
takes[i].ratifierData,
takes[i].root,
takes[i].proof
) returns (
Expand Down Expand Up @@ -93,7 +93,7 @@ contract TakeBundler is ITakeBundler {
"",
receiverIfTakerIsSeller,
takes[i].offer,
takes[i].sig,
takes[i].ratifierData,
takes[i].root,
takes[i].proof
) returns (
Expand Down Expand Up @@ -142,7 +142,7 @@ contract TakeBundler is ITakeBundler {
"",
receiverIfTakerIsSeller,
takes[i].offer,
takes[i].sig,
takes[i].ratifierData,
takes[i].root,
takes[i].proof
) returns (
Expand Down
2 changes: 1 addition & 1 deletion src/periphery/interfaces/ITakeBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {Offer} from "../../interfaces/IMidnight.sol";
struct Take {
uint256 units;
Offer offer;
bytes sig;
bytes ratifierData;
bytes32 root;
bytes32[] proof;
}
Expand Down
4 changes: 2 additions & 2 deletions src/ratifiers/EcrecoverRatifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ contract EcrecoverRatifier is IEcrecoverRatifier {
MIDNIGHT = _midnight;
}

function onRatify(Offer memory offer, bytes32 root, bytes memory data) external view returns (bytes32) {
Signature memory sig = abi.decode(data, (Signature));
function onRatify(Offer memory offer, bytes32 root, bytes memory ratifierData) external view returns (bytes32) {
Signature memory sig = abi.decode(ratifierData, (Signature));
bytes32 structHash = keccak256(abi.encode(ROOT_TYPEHASH, root));
bytes32 domainSeparator = keccak256(abi.encode(EIP712_DOMAIN_TYPEHASH, block.chainid, address(this)));
bytes32 digest = keccak256(bytes.concat("\x19\x01", domainSeparator, structHash));
Expand Down
8 changes: 6 additions & 2 deletions test/AuthorizationTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,9 @@ contract AuthorizationTest is BaseTest {
address attacker = makeAddr("attacker");
vm.prank(attacker);
vm.expectRevert(IMidnight.TakerUnauthorized.selector);
midnight.take(units, taker, address(0), hex"", address(0), offer, sig([offer]), root([offer]), proof([offer]));
midnight.take(
units, taker, address(0), hex"", address(0), offer, ratifierData([offer]), root([offer]), proof([offer])
);
}

function testTakeAuthorized() public {
Expand All @@ -240,7 +242,9 @@ contract AuthorizationTest is BaseTest {

// Operator can take on behalf of taker
vm.prank(operator);
midnight.take(units, taker, address(0), hex"", taker, offer, sig([offer]), root([offer]), proof([offer]));
midnight.take(
units, taker, address(0), hex"", taker, offer, ratifierData([offer]), root([offer]), proof([offer])
);

assertEq(midnight.debtOf(id, taker), units);
}
Expand Down
12 changes: 7 additions & 5 deletions test/BaseTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ abstract contract BaseTest is Test {
// receiverIfTakerIsSeller param is for taker (when offer.buy == true)
// offer.receiverIfMakerIsSeller is for maker (when offer.buy == false)
vm.prank(taker);
return midnight.take(units, taker, address(0), hex"", taker, offer, sig([offer]), root([offer]), proof([offer]));
return midnight.take(
units, taker, address(0), hex"", taker, offer, ratifierData([offer]), root([offer]), proof([offer])
);
}

function setupOtherUsers(Obligation memory obligation, uint256 units) internal {
Expand Down Expand Up @@ -217,7 +219,7 @@ abstract contract BaseTest is Test {
return IdLib.toId(obligation, block.chainid, address(midnight));
}

function sig(Offer[1] memory offers, address _signer) internal view returns (bytes memory) {
function ratifierData(Offer[1] memory offers, address _signer) internal view returns (bytes memory) {
return abi.encode(signature(root(offers), privateKey[_signer], offers[0].ratifier));
}

Expand Down Expand Up @@ -260,12 +262,12 @@ abstract contract BaseTest is Test {
return _signature;
}

function sig(Offer[1] memory offers) internal view returns (bytes memory) {
function ratifierData(Offer[1] memory offers) internal view returns (bytes memory) {
bytes32 _root = root(offers);
return abi.encode(signature(_root, privateKey[offers[0].maker], offers[0].ratifier));
}

function sig(Offer[2] memory offers) internal view returns (bytes memory) {
function ratifierData(Offer[2] memory offers) internal view returns (bytes memory) {
bytes32 _root = root(offers);
return abi.encode(signature(_root, privateKey[offers[0].maker], offers[0].ratifier));
}
Expand Down Expand Up @@ -328,7 +330,7 @@ abstract contract BaseTest is Test {
hex"",
borrower,
borrowerOffer,
sig([borrowerOffer]),
ratifierData([borrowerOffer]),
root([borrowerOffer]),
proof([borrowerOffer])
);
Expand Down
26 changes: 15 additions & 11 deletions test/BundlerTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ contract BundlerTest is BaseTest {
function testUnauthorized() public {
Take[] memory takes = new Take[](1);
takes[0] = Take({
offer: offers[0], units: 100, sig: sig([offers[0]]), root: root([offers[0]]), proof: proof([offers[0]])
offer: offers[0],
units: 100,
ratifierData: ratifierData([offers[0]]),
root: root([offers[0]]),
proof: proof([offers[0]])
});

vm.prank(address(0xdead));
Expand All @@ -107,14 +111,14 @@ contract BundlerTest is BaseTest {
takes[0] = Take({
offer: offers[0],
units: offerUnits0,
sig: sig([offers[0]]),
ratifierData: ratifierData([offers[0]]),
root: root([offers[0]]),
proof: proof([offers[0]])
});
takes[1] = Take({
offer: offers[1],
units: offerUnits1,
sig: sig([offers[1]]),
ratifierData: ratifierData([offers[1]]),
root: root([offers[1]]),
proof: proof([offers[1]])
});
Expand Down Expand Up @@ -157,14 +161,14 @@ contract BundlerTest is BaseTest {
takes[0] = Take({
offer: offers[0],
units: offerUnits0,
sig: sig([offers[0]]),
ratifierData: ratifierData([offers[0]]),
root: root([offers[0]]),
proof: proof([offers[0]])
});
takes[1] = Take({
offer: offers[1],
units: offerUnits1,
sig: sig([offers[1]]),
ratifierData: ratifierData([offers[1]]),
root: root([offers[1]]),
proof: proof([offers[1]])
});
Expand Down Expand Up @@ -209,14 +213,14 @@ contract BundlerTest is BaseTest {
takes[0] = Take({
offer: offers[0],
units: offerUnits0,
sig: sig([offers[0]]),
ratifierData: ratifierData([offers[0]]),
root: root([offers[0]]),
proof: proof([offers[0]])
});
takes[1] = Take({
offer: offers[1],
units: offerUnits1,
sig: sig([offers[1]]),
ratifierData: ratifierData([offers[1]]),
root: root([offers[1]]),
proof: proof([offers[1]])
});
Expand Down Expand Up @@ -302,14 +306,14 @@ contract BundlerTest is BaseTest {
takes[0] = Take({
offer: offers[0],
units: offerUnits0,
sig: sig([offers[0]]),
ratifierData: ratifierData([offers[0]]),
root: root([offers[0]]),
proof: proof([offers[0]])
});
takes[1] = Take({
offer: offers[1],
units: offerUnits1,
sig: sig([offers[1]]),
ratifierData: ratifierData([offers[1]]),
root: root([offers[1]]),
proof: proof([offers[1]])
});
Expand Down Expand Up @@ -352,14 +356,14 @@ contract BundlerTest is BaseTest {
takes[0] = Take({
offer: offers[0],
units: offerUnits0,
sig: sig([offers[0]]),
ratifierData: ratifierData([offers[0]]),
root: root([offers[0]]),
proof: proof([offers[0]])
});
takes[1] = Take({
offer: offers[1],
units: offerUnits1,
sig: sig([offers[1]]),
ratifierData: ratifierData([offers[1]]),
root: root([offers[1]]),
proof: proof([offers[1]])
});
Expand Down
26 changes: 13 additions & 13 deletions test/EcrecoverRatifierTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ contract EcrecoverRatifierTest is BaseTest {
function testOnRatifyMakerSigns() public view {
Offer memory offer = makeOffer(lender);
bytes32 _root = keccak256(abi.encode(offer));
bytes memory data = signRoot(_root, lender);
bytes memory ratifierData = signRoot(_root, lender);

bytes32 result = ecrecoverRatifier.onRatify(offer, _root, data);
bytes32 result = ecrecoverRatifier.onRatify(offer, _root, ratifierData);
assertEq(result, CALLBACK_SUCCESS);
}

Expand All @@ -40,38 +40,38 @@ contract EcrecoverRatifierTest is BaseTest {
vm.prank(lender);

midnight.setIsAuthorized(lender, borrower, true);
bytes memory data = signRoot(_root, borrower);
bytes memory ratifierData = signRoot(_root, borrower);

bytes32 result = ecrecoverRatifier.onRatify(offer, _root, data);
bytes32 result = ecrecoverRatifier.onRatify(offer, _root, ratifierData);
assertEq(result, CALLBACK_SUCCESS);
}

function testOnRatifyUnauthorizedSigner() public {
Offer memory offer = makeOffer(lender);
bytes32 _root = keccak256(abi.encode(offer));
bytes memory data = signRoot(_root, borrower);
bytes memory ratifierData = signRoot(_root, borrower);

vm.expectRevert(IEcrecoverRatifier.Unauthorized.selector);
ecrecoverRatifier.onRatify(offer, _root, data);
ecrecoverRatifier.onRatify(offer, _root, ratifierData);
}

function testOnRatifyInvalidSignature() public {
Offer memory offer = makeOffer(lender);
bytes32 _root = keccak256(abi.encode(offer));
bytes memory data = abi.encode(Signature({v: 27, r: bytes32(uint256(1)), s: bytes32(uint256(2))}));
bytes memory ratifierData = abi.encode(Signature({v: 27, r: bytes32(uint256(1)), s: bytes32(uint256(2))}));

vm.expectRevert(IEcrecoverRatifier.Unauthorized.selector);
ecrecoverRatifier.onRatify(offer, _root, data);
ecrecoverRatifier.onRatify(offer, _root, ratifierData);
}

function testOnRatifyWrongRoot() public {
Offer memory offer = makeOffer(lender);
bytes32 _root = keccak256(abi.encode(offer));
bytes memory data = signRoot(_root, lender);
bytes memory ratifierData = signRoot(_root, lender);

bytes32 wrongRoot = keccak256("wrong");
vm.expectRevert(IEcrecoverRatifier.Unauthorized.selector);
ecrecoverRatifier.onRatify(offer, wrongRoot, data);
ecrecoverRatifier.onRatify(offer, wrongRoot, ratifierData);
}

function testOnRatifyRevokeAuthorizationInvalidates() public {
Expand All @@ -81,16 +81,16 @@ contract EcrecoverRatifierTest is BaseTest {
vm.prank(lender);

midnight.setIsAuthorized(lender, borrower, true);
bytes memory data = signRoot(_root, borrower);
bytes memory ratifierData = signRoot(_root, borrower);

// Works while authorized.
ecrecoverRatifier.onRatify(offer, _root, data);
ecrecoverRatifier.onRatify(offer, _root, ratifierData);

// Revoke.
vm.prank(lender);
midnight.setIsAuthorized(lender, borrower, false);

vm.expectRevert(IEcrecoverRatifier.Unauthorized.selector);
ecrecoverRatifier.onRatify(offer, _root, data);
ecrecoverRatifier.onRatify(offer, _root, ratifierData);
}
}
Loading
Loading