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

feat: add SemVerMixin #1034

Merged
merged 6 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from 2 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
11 changes: 11 additions & 0 deletions src/contracts/interfaces/ISemVerMixin.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

/// @title ISemVerMixin
/// @notice A mixin interface that provides semantic versioning functionality.
/// @dev Follows SemVer 2.0.0 specification (https://semver.org/)
interface ISemVerMixin {
/// @notice Returns the semantic version string of the contract.
/// @return The version string in SemVer format (e.g., "v1.1.1")
function version() external view returns (string memory);
0xClandestine marked this conversation as resolved.
Show resolved Hide resolved
}
5 changes: 0 additions & 5 deletions src/contracts/interfaces/ISignatureUtilsMixin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@ interface ISignatureUtilsMixinTypes {
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
*/
interface ISignatureUtilsMixin is ISignatureUtilsMixinErrors, ISignatureUtilsMixinTypes {
/// @notice Returns the semantic version string used in the domain separator.
/// @return The version string as a regular string (converted from ShortString).
/// @dev Returns a SemVer-formatted string with 'v' prefix (e.g., "v1.1.1").
function version() external view returns (string memory);

/// @notice Computes the EIP-712 domain separator used for signature validation.
/// @dev The domain separator is computed according to EIP-712 specification, using:
/// - The hardcoded name "EigenLayer"
Expand Down
38 changes: 38 additions & 0 deletions src/contracts/mixins/SemVerMixin.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import "@openzeppelin-upgrades/contracts/utils/ShortStringsUpgradeable.sol";
import "../interfaces/ISemVerMixin.sol";

/// @title SemVerMixin
/// @notice A mixin contract that provides semantic versioning functionality.
/// @dev Follows SemVer 2.0.0 specification (https://semver.org/).
abstract contract SemVerMixin is ISemVerMixin {
using ShortStringsUpgradeable for *;

/// @notice The semantic version string for this contract, stored as a ShortString for gas efficiency.
/// @dev Follows SemVer 2.0.0 specification (https://semver.org/). Prefixed with 'v' (e.g., "v1.2.3").
ShortString internal immutable _VERSION;

/// @notice Initializes the contract with a semantic version string.
/// @param _version The SemVer-formatted version string (e.g., "v1.2.3")
/// @dev Version should follow SemVer 2.0.0 format with 'v' prefix: vMAJOR.MINOR.PATCH
constructor(
string memory _version
) {
_VERSION = _version.toShortString();
}

/// @notice Returns the semantic version string of the contract
/// @return The version string in SemVer format (e.g., "v1.2.3")
function version() public view virtual returns (string memory) {
return _VERSION.toString();
}

/// @notice Returns the major version of the contract.
/// @return The major version string (e.g., "v1" for version "v1.2.3")
function _majorVersion() internal view returns (string memory) {
bytes memory v = bytes(_VERSION.toString());
return string(bytes.concat(v[0], v[1]));
}
}
30 changes: 8 additions & 22 deletions src/contracts/mixins/SignatureUtilsMixin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "@openzeppelin-upgrades/contracts/utils/ShortStringsUpgradeable.sol";
import "@openzeppelin-upgrades/contracts/utils/cryptography/SignatureCheckerUpgradeable.sol";

import "../interfaces/ISignatureUtilsMixin.sol";
import "./SemVerMixin.sol";

/// @dev The EIP-712 domain type hash used for computing the domain separator
/// See https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator
Expand All @@ -14,37 +15,22 @@ bytes32 constant EIP712_DOMAIN_TYPEHASH =
/// @title SignatureUtilsMixin
/// @notice A mixin contract that provides utilities for validating signatures according to EIP-712 and EIP-1271 standards.
/// @dev The domain name is hardcoded to "EigenLayer". This contract implements signature validation functionality that can be
/// inherited by other contracts.
abstract contract SignatureUtilsMixin is ISignatureUtilsMixin {
using ShortStringsUpgradeable for *;
/// inherited by other contracts. The domain separator uses the major version (e.g., "v1") to maintain EIP-712
/// signature compatibility across minor and patch version updates.
abstract contract SignatureUtilsMixin is ISignatureUtilsMixin, SemVerMixin {
using SignatureCheckerUpgradeable for address;

/// IMMUTABLES ///

/// @notice The semantic version string for this contract, stored as a ShortString for gas efficiency.
/// @dev Follows SemVer 2.0.0 specification (https://semver.org/). Prefixed with 'v' (e.g., "v1.1.1").
/// WARNING: Modifying this version will invalidate all existing EIP-712 signatures since it changes the domain separator.
ShortString private immutable _VERSION;

/// CONSTRUCTION ///

/// @notice Initializes the contract with a semantic version string.
/// @param _version The SemVer-formatted version string (e.g., "v1.1.1") to use for this contract's domain separator.
/// @dev Version should follow SemVer 2.0.0 format with 'v' prefix: vMAJOR.MINOR.PATCH.
/// WARNING: Modifying this version will invalidate all existing EIP-712 signatures since it changes the domain separator.
/// Only the major version component is used in the domain separator to maintain signature compatibility
/// across minor and patch version updates.
constructor(
string memory _version
) {
_VERSION = _version.toShortString();
}
) SemVerMixin(_version) {}

/// EXTERNAL FUNCTIONS ///

/// @inheritdoc ISignatureUtilsMixin
function version() public view virtual returns (string memory) {
return _VERSION.toString();
}

/// @inheritdoc ISignatureUtilsMixin
function domainSeparator() public view virtual returns (bytes32) {
// forgefmt: disable-next-item
Expand All @@ -53,7 +39,7 @@ abstract contract SignatureUtilsMixin is ISignatureUtilsMixin {
abi.encode(
EIP712_DOMAIN_TYPEHASH,
keccak256(bytes("EigenLayer")),
keccak256(bytes(version())),
keccak256(bytes(_majorVersion())),
block.chainid,
address(this)
)
Expand Down
29 changes: 29 additions & 0 deletions src/test/unit/mixins/SemVerMixin.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import {Test} from "forge-std/Test.sol";
import {SemVerMixin} from "src/contracts/mixins/SemVerMixin.sol";

// Helper contract to test the abstract SemVerMixin
contract SemVerMixinMock is SemVerMixin {
constructor(string memory version) SemVerMixin(version) {}

// Expose internal function for testing
function majorVersion() public view returns (string memory) {
return _majorVersion();
}
}

contract SemVerMixinTest is Test {
SemVerMixinMock public semVer;

function test_version_returnsCorrectVersion() public {
semVer = new SemVerMixinMock("v1.2.3");
assertEq(semVer.version(), "v1.2.3");
}

function test_majorVersion_returnsCorrectMajorVersion() public {
semVer = new SemVerMixinMock("v1.2.3");
assertEq(semVer.majorVersion(), "v1");
}
}
Loading