Skip to content

Commit

Permalink
feat: Improved Errors (#36)
Browse files Browse the repository at this point in the history
Co-authored-by: toninorair <[email protected]>
  • Loading branch information
deluca-mike and toninorair authored Dec 1, 2023
1 parent 870ef23 commit fd3fcbf
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 41 deletions.
47 changes: 28 additions & 19 deletions src/Protocol.sol
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,10 @@ contract Protocol is IProtocol, ContinuousIndexing, StatelessERC712 {

// Check that mint proposal is executable.
uint256 activeAt_ = createdAt_ + SPOGRegistrarReader.getMintDelay(spogRegistrar);
if (block.timestamp < activeAt_) revert PendingMintProposal();
if (block.timestamp < activeAt_) revert PendingMintProposal(activeAt_);

uint256 expiresAt_ = activeAt_ + SPOGRegistrarReader.getMintTTL(spogRegistrar);
if (block.timestamp > expiresAt_) revert ExpiredMintProposal();
if (block.timestamp > expiresAt_) revert ExpiredMintProposal(expiresAt_);

_revertIfUndercollateralized(msg.sender, amount_); // Check that minter will remain sufficiently collateralized.

Expand Down Expand Up @@ -291,7 +291,7 @@ contract Protocol is IProtocol, ContinuousIndexing, StatelessERC712 {
function updateIndex() public override(IContinuousIndexing, ContinuousIndexing) returns (uint256 index_) {
// NOTE: Since the currentIndex of the protocol and mToken are constant thought this context's execution (since
// the block.timestamp is not changing) we can compute excessOwedM without updating the mToken index.
uint256 excessOwedM_ = _getExcessActiveOwedM();
uint256 excessOwedM_ = excessActiveOwedM();

if (excessOwedM_ > 0) IMToken(mToken).mint(spogVault, excessOwedM_); // Mint M to SPOG Vault.

Expand Down Expand Up @@ -330,6 +330,17 @@ contract Protocol is IProtocol, ContinuousIndexing, StatelessERC712 {
return _lastCollateralUpdates[minter_] + _lastUpdateIntervals[minter_];
}

function excessActiveOwedM() public view returns (uint256 getExcessOwedM_) {
uint256 totalMSupply_ = IMToken(mToken).totalSupply();
uint256 totalActiveOwedM_ = _getPresentValue(_totalPrincipalOfActiveOwedM);

if (totalActiveOwedM_ > totalMSupply_) return totalActiveOwedM_ - totalMSupply_;
}

function getMaxOwedM(address minter_) public view returns (uint256 maxOwedM_) {
return (collateralOf(minter_) * mintRatio()) / ONE;
}

function getPenaltyForMissedCollateralUpdates(address minter_) public view returns (uint256 penalty_) {
(uint256 penaltyBase_, ) = _getPenaltyBaseAndTimeForMissedCollateralUpdates(minter_);

Expand Down Expand Up @@ -453,7 +464,7 @@ contract Protocol is IProtocol, ContinuousIndexing, StatelessERC712 {
}

function _imposePenaltyIfUndercollateralized(address minter_) internal {
uint256 maxOwedM_ = _getMaxOwedM(minter_);
uint256 maxOwedM_ = getMaxOwedM(minter_);
uint256 activeOwedM_ = activeOwedMOf(minter_);

if (maxOwedM_ >= activeOwedM_) return;
Expand Down Expand Up @@ -487,7 +498,9 @@ contract Protocol is IProtocol, ContinuousIndexing, StatelessERC712 {
}

function _updateCollateral(address minter_, uint256 amount_, uint256 newTimestamp_) internal {
if (newTimestamp_ < _lastCollateralUpdates[minter_]) revert StaleCollateralUpdate();
uint256 lastCollateralUpdate_ = _lastCollateralUpdates[minter_];

if (newTimestamp_ < lastCollateralUpdate_) revert StaleCollateralUpdate(newTimestamp_, lastCollateralUpdate_);

_collaterals[minter_] = amount_;
_lastCollateralUpdates[minter_] = newTimestamp_;
Expand All @@ -498,17 +511,6 @@ contract Protocol is IProtocol, ContinuousIndexing, StatelessERC712 {
| Internal View/Pure Functions |
\******************************************************************************************************************/

function _getExcessActiveOwedM() internal view returns (uint256 getExcessOwedM_) {
uint256 totalMSupply_ = IMToken(mToken).totalSupply();
uint256 totalActiveOwedM_ = _getPresentValue(_totalPrincipalOfActiveOwedM);

if (totalActiveOwedM_ > totalMSupply_) return totalActiveOwedM_ - totalMSupply_;
}

function _getMaxOwedM(address minter_) internal view returns (uint256 maxOwedM_) {
return (collateralOf(minter_) * mintRatio()) / ONE;
}

function _getPenaltyBaseAndTimeForMissedCollateralUpdates(
address minter_
) internal view returns (uint256 penaltyBase_, uint256 penalizedUntil_) {
Expand Down Expand Up @@ -606,10 +608,11 @@ contract Protocol is IProtocol, ContinuousIndexing, StatelessERC712 {
}

function _revertIfUndercollateralized(address minter_, uint256 additionalOwedM_) internal view {
uint256 maxOwedM_ = _getMaxOwedM(minter_);
uint256 maxOwedM_ = getMaxOwedM(minter_);
uint256 activeOwedM_ = activeOwedMOf(minter_);
uint256 finalActiveOwedM_ = activeOwedM_ + additionalOwedM_;

if (activeOwedM_ + additionalOwedM_ > maxOwedM_) revert Undercollateralized();
if (finalActiveOwedM_ > maxOwedM_) revert Undercollateralized(finalActiveOwedM_, maxOwedM_);
}

/**
Expand Down Expand Up @@ -665,6 +668,12 @@ contract Protocol is IProtocol, ContinuousIndexing, StatelessERC712 {
--threshold_;
}

if (threshold_ > 0) revert NotEnoughValidSignatures();
// NOTE: Due to STACK_TOO_DEEP issues, we need to refetch `requiredThreshold_` and compute the number of valid
// signatures here, in order to emit the correct error message. However, the code will only reach this
// point to inevitably revert, so the gas cost is not much of a concern.
uint256 requiredThreshold_ = SPOGRegistrarReader.getUpdateCollateralValidatorThreshold(spogRegistrar);
uint256 validSignatures_ = requiredThreshold_ - threshold_;

if (threshold_ > 0) revert NotEnoughValidSignatures(validSignatures_, requiredThreshold_);
}
}
26 changes: 20 additions & 6 deletions src/interfaces/IProtocol.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface IProtocol is IContinuousIndexing {
/// @notice Emitted when calling `activeMinter` with an already active minter.
error AlreadyActiveMinter();

error ExpiredMintProposal();
error ExpiredMintProposal(uint256 deadline);

error FrozenMinter();

Expand All @@ -29,18 +29,18 @@ interface IProtocol is IContinuousIndexing {

error NotApprovedValidator();

error NotEnoughValidSignatures();
error NotEnoughValidSignatures(uint256 validSignatures, uint256 requiredThreshold);

error PendingMintProposal();
error PendingMintProposal(uint256 activeTimestamp);

error SignatureArrayLengthsMismatch();

error StaleCollateralUpdate();
error StaleCollateralUpdate(uint256 newTimestamp, uint256 lastCollateralUpdate);

/// @notice Emitted when calling `deactivateMinter` with a minter still approved in SPOG Registrar.
error StillApprovedMinter();

error Undercollateralized();
error Undercollateralized(uint256 activeOwedM, uint256 maxOwedM);

error ZeroMToken();

Expand All @@ -57,7 +57,7 @@ interface IProtocol is IContinuousIndexing {
event CollateralUpdated(
address indexed minter,
uint256 collateral,
uint256[] indexed retrieveIds,
uint256[] indexed retrievalIds,
bytes32 indexed metadata,
uint256 timestamp
);
Expand All @@ -79,12 +79,22 @@ interface IProtocol is IContinuousIndexing {
*/
event MinterDeactivated(address indexed minter, uint256 owedM, address indexed caller);

event MinterFrozen(address indexed minter, uint256 frozenUntil, address indexed validator);

event MinterFrozen(address indexed minter, uint256 frozenUntil);

event MinterRemoved(address indexed minter, uint256 inactiveOwedM);

event MintExecuted(uint256 indexed mintId);

event MintProposed(uint256 indexed mintId, address indexed minter, uint256 amount, address indexed destination);

event MintRequestCanceled(uint256 indexed mintId, address indexed caller);

event MintRequestExecuted(uint256 indexed mintId);

event PenaltyAccrued(address indexed minter, uint256 amount);

event PenaltyImposed(address indexed minter, uint256 amount);

event RetrievalCreated(uint256 indexed retrievalId, address indexed minter, uint256 amount);
Expand Down Expand Up @@ -194,6 +204,10 @@ interface IProtocol is IContinuousIndexing {

function collateralUpdateDeadlineOf(address minter) external view returns (uint256 lastUpdate);

function excessActiveOwedM() external view returns (uint256 getExcessOwedM);

function getMaxOwedM(address minter_) external view returns (uint256 maxOwedM);

/**
* @notice Returns the penalty for expired collateral value.
* @dev Minter is penalized on current outstanding value per every missed interval.
Expand Down
2 changes: 0 additions & 2 deletions test/Integrations.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,6 @@ contract IntegrationTests is Test {
assertEq(_mToken.balanceOf(_alice), 551_170_800163); // ~500k with 10% APY compounded continuously.
assertEq(_mToken.balanceOf(_vault), 0); // Still 0 since no call to `_protocol.updateIndex()`.

vm.stopPrank();

uint256 transferAmount_ = _mToken.balanceOf(_alice);

vm.prank(_alice);
Expand Down
46 changes: 32 additions & 14 deletions test/Protocol.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -208,19 +208,22 @@ contract ProtocolTests is Test {
vm.prank(_minter1);
_protocol.updateCollateral(100, retrievalIds, bytes32(0), validators, timestamps, signatures);

uint256 timestamp = _protocol.lastUpdateIntervalOf(_minter1) - 1;
uint256 lastUpdateTimestamp = _protocol.lastUpdateOf(_minter1);
uint256 newTimestamp = lastUpdateTimestamp - 1;

timestamps[0] = timestamp;
timestamps[0] = newTimestamp;
signatures[0] = _getCollateralUpdateSignature(
_minter1,
100,
retrievalIds,
bytes32(0),
timestamp,
newTimestamp,
_validator1Pk
);

vm.expectRevert(IProtocol.StaleCollateralUpdate.selector);
vm.expectRevert(
abi.encodeWithSelector(IProtocol.StaleCollateralUpdate.selector, newTimestamp, lastUpdateTimestamp)
);

vm.prank(_minter1);
_protocol.updateCollateral(100, retrievalIds, bytes32(0), validators, timestamps, signatures);
Expand Down Expand Up @@ -320,10 +323,11 @@ contract ProtocolTests is Test {
function test_proposeMint_undercollateralizedMint() external {
_protocol.setCollateralOf(_minter1, 100e18);
_protocol.setLastCollateralUpdateOf(_minter1, block.timestamp);
_protocol.setLastUpdateIntervalOf(_minter1, _updateCollateralInterval);

vm.warp(block.timestamp + _mintDelay);

vm.expectRevert(IProtocol.Undercollateralized.selector);
vm.expectRevert(abi.encodeWithSelector(IProtocol.Undercollateralized.selector, 100e18, 90e18));

vm.prank(_minter1);
_protocol.proposeMint(100e18, _alice);
Expand Down Expand Up @@ -435,10 +439,11 @@ contract ProtocolTests is Test {
function test_mintM_pendingMintRequest() external {
uint256 timestamp = block.timestamp;
uint256 mintId = _protocol.setMintProposalOf(_minter1, 100, timestamp, _alice);
uint256 activeTimestamp_ = timestamp + _mintDelay;

vm.warp(timestamp + _mintDelay / 2);
vm.warp(activeTimestamp_ - 10);

vm.expectRevert(IProtocol.PendingMintProposal.selector);
vm.expectRevert(abi.encodeWithSelector(IProtocol.PendingMintProposal.selector, activeTimestamp_));

vm.prank(_minter1);
_protocol.mintM(mintId);
Expand All @@ -447,24 +452,26 @@ contract ProtocolTests is Test {
function test_mintM_expiredMintRequest() external {
uint256 timestamp = block.timestamp;
uint256 mintId = _protocol.setMintProposalOf(_minter1, 100, timestamp, _alice);
uint256 deadline_ = timestamp + _mintDelay + _mintTTL;

vm.warp(timestamp + _mintDelay + _mintTTL + 1);
vm.warp(deadline_ + 1);

vm.expectRevert(IProtocol.ExpiredMintProposal.selector);
vm.expectRevert(abi.encodeWithSelector(IProtocol.ExpiredMintProposal.selector, deadline_));

vm.prank(_minter1);
_protocol.mintM(mintId);
}

function test_mintM_undercollateralizedMint() external {
function test_mintM_undercollateralizedMint_xxx() external {
_protocol.setCollateralOf(_minter1, 100e18);
_protocol.setLastCollateralUpdateOf(_minter1, block.timestamp);
_protocol.setLastUpdateIntervalOf(_minter1, _updateCollateralInterval);

uint256 mintId = _protocol.setMintProposalOf(_minter1, 95e18, block.timestamp, _alice);

vm.warp(block.timestamp + _mintDelay + 1);

vm.expectRevert(IProtocol.Undercollateralized.selector);
vm.expectRevert(abi.encodeWithSelector(IProtocol.Undercollateralized.selector, 95e18, 90e18));

vm.prank(_minter1);
_protocol.mintM(mintId);
Expand All @@ -473,12 +480,13 @@ contract ProtocolTests is Test {
function test_mintM_undercollateralizedMint_outdatedCollateral() external {
_protocol.setCollateralOf(_minter1, 100e18);
_protocol.setLastCollateralUpdateOf(_minter1, block.timestamp - _updateCollateralInterval);
_protocol.setLastUpdateIntervalOf(_minter1, _updateCollateralInterval);

uint256 mintId = _protocol.setMintProposalOf(_minter1, 95e18, block.timestamp, _alice);

vm.warp(block.timestamp + _mintDelay + 1);

vm.expectRevert(IProtocol.Undercollateralized.selector);
vm.expectRevert(abi.encodeWithSelector(IProtocol.Undercollateralized.selector, 95e18, 0));

vm.prank(_minter1);
_protocol.mintM(mintId);
Expand Down Expand Up @@ -1283,12 +1291,22 @@ contract ProtocolTests is Test {

_protocol.setCollateralOf(_minter1, collateral);
_protocol.setLastCollateralUpdateOf(_minter1, block.timestamp);
_protocol.setLastUpdateIntervalOf(_minter1, _updateCollateralInterval);
_protocol.setPrincipalOfActiveOwedMOf(_minter1, (collateral * _mintRatio) / ONE);

vm.expectRevert(IProtocol.Undercollateralized.selector);
uint256 retrievalAmount = 10e18;
uint256 expectedMaxOwedM = ((collateral - retrievalAmount) * _mintRatio) / ONE;

vm.expectRevert(
abi.encodeWithSelector(
IProtocol.Undercollateralized.selector,
_protocol.activeOwedMOf(_minter1),
expectedMaxOwedM
)
);

vm.prank(_minter1);
_protocol.proposeRetrieval(10e18);
_protocol.proposeRetrieval(retrievalAmount);
}

function test_retrieve_multipleRequests() external {
Expand Down

0 comments on commit fd3fcbf

Please sign in to comment.