Skip to content

Commit

Permalink
Merge pull request #132 from lidofinance/feature/types-tests
Browse files Browse the repository at this point in the history
Custom types code standardization & tests
  • Loading branch information
bulbozaur authored Oct 2, 2024
2 parents 1ffa251 + c4e127a commit a0768dd
Show file tree
Hide file tree
Showing 23 changed files with 1,753 additions and 199 deletions.
6 changes: 4 additions & 2 deletions contracts/libraries/AssetsAccounting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,10 @@ library AssetsAccounting {
_checkNonZeroShares(stETHSharesToWithdraw);

assets.stETHLockedShares = SharesValues.ZERO;
ethWithdrawn =
SharesValues.calcETHValue(self.stETHTotals.claimedETH, stETHSharesToWithdraw, self.stETHTotals.lockedShares);
ethWithdrawn = ETHValues.from(
self.stETHTotals.claimedETH.toUint256() * stETHSharesToWithdraw.toUint256()
/ self.stETHTotals.lockedShares.toUint256()
);

emit ETHWithdrawn(holder, stETHSharesToWithdraw, ethWithdrawn);
}
Expand Down
4 changes: 2 additions & 2 deletions contracts/libraries/DualGovernanceConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ library DualGovernanceConfig {

return vetoSignallingMinDuration
+ Durations.from(
PercentD16.unwrap(rageQuitSupport - firstSealRageQuitSupport)
(rageQuitSupport - firstSealRageQuitSupport).toUint256()
* (vetoSignallingMaxDuration - vetoSignallingMinDuration).toSeconds()
/ PercentD16.unwrap(secondSealRageQuitSupport - firstSealRageQuitSupport)
/ (secondSealRageQuitSupport - firstSealRageQuitSupport).toUint256()
);
}

Expand Down
132 changes: 87 additions & 45 deletions contracts/types/Duration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,37 @@ pragma solidity 0.8.26;

import {Timestamp, Timestamps} from "./Timestamp.sol";

// ---
// Type Definition
// ---

type Duration is uint32;

// ---
// Assign Global Operations
// ---

using {lt as <, lte as <=, eq as ==, neq as !=, gte as >=, gt as >} for Duration global;
using {addTo, plusSeconds, minusSeconds, multipliedBy, dividedBy, toSeconds} for Duration global;
using {plus as +, minus as -} for Duration global;

// ---
// Errors
// ---

error DivisionByZero();
error DurationOverflow();
error DurationUnderflow();

// the max possible duration is ~ 106 years
uint256 constant MAX_VALUE = type(uint32).max;
// ---
// Constants
// ---

using {lt as <, lte as <=, gt as >, gte as >=, eq as ==, notEq as !=} for Duration global;
using {plus as +, minus as -} for Duration global;
using {addTo, plusSeconds, minusSeconds, multipliedBy, dividedBy, toSeconds} for Duration global;
/// @dev The maximum possible duration is approximately 136 years (assuming 365 days per year).
uint32 constant MAX_DURATION_VALUE = type(uint32).max;

// ---
// Comparison Ops
// Comparison Operations
// ---

function lt(Duration d1, Duration d2) pure returns (bool) {
Expand All @@ -27,88 +44,113 @@ function lte(Duration d1, Duration d2) pure returns (bool) {
return Duration.unwrap(d1) <= Duration.unwrap(d2);
}

function gt(Duration d1, Duration d2) pure returns (bool) {
return Duration.unwrap(d1) > Duration.unwrap(d2);
function eq(Duration d1, Duration d2) pure returns (bool) {
return Duration.unwrap(d1) == Duration.unwrap(d2);
}

function neq(Duration d1, Duration d2) pure returns (bool) {
return Duration.unwrap(d1) != Duration.unwrap(d2);
}

function gte(Duration d1, Duration d2) pure returns (bool) {
return Duration.unwrap(d1) >= Duration.unwrap(d2);
}

function eq(Duration d1, Duration d2) pure returns (bool) {
return Duration.unwrap(d1) == Duration.unwrap(d2);
function gt(Duration d1, Duration d2) pure returns (bool) {
return Duration.unwrap(d1) > Duration.unwrap(d2);
}

function notEq(Duration d1, Duration d2) pure returns (bool) {
return !(d1 == d2);
// ---
// Conversion Operations
// ---

function toSeconds(Duration d) pure returns (uint256) {
return Duration.unwrap(d);
}

// ---
// Arithmetic Operations
// ---

function plus(Duration d1, Duration d2) pure returns (Duration) {
return toDuration(Duration.unwrap(d1) + Duration.unwrap(d2));
unchecked {
/// @dev Both `d1.toSeconds()` and `d2.toSeconds()` are <= type(uint32).max. Therefore, their
/// sum is <= type(uint256).max.
return Durations.from(d1.toSeconds() + d2.toSeconds());
}
}

function minus(Duration d1, Duration d2) pure returns (Duration) {
if (d1 < d2) {
uint256 d1Seconds = d1.toSeconds();
uint256 d2Seconds = d2.toSeconds();

if (d1Seconds < d2Seconds) {
revert DurationUnderflow();
}
return Duration.wrap(Duration.unwrap(d1) - Duration.unwrap(d2));

unchecked {
/// @dev Subtraction is safe because `d1Seconds` >= `d2Seconds`.
/// Both `d1Seconds` and `d2Seconds` <= `type(uint32).max`, so the difference fits within `uint32`.
return Duration.wrap(uint32(d1Seconds - d2Seconds));
}
}

function plusSeconds(Duration d, uint256 seconds_) pure returns (Duration) {
return toDuration(Duration.unwrap(d) + seconds_);
// ---
// Custom Operations
// ---

function plusSeconds(Duration d, uint256 secondsToAdd) pure returns (Duration) {
return Durations.from(d.toSeconds() + secondsToAdd);
}

function minusSeconds(Duration d, uint256 seconds_) pure returns (Duration) {
uint256 durationValue = Duration.unwrap(d);
if (durationValue < seconds_) {
function minusSeconds(Duration d, uint256 secondsToSubtract) pure returns (Duration) {
uint256 durationSeconds = d.toSeconds();

if (durationSeconds < secondsToSubtract) {
revert DurationUnderflow();
}
return Duration.wrap(uint32(durationValue - seconds_));

unchecked {
/// @dev Subtraction is safe because `durationSeconds` >= `secondsToSubtract`.
/// Both `durationSeconds` and `secondsToSubtract` <= `type(uint32).max`,
/// so the difference fits within `uint32`.
return Duration.wrap(uint32(durationSeconds - secondsToSubtract));
}
}

function dividedBy(Duration d, uint256 divisor) pure returns (Duration) {
return Duration.wrap(uint32(Duration.unwrap(d) / divisor));
if (divisor == 0) {
revert DivisionByZero();
}
return Duration.wrap(uint32(d.toSeconds() / divisor));
}

function multipliedBy(Duration d, uint256 multiplicand) pure returns (Duration) {
return toDuration(Duration.unwrap(d) * multiplicand);
return Durations.from(multiplicand * d.toSeconds());
}

function addTo(Duration d, Timestamp t) pure returns (Timestamp) {
return Timestamps.from(t.toSeconds() + d.toSeconds());
unchecked {
/// @dev Both `t.toSeconds()` <= `type(uint40).max` and `d.toSeconds()` <= `type(uint32).max`, so their
/// sum fits within `uint256`.
return Timestamps.from(t.toSeconds() + d.toSeconds());
}
}

// ---
// Conversion Ops
// Namespaced Helper Methods
// ---

function toDuration(uint256 value) pure returns (Duration) {
if (value > MAX_VALUE) {
revert DurationOverflow();
}
return Duration.wrap(uint32(value));
}

function toSeconds(Duration d) pure returns (uint256) {
return Duration.unwrap(d);
}

library Durations {
Duration internal constant ZERO = Duration.wrap(0);

Duration internal constant MIN = ZERO;
Duration internal constant MAX = Duration.wrap(uint32(MAX_VALUE));

function from(uint256 seconds_) internal pure returns (Duration res) {
res = toDuration(seconds_);
}

function between(Timestamp t1, Timestamp t2) internal pure returns (Duration res) {
res = toDuration(t1.toSeconds() - t2.toSeconds());
function from(uint256 durationInSeconds) internal pure returns (Duration res) {
if (durationInSeconds > MAX_DURATION_VALUE) {
revert DurationOverflow();
}
/// @dev Casting `durationInSeconds` to `uint32` is safe as the check ensures it is less than or equal
/// to `MAX_DURATION_VALUE`, which fits within the `uint32`.
res = Duration.wrap(uint32(durationInSeconds));
}

function min(Duration d1, Duration d2) internal pure returns (Duration res) {
Expand Down
94 changes: 73 additions & 21 deletions contracts/types/ETHValue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,41 @@ pragma solidity 0.8.26;

import {Address} from "@openzeppelin/contracts/utils/Address.sol";

// ---
// Type Definition
// ---

type ETHValue is uint128;

// ---
// Assign Global Operations
// ---

using {lt as <, eq as ==, neq as !=, gt as >} for ETHValue global;
using {toUint256, sendTo} for ETHValue global;
using {plus as +, minus as -} for ETHValue global;

// ---
// Errors
// ---

error ETHValueOverflow();
error ETHValueUnderflow();

using {plus as +, minus as -, lt as <, gt as >, eq as ==, neq as !=} for ETHValue global;
using {toUint256} for ETHValue global;
using {sendTo} for ETHValue global;
// ---
// Constants
// ---

function sendTo(ETHValue value, address payable recipient) {
Address.sendValue(recipient, value.toUint256());
}
uint128 constant MAX_ETH_VALUE = type(uint128).max;

function plus(ETHValue v1, ETHValue v2) pure returns (ETHValue) {
return ETHValues.from(ETHValue.unwrap(v1) + ETHValue.unwrap(v2));
}

function minus(ETHValue v1, ETHValue v2) pure returns (ETHValue) {
if (v1 < v2) {
revert ETHValueUnderflow();
}
return ETHValues.from(ETHValue.unwrap(v1) - ETHValue.unwrap(v2));
}
// ---
// Comparison Operations
// ---

function lt(ETHValue v1, ETHValue v2) pure returns (bool) {
return ETHValue.unwrap(v1) < ETHValue.unwrap(v2);
}

function gt(ETHValue v1, ETHValue v2) pure returns (bool) {
return ETHValue.unwrap(v1) > ETHValue.unwrap(v2);
}

function eq(ETHValue v1, ETHValue v2) pure returns (bool) {
return ETHValue.unwrap(v1) == ETHValue.unwrap(v2);
}
Expand All @@ -43,17 +46,66 @@ function neq(ETHValue v1, ETHValue v2) pure returns (bool) {
return ETHValue.unwrap(v1) != ETHValue.unwrap(v2);
}

function gt(ETHValue v1, ETHValue v2) pure returns (bool) {
return ETHValue.unwrap(v1) > ETHValue.unwrap(v2);
}

// ---
// Conversion Operations
// ---

function toUint256(ETHValue value) pure returns (uint256) {
return ETHValue.unwrap(value);
}

// ---
// Arithmetic Operations
// ---

function plus(ETHValue v1, ETHValue v2) pure returns (ETHValue) {
unchecked {
/// @dev Both `v1.toUint256()` and `v2.toUint256()` are <= type(uint128).max. Therefore, their
/// sum is <= type(uint256).max.
return ETHValues.from(v1.toUint256() + v2.toUint256());
}
}

function minus(ETHValue v1, ETHValue v2) pure returns (ETHValue) {
uint256 v1Value = v1.toUint256();
uint256 v2Value = v2.toUint256();

if (v1Value < v2Value) {
revert ETHValueUnderflow();
}

unchecked {
/// @dev Subtraction is safe because `v1Value` >= `v2Value`.
/// Both `v1Value` and `v2Value` <= `type(uint128).max`, so the difference fits within `uint128`.
return ETHValue.wrap(uint128(v1Value - v2Value));
}
}

// ---
// Custom Operations
// ---

function sendTo(ETHValue value, address payable recipient) {
Address.sendValue(recipient, value.toUint256());
}

// ---
// Namespaced Helper Methods
// ---

library ETHValues {
ETHValue internal constant ZERO = ETHValue.wrap(0);

function from(uint256 value) internal pure returns (ETHValue) {
if (value > type(uint128).max) {
if (value > MAX_ETH_VALUE) {
revert ETHValueOverflow();
}
/// @dev Casting `value` to `uint128` is safe as the check ensures it is less than or equal
/// to `MAX_ETH_VALUE`, which fits within the `uint128`.
return ETHValue.wrap(uint128(value));
}

Expand Down
Loading

0 comments on commit a0768dd

Please sign in to comment.