Skip to content

Commit

Permalink
feat: Save last validator timestsamps (#166)
Browse files Browse the repository at this point in the history
  • Loading branch information
deluca-mike authored Apr 11, 2024
1 parent 387c1e3 commit c783720
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 85 deletions.
116 changes: 79 additions & 37 deletions src/MinterGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ contract MinterGateway is IMinterGateway, ContinuousIndexing, ERC712Extended {
/// @dev The pending collateral retrievals of minter (retrieval ID, amount).
mapping(address minter => mapping(uint48 retrievalId => uint240 amount)) internal _pendingCollateralRetrievals;

/// @dev The last update signature timestamp of each validator for each minter.
mapping(address minter => mapping(address validator => uint256 timestamp)) internal _lastSignatureTimestamp;

/* ============ Modifiers ============ */

/**
Expand Down Expand Up @@ -587,6 +590,11 @@ contract MinterGateway is IMinterGateway, ContinuousIndexing, ERC712Extended {
return _minterStates[minter_].latestProposedRetrievalTimestamp;
}

/// @inheritdoc IMinterGateway
function getLastSignatureTimestamp(address minter_, address validator_) external view returns (uint256) {
return _lastSignatureTimestamp[minter_][validator_];
}

/// @inheritdoc IMinterGateway
function getPenaltyForMissedCollateralUpdates(address minter_) external view returns (uint240) {
uint112 penaltyPrincipal_ = _getPenaltyPrincipalForMissedCollateralUpdates(minter_);
Expand Down Expand Up @@ -1021,7 +1029,7 @@ contract MinterGateway is IMinterGateway, ContinuousIndexing, ERC712Extended {
* @param validator_ The address of the validator
*/
function _revertIfNotApprovedValidator(address validator_) internal view {
if (!isValidatorApprovedByTTG(validator_)) revert NotApprovedValidator();
if (!isValidatorApprovedByTTG(validator_)) revert NotApprovedValidator(validator_);
}

/**
Expand Down Expand Up @@ -1064,58 +1072,92 @@ contract MinterGateway is IMinterGateway, ContinuousIndexing, ERC712Extended {
address[] calldata validators_,
uint256[] calldata timestamps_,
bytes[] calldata signatures_
) internal view returns (uint40 minTimestamp_) {
uint256 threshold_ = updateCollateralValidatorThreshold();

) internal returns (uint40 minTimestamp_) {
minTimestamp_ = uint40(block.timestamp);

// Stop processing if there are no more signatures or `threshold_` is reached.
for (uint256 index_; index_ < signatures_.length && threshold_ > 0; ++index_) {
uint256 validCount_;

for (uint256 index_; index_ < signatures_.length; ++index_) {
unchecked {
// Check that validator address is unique and not accounted for
// NOTE: We revert here because this failure is entirely within the minter's control.
if (index_ > 0 && validators_[index_] <= validators_[index_ - 1]) revert InvalidSignatureOrder();
}

// Check that validator is approved by TTG.
if (!isValidatorApprovedByTTG(validators_[index_])) continue;

// Check that the timestamp is not 0.
if (timestamps_[index_] == 0) revert ZeroTimestamp();

// Check that the timestamp is not in the future.
if (timestamps_[index_] > uint40(block.timestamp)) revert FutureTimestamp();

// NOTE: Need to store the variable here to avoid a stack too deep error.
bytes32 digest_ = _getUpdateCollateralDigest(
minter_,
collateral_,
retrievalIds_,
metadataHash_,
timestamps_[index_]
);

// Check that ECDSA or ERC1271 signatures for given digest are valid.
if (!SignatureChecker.isValidSignature(validators_[index_], digest_, signatures_[index_])) continue;
if (
!_verifyValidatorSignature(
minter_,
collateral_,
retrievalIds_,
metadataHash_,
validators_[index_],
timestamps_[index_],
signatures_[index_]
)
) continue;

// Find minimum between all valid timestamps for valid signatures.
minTimestamp_ = UIntMath.min40(minTimestamp_, uint40(timestamps_[index_]));

unchecked {
--threshold_;
++validCount_;
}
}

// 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.
if (threshold_ > 0) {
uint256 requiredThreshold_ = updateCollateralValidatorThreshold();
uint256 requiredThreshold_ = updateCollateralValidatorThreshold();

unchecked {
// NOTE: By this point, it is already established that `threshold_` is less than `requiredThreshold_`.
revert NotEnoughValidSignatures(requiredThreshold_ - threshold_, requiredThreshold_);
}
}
if (validCount_ < requiredThreshold_) revert NotEnoughValidSignatures(validCount_, requiredThreshold_);
}

/**
* @dev Checks that a signature is a valid validator signature.
* @param minter_ The address of the minter.
* @param collateral_ The amount of collateral.
* @param retrievalIds_ The list of outstanding collateral retrieval IDs to resolve.
* @param metadataHash_ The hash of metadata of the collateral update, reserved for future informational use.
* @param validator_ The address of a validator.
* @param timestamp_ The timestamp for the collateral update signature.
* @param signature_ The signature from the validator.
* @return Whether the signature is a valid validator signature or not.
*/
function _verifyValidatorSignature(
address minter_,
uint256 collateral_,
uint256[] calldata retrievalIds_,
bytes32 metadataHash_,
address validator_,
uint256 timestamp_,
bytes calldata signature_
) internal returns (bool) {
// Check that the timestamp is not 0.
// NOTE: Revert here because this failure is entirely within the minter's control.
if (timestamp_ == 0) revert ZeroTimestamp();

// Check that the timestamp is not in the future.
// NOTE: Revert here because this failure is entirely within the minter's control.
if (timestamp_ > uint40(block.timestamp)) revert FutureTimestamp();

uint256 lastTimestamp_ = _lastSignatureTimestamp[minter_][validator_];

// Check that the timestamp is not older than the last signature timestamp.
// NOTE: Revert here because this failure is entirely within the minter's control.
if (timestamp_ <= lastTimestamp_) revert OutdatedValidatorTimestamp(validator_, timestamp_, lastTimestamp_);

// Check that validator is approved by TTG.
if (!isValidatorApprovedByTTG(validator_)) return false;

// Check that ECDSA or ERC1271 signatures for given digest are valid.
if (
!SignatureChecker.isValidSignature(
validator_,
_getUpdateCollateralDigest(minter_, collateral_, retrievalIds_, metadataHash_, timestamp_),
signature_
)
) return false;

// Save the last signature timestamp for the minter and validator combination.
_lastSignatureTimestamp[minter_][validator_] = timestamp_;

return true;
}
}
20 changes: 16 additions & 4 deletions src/interfaces/IMinterGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ interface IMinterGateway is IContinuousIndexing, IERC712 {
/// @notice Emitted when calling `mintM` or `proposeMint` by a minter who was frozen by validator.
error FrozenMinter();

/// @notice Emitted when calling `updateCollateral` if validator timestamp is in the future.
/// @notice Emitted when calling `updateCollateral` with any validator timestamp in the future.
error FutureTimestamp();

/// @notice Emitted when calling a function only allowed for active minters.
Expand All @@ -145,8 +145,8 @@ interface IMinterGateway is IContinuousIndexing, IERC712 {
/// @notice Emitted when calling `activateMinter` if minter was not approved by TTG.
error NotApprovedMinter();

/// @notice Emitted when calling `cancelMint` or `freezeMinter` if validator was not approved by TTG.
error NotApprovedValidator();
/// @notice Emitted when calling `cancelMint` or `freezeMinter` if `validator` was not approved by TTG.
error NotApprovedValidator(address validator);

/// @notice Emitted when calling `updateCollateral` if `validatorThreshold` of signatures was not reached.
error NotEnoughValidSignatures(uint256 validSignatures, uint256 requiredThreshold);
Expand All @@ -168,6 +168,10 @@ interface IMinterGateway is IContinuousIndexing, IERC712 {
/// @notice Emitted when updating collateral with a timestamp earlier than allowed.
error StaleCollateralUpdate(uint40 newTimestamp, uint40 earliestAllowedTimestamp);

/// @notice Emitted when calling `updateCollateral` with any validator timestamp older than the last signature
/// timestamp for that minter and validator.
error OutdatedValidatorTimestamp(address validator, uint256 timestamp, uint256 lastSignatureTimestamp);

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

Expand Down Expand Up @@ -199,7 +203,7 @@ interface IMinterGateway is IContinuousIndexing, IERC712 {
/// @notice Emitted in constructor if TTG Distribution Vault is set to 0x0 in TTG Registrar.
error ZeroTTGVault();

/// @notice Emitted when calling `updateCollateral` if validator timestamp is 0.
/// @notice Emitted when calling `updateCollateral` with any validator timestamp of 0.
error ZeroTimestamp();

/* ============ Interactive Functions ============ */
Expand Down Expand Up @@ -365,6 +369,14 @@ interface IMinterGateway is IContinuousIndexing, IERC712 {
/// @notice The timestamp when `minter` created their latest retrieval proposal.
function latestProposedRetrievalTimestampOf(address minter) external view returns (uint40);

/**
* @notice Returns the last signature timestamp used by `validator` to update collateral for `minter`.
* @param minter The address of the minter.
* @param validator The address of the validator.
* @return The last signature timestamp used.
*/
function getLastSignatureTimestamp(address minter, address validator) external view returns (uint256);

/// @notice The penalty for missed collateral updates. Penalized once per missed interval.
function getPenaltyForMissedCollateralUpdates(address minter) external view returns (uint240);

Expand Down
Loading

0 comments on commit c783720

Please sign in to comment.