Warning: The SDK is a work in progress and is currently under audits. Use with caution.
This repository provides a framework for developing middleware in a modular and extensible way. It leverages various base contracts and extensions to handle key functionalities such as operator management, access control, key storage, timestamp capturing and stake to power calculation.
-
BaseMiddleware: The foundational contract that combines core manager functionalities from
VaultManager
,OperatorManager
,AccessManager
, andKeyManager
. -
Extensions: Modular contracts that provide additional functionalities. Key extensions include:
-
Operators: Manages operator registration and operator's vault.
-
KeyManager: Manages operator keys. Variants include
KeyManagerAddress
,KeyManager256
,KeyManagerBytes
, andNoKeyManager
. -
AccessManager: Controls access to restricted functions. Implementations include
OwnableAccessManager
,OzAccessControl
,OzAccessManaged
, andNoAccessManager
. -
CaptureTimestamp: Captures the active state at specific timestamps. Options are
EpochCapture
andTimestampCapture
. -
Signature Verification: Verifies operator signatures. Implementations include
ECDSASig
andEdDSASig
. -
StakePower: Calculates operator power based on stake. Implementations include
EqualStakePower
for 1:1 stake-to-power ratio, and can be extended for custom power calculations. -
SharedVaults: Manages vaults shared between all operators.
-
Subnetworks: Manages subnetworks.
-
Below are examples of middleware implementations using different combinations of the extensions.
contract SimplePosMiddleware is SharedVaults, Operators, KeyManager256, OwnableAccessManager, EpochCapture, EqualStakePower {
// Implementation details...
}
Features:
- Manages operator keys and stakes.
- Retrieves validator sets and total stakes.
- Implements slashing logic based on epochs.
contract SqrtTaskMiddleware is SharedVaults, Operators, NoKeyManager, EIP712, OwnableAccessManager, TimestampCapture, EqualStakePower {
// Implementation details...
}
Features:
- Allows creation of computational tasks.
- Verifies task completion using signatures.
- Implements slashing for incorrect task completion.
contract SelfRegisterMiddleware is SharedVaults, SelfRegisterOperators, KeyManagerAddress, ECDSASig, NoAccessManager, TimestampCapture, EqualStakePower {
// Implementation details...
}
Features:
- Operators can self-register using ECDSA signatures.
- Manages operator keys and vault associations.
- No access restrictions on functions.
contract SelfRegisterEd25519Middleware is SharedVaults, SelfRegisterOperators, KeyManager256, EdDSASig, NoAccessManager, TimestampCapture {
// Implementation details...
}
Features:
- Similar to
SelfRegisterMiddleware
but uses Ed25519 keys and EdDSA signatures.
contract SelfRegisterSqrtTaskMiddleware is SharedVaults, SelfRegisterOperators, KeyManagerAddress, ECDSASig, OwnableAccessManager, TimestampCapture, EqualStakePower {
// Implementation details...
}
Features:
- Similar to
SqrtTaskMiddleware
but allows self-registration of operators, permissionless shared vaults management and uses ECDSA signatures and keys.
To develop your middleware:
-
Choose Extensions: Based on your requirements, include extensions for operator management, key storage, access control, and timestamp capturing.
-
Initialize Properly: Ensure all inherited contracts are properly initialized:
- Write an initialization function with the
initializer
modifier - Call
_disableInitializers()
in the constructor for upgradeable contracts - Initialize
BaseMiddleware
and extensions in the correct order - Pass required parameters to each contract's initialization function
- Follow initialization order from most base to most derived contract
- Note: If your contract is not upgradeable, initialization can be done directly in the constructor:
constructor( address network, uint48 slashingWindow, address vaultRegistry, address operatorRegistry, address operatorNetOptIn, address readHelper, address admin ) { initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn, readHelper, admin); }
- Example initialization pattern:
function initialize( address network, uint48 slashingWindow, address vaultRegistry, address operatorRegistry, address operatorNetOptIn, address readHelper, address admin ) public initializer { __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn, readHelper); __OzAccessManaged_init(admin); __AdditionalExtension_init(); }
- Write an initialization function with the
-
Custom Logic (Optional): Override manager functions to implement custom logic without using extensions (e.g.
StakePower
manager). Additionally, implement your own functions to extend the middleware's capabilities.
contract MyCustomMiddleware is BaseMiddleware, Operators, KeyStorage256, OwnableAccessManager {
uint64 public constant MyCustomMiddleware_VERSION = 1;
/**
* @notice Override getCaptureTimestamp to provide custom timestamp logic
* @return timestamp The current block timestamp
*/
function getCaptureTimestamp() public view override returns (uint48 timestamp) {
return uint48(block.timestamp);
}
/**
* @notice Custom function to calculate the square of a given number
* @param number The number to be squared
* @return result The square of the given number
*/
function calculateSquare(uint256 number) public pure returns (uint256 result) {
return number * number;
}
}
-
Configure Access Control (Optional): When using access control extensions, set up roles and permissions:
// Define role identifiers as constants bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); bytes32 public constant VAULT_ROLE = keccak256("VAULT_ROLE"); bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); function initialize(...) public initializer { // Initialize base contracts __BaseMiddleware_init(...); __OzAccessControl_init(admin); // Set up role hierarchy _setRoleAdmin(OPERATOR_ROLE, MANAGER_ROLE); // Manager role can grant/revoke operator role _setRoleAdmin(VAULT_ROLE, MANAGER_ROLE); // Manager role can grant/revoke vault role _setRoleAdmin(MANAGER_ROLE, DEFAULT_ADMIN_ROLE); // Default admin can grant/revoke manager role // Assign roles to function selectors _setSelectorRole(this.registerOperator.selector, OPERATOR_ROLE); _setSelectorRole(this.registerVault.selector, VAULT_ROLE); _setSelectorRole(this.updateParameters.selector, MANAGER_ROLE); // Grant initial roles _grantRole(MANAGER_ROLE, admin); }
-
Storage Slots: When creating extensions, ensure you follow the ERC-7201 standard for storage slot allocation to prevent conflicts.
-
Versioning: Include a public constant variable for versioning in your contracts (e.g.,
uint64 public constant MyExtension_VERSION = 1;
). -
Access Control: Choose an appropriate
AccessManager
based on your needs:NoAccessManager
: Allows unrestricted access to all functionsOwnableAccessManager
: Restricts access to a single owner addressOzAccessControl
: Implements OpenZeppelin-style role-based access control where different roles can be assigned to specific function selectors. Roles can be granted and revoked by role admins, with a default admin role that can manage all other roles. Roles can be set up by:- Granting roles to addresses using
grantRole(bytes32 role, address account)
- Setting role admins with
_setRoleAdmin(bytes32 role, bytes32 adminRole)
- Assigning roles to function selectors via
_setSelectorRole(bytes4 selector, bytes32 role)
- Granting roles to addresses using
OzAccessManaged
: Wraps OpenZeppelin's AccessManaged contract to integrate with external access control systems. This allows for more complex access control scenarios where permissions are managed externally, providing flexibility and scalability in managing roles and permissions.
-
Key Manager: Choose a
KeyManager
implementation that suits your key management needs. UseKeyManagerAddress
for managing address keys,KeyManager256
for managing 256-bit keys,KeyManagerBytes
for handling arbitrary-length keys, orNoKeyManager
if key management is not required.
This framework provides flexibility in building middleware by allowing you to mix and match various extensions based on your requirements. By following the modular approach and best practices outlined, you can develop robust middleware solutions that integrate seamlessly with the network.
This project is licensed under the MIT License. See the LICENSE file for details.