Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/contracts/permissions/KeyRegistrar.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "../libraries/BN254.sol";
import "../libraries/BN254SignatureVerifier.sol";
import "../mixins/PermissionControllerMixin.sol";
import "../mixins/SignatureUtilsMixin.sol";
import "../interfaces/IPermissionController.sol";
import "../interfaces/IAllocationManager.sol";
import "../interfaces/IKeyRegistrar.sol";
import "../libraries/OperatorSetLib.sol";
import "./KeyRegistrarStorage.sol";
/**
* @title KeyRegistrar
* @notice A core singleton contract that manages operator keys for different AVSs with global key uniqueness
* @dev Provides registration and deregistration of keys with support for aggregate keys
* Keys must be unique globally across all AVSs and operator sets
* Operators call functions directly to manage their own keys
* Aggregate keys are updated via callback from AVSRegistrar on registration and deregistration
*/
contract KeyRegistrar is KeyRegistrarStorage, PermissionControllerMixin, SignatureUtilsMixin {
using BN254 for BN254.G1Point;
// EIP-712 type hashes
bytes32 public constant ECDSA_KEY_REGISTRATION_TYPEHASH =
keccak256("ECDSAKeyRegistration(address operator,address avs,uint32 operatorSetId,address keyAddress)");
bytes32 public constant BN254_KEY_REGISTRATION_TYPEHASH =
keccak256("BN254KeyRegistration(address operator,address avs,uint32 operatorSetId,bytes keyData)");
/**
* @dev Constructor for the KeyRegistrar contract
* @param _permissionController The permission controller contract
* @param _allocationManager The allocation manager contract
* @param _version The version string for the contract
*/
constructor(
IPermissionController _permissionController,
IAllocationManager _allocationManager,
string memory _version
)
KeyRegistrarStorage(_allocationManager)
PermissionControllerMixin(_permissionController)
SignatureUtilsMixin(_version)
{}
/// @inheritdoc IKeyRegistrar
function configureOperatorSet(
OperatorSet memory operatorSet,
CurveType curveType
) external checkCanCall(operatorSet.avs) {
require(curveType == CurveType.ECDSA || curveType == CurveType.BN254, InvalidCurveType());
// Prevent overwriting existing configurations
CurveType _curveType = _operatorSetCurveTypes[operatorSet.key()];
require(_curveType == CurveType.NONE, ConfigurationAlreadySet());
_operatorSetCurveTypes[operatorSet.key()] = curveType;
emit OperatorSetConfigured(operatorSet, curveType);
}
/// @inheritdoc IKeyRegistrar
function registerKey(
address operator,
OperatorSet memory operatorSet,
bytes calldata keyData,
bytes calldata signature
) external checkCanCall(operator) {
CurveType curveType = _operatorSetCurveTypes[operatorSet.key()];
require(curveType != CurveType.NONE, OperatorSetNotConfigured());
// Check if the operator is already registered to the operatorSet
require(!_operatorKeyInfo[operatorSet.key()][operator].isRegistered, OperatorAlreadyRegistered());
// Register key based on curve type - both now require signature verification
if (curveType == CurveType.ECDSA) {
_registerECDSAKey(operatorSet, operator, keyData, signature);
} else if (curveType == CurveType.BN254) {
_registerBN254Key(operatorSet, operator, keyData, signature);
} else {
revert InvalidCurveType();
}
emit KeyRegistered(operatorSet, operator, curveType, keyData);
}
/// @inheritdoc IKeyRegistrar
function deregisterKey(address operator, OperatorSet memory operatorSet) external checkCanCall(operator) {
// Operators can only deregister if they are not slashable for this operator set
require(
!allocationManager.isOperatorSlashable(operator, operatorSet), OperatorStillSlashable(operatorSet, operator)
);
CurveType curveType = _operatorSetCurveTypes[operatorSet.key()];
require(curveType != CurveType.NONE, OperatorSetNotConfigured());
KeyInfo memory keyInfo = _operatorKeyInfo[operatorSet.key()][operator];
require(keyInfo.isRegistered, KeyNotFound(operatorSet, operator));
// Clear key info
delete _operatorKeyInfo[operatorSet.key()][operator];
emit KeyDeregistered(operatorSet, operator, curveType);
}
/**
*
* INTERNAL FUNCTIONS
*
*/
/**
* @notice Validates and registers an ECDSA address with EIP-712 signature verification
* @param operatorSet The operator set to register the key for
* @param operator Address of the operator
* @param keyData The ECDSA address encoded as bytes (20 bytes)
* @param signature EIP-712 signature over the registration message
* @dev Validates address format, verifies signature ownership, and ensures global uniqueness
*/
function _registerECDSAKey(
OperatorSet memory operatorSet,
address operator,
bytes calldata keyData,
bytes calldata signature
) internal {
// Validate ECDSA address format
require(keyData.length == 20, InvalidKeyFormat());
// Decode address from bytes
address keyAddress = address(bytes20(keyData));
require(keyAddress != address(0), ZeroPubkey());
// Calculate key hash using the address
bytes32 keyHash = _getKeyHashForKeyData(keyData, CurveType.ECDSA);
// Check global uniqueness
require(!_globalKeyRegistry[keyHash], KeyAlreadyRegistered());
// Get the signable digest for the ECDSA key registration message
bytes32 signableDigest = getECDSAKeyRegistrationMessageHash(operator, operatorSet, keyAddress);
_checkIsValidSignatureNow(keyAddress, signableDigest, signature, type(uint256).max);
// Store key data
_storeKeyData(operatorSet, operator, keyData, keyHash);
}
/**
* @notice Validates and registers a BN254 public key with proper signature verification
* @param operatorSet The operator set to register the key for
* @param operator Address of the operator
* @param keyData The BN254 public key bytes (G1 and G2 components)
* @param signature Signature proving key ownership
* @dev Validates keypair, verifies signature using hash-to-G1, and ensures global uniqueness
*/
function _registerBN254Key(
OperatorSet memory operatorSet,
address operator,
bytes calldata keyData,
bytes calldata signature
) internal {
require(keyData.length == 192, InvalidKeyFormat());
require(signature.length == 64, InvalidSignature());
BN254.G1Point memory g1Point;
BN254.G2Point memory g2Point;
{
// Decode BN254 G1 and G2 points from the keyData bytes
(uint256 g1X, uint256 g1Y, uint256[2] memory g2X, uint256[2] memory g2Y) =
abi.decode(keyData, (uint256, uint256, uint256[2], uint256[2]));
// Validate G1 point
g1Point = BN254.G1Point(g1X, g1Y);
require(!(g1X == 0 && g1Y == 0), ZeroPubkey());
// Construct BN254 G2 point from coordinates
g2Point = BN254.G2Point(g2X, g2Y);
}
// Create EIP-712 compliant message hash
bytes32 signableDigest = getBN254KeyRegistrationMessageHash(operator, operatorSet, keyData);
// Decode signature from bytes to G1 point
(uint256 sigX, uint256 sigY) = abi.decode(signature, (uint256, uint256));
BN254.G1Point memory signaturePoint = BN254.G1Point(sigX, sigY);
// Verify signature
(, bool pairingSuccessful) =
BN254SignatureVerifier.verifySignature(signableDigest, signaturePoint, g1Point, g2Point, false, 0);
require(pairingSuccessful, InvalidSignature());
// Calculate key hash and check global uniqueness
bytes32 keyHash = _getKeyHashForKeyData(keyData, CurveType.BN254);
require(!_globalKeyRegistry[keyHash], KeyAlreadyRegistered());
// Store key data
_storeKeyData(operatorSet, operator, keyData, keyHash);
}
/**
* @notice Internal helper to store key data and update global registry
* @param operatorSet The operator set
* @param operator The operator address
* @param pubkey The public key data
* @param keyHash The key hash
*/
function _storeKeyData(
OperatorSet memory operatorSet,
address operator,
bytes memory pubkey,
bytes32 keyHash
) internal {
// Store key data
_operatorKeyInfo[operatorSet.key()][operator] = KeyInfo({isRegistered: true, keyData: pubkey});
// Mark the key hash as spent
_globalKeyRegistry[keyHash] = true;
// Store the operator for the key hash
_keyHashToOperator[keyHash] = operator;
}
/**
* @notice Internal helper to get key hash for pubkey data using consistent hashing
* @param pubkey The public key data
* @param curveType The curve type (ECDSA or BN254)
* @return keyHash The key hash
*/
function _getKeyHashForKeyData(bytes memory pubkey, CurveType curveType) internal pure returns (bytes32) {
if (curveType == CurveType.ECDSA) {
return keccak256(pubkey);
} else if (curveType == CurveType.BN254) {
(uint256 g1X, uint256 g1Y,,) = abi.decode(pubkey, (uint256, uint256, uint256[2], uint256[2]));
return BN254.hashG1Point(BN254.G1Point(g1X, g1Y));
}
revert InvalidCurveType();
}
/**
*
* VIEW FUNCTIONS
*
*/
/// @inheritdoc IKeyRegistrar
function isRegistered(OperatorSet memory operatorSet, address operator) public view returns (bool) {
return _operatorKeyInfo[operatorSet.key()][operator].isRegistered;
}
/// @inheritdoc IKeyRegistrar
function getOperatorSetCurveType(
OperatorSet memory operatorSet
) external view returns (CurveType) {
return _operatorSetCurveTypes[operatorSet.key()];
}
/// @inheritdoc IKeyRegistrar
function getBN254Key(
OperatorSet memory operatorSet,
address operator
) external view returns (BN254.G1Point memory g1Point, BN254.G2Point memory g2Point) {
// Validate operator set curve type
CurveType curveType = _operatorSetCurveTypes[operatorSet.key()];
require(curveType == CurveType.BN254, InvalidCurveType());
KeyInfo memory keyInfo = _operatorKeyInfo[operatorSet.key()][operator];
if (!keyInfo.isRegistered) {
// Create default values for an empty key
uint256[2] memory zeroArray = [uint256(0), uint256(0)];
return (BN254.G1Point(0, 0), BN254.G2Point(zeroArray, zeroArray));
}
(uint256 g1X, uint256 g1Y, uint256[2] memory g2X, uint256[2] memory g2Y) =
abi.decode(keyInfo.keyData, (uint256, uint256, uint256[2], uint256[2]));
return (BN254.G1Point(g1X, g1Y), BN254.G2Point(g2X, g2Y));
}
/// @inheritdoc IKeyRegistrar
function getECDSAKey(OperatorSet memory operatorSet, address operator) public view returns (bytes memory) {
// Validate operator set curve type
CurveType curveType = _operatorSetCurveTypes[operatorSet.key()];
require(curveType == CurveType.ECDSA, InvalidCurveType());
KeyInfo memory keyInfo = _operatorKeyInfo[operatorSet.key()][operator];
return keyInfo.keyData; // Returns the 20-byte address as bytes
}
/// @inheritdoc IKeyRegistrar
function getECDSAAddress(OperatorSet memory operatorSet, address operator) external view returns (address) {
return address(bytes20(getECDSAKey(operatorSet, operator)));
}
/// @inheritdoc IKeyRegistrar
function isKeyGloballyRegistered(
bytes32 keyHash
) external view returns (bool) {
return _globalKeyRegistry[keyHash];
}
/// @inheritdoc IKeyRegistrar
function getKeyHash(OperatorSet memory operatorSet, address operator) external view returns (bytes32) {
KeyInfo memory keyInfo = _operatorKeyInfo[operatorSet.key()][operator];
CurveType curveType = _operatorSetCurveTypes[operatorSet.key()];
if (!keyInfo.isRegistered) {
return bytes32(0);
}
return _getKeyHashForKeyData(keyInfo.keyData, curveType);
}
/// @inheritdoc IKeyRegistrar
function getOperatorFromSigningKey(
OperatorSet memory operatorSet,
bytes memory keyData
) external view returns (address, bool) {
CurveType curveType = _operatorSetCurveTypes[operatorSet.key()];
// We opt to not use _getKeyHashForKeyData here because it expects the G1 and G2 key encoded together for BN254
bytes32 keyHash;
if (curveType == CurveType.ECDSA) {
keyHash = keccak256(keyData);
} else if (curveType == CurveType.BN254) {
/// We cannot use _getKeyHashForKeyData here because it expects the G1 and G2 key encoded together
(uint256 g1X, uint256 g1Y) = abi.decode(keyData, (uint256, uint256));
keyHash = BN254.hashG1Point(BN254.G1Point(g1X, g1Y));
} else {
revert InvalidCurveType();
}
address operator = _keyHashToOperator[keyHash];
return (operator, isRegistered(operatorSet, operator));
}
/// @inheritdoc IKeyRegistrar
function getECDSAKeyRegistrationMessageHash(
address operator,
OperatorSet memory operatorSet,
address keyAddress
) public view returns (bytes32) {
bytes32 structHash = keccak256(
abi.encode(ECDSA_KEY_REGISTRATION_TYPEHASH, operator, operatorSet.avs, operatorSet.id, keyAddress)
);
return _calculateSignableDigest(structHash);
}
/// @inheritdoc IKeyRegistrar
function getBN254KeyRegistrationMessageHash(
address operator,
OperatorSet memory operatorSet,
bytes calldata keyData
) public view returns (bytes32) {
bytes32 structHash = keccak256(
abi.encode(BN254_KEY_REGISTRATION_TYPEHASH, operator, operatorSet.avs, operatorSet.id, keccak256(keyData))
);
return _calculateSignableDigest(structHash);
}
/// @inheritdoc IKeyRegistrar
function encodeBN254KeyData(
BN254.G1Point memory g1Point,
BN254.G2Point memory g2Point
) public pure returns (bytes memory) {
return abi.encode(g1Point.X, g1Point.Y, g2Point.X[0], g2Point.X[1], g2Point.Y[0], g2Point.Y[1]);
}
}
"
},
"lib/openzeppelin-contracts-v4.9.0/contracts/utils/cryptography/ECDSA.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../Strings.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV // Deprecated in v4.8
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, "\x19Ethereum Signed Message:\
32")
mstore(0x1c, hash)
message := keccak256(0x00, 0x3c)
}
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\
", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, "\x19\x01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
data := keccak256(ptr, 0x42)
}
}
/**
* @dev Returns an Ethereum Signed Data with intended validator, created from a
* `validator` and `data` according to the version 0 of EIP-191.
*
* See {recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x00", validator, data));
}
}
"
},
"src/contracts/libraries/BN254.sol": {
"content": "// SPDX-License-Identifier: MIT
// several functions are taken or adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol (MIT license):
// Copyright 2017 Christian Reitwiessner
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// The remainder of the code in this library is written by LayrLabs Inc. and is also under an MIT license
pragma solidity ^0.8.27;
/**
* @title Library for operations on the BN254 elliptic curve.
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
* @notice Contains BN254 parameters, common operations (addition, scalar mul, pairing), and BLS signature functionality.
*/
library BN254 {
// modulus for the underlying field F_p of the elliptic curve
uint256 internal constant FP_MODULUS =
21_888_242_871_839_275_222_246_405_745_257_275_088_696_311_157_297_823_662_689_037_894_645_226_208_583;
// modulus for the underlying field F_r of the elliptic curve
uint256 internal constant FR_MODULUS =
21_888_242_871_839_275_222_246_405_745_257_275_088_548_364_400_416_034_343_698_204_186_575_808_495_617;
struct G1Point {
uint256 X;
uint256 Y;
}
// Encoding of field elements is: X[1] * i + X[0]
struct G2Point {
uint256[2] X;
uint256[2] Y;
}
/// @dev Thrown when the sum of two points of G1 fails
error ECAddFailed();
/// @dev Thrown when the scalar multiplication of a point of G1 fails
error ECMulFailed();
/// @dev Thrown when the scalar is too large.
error ScalarTooLarge();
/// @dev Thrown when the pairing check fails
error ECPairingFailed();
/// @dev Thrown when the exponentiation mod fails
error ExpModFailed();
function generatorG1() internal pure returns (G1Point memory) {
return G1Point(1, 2);
}
// generator of group G2
/// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + it1).
uint256 internal constant G2x1 =
11_559_732_032_986_387_107_991_004_021_392_285_783_925_812_861_821_192_530_917_403_151_452_391_805_634;
uint256 internal constant G2x0 =
10_857_046_999_023_057_135_944_570_762_232_829_481_370_756_359_578_518_086_990_519_993_285_655_852_781;
uint256 internal constant G2y1 =
4_082_367_875_863_433_681_332_203_403_145_435_568_316_851_327_593_401_208_105_741_076_214_120_093_531;
uint256 internal constant G2y0 =
8_495_653_923_123_431_417_604_973_247_489_272_438_418_190_587_263_600_148_770_280_649_306_958_101_930;
/// @notice returns the G2 generator
/// @dev mind the ordering of the 1s and 0s!
/// this is because of the (unknown to us) convention used in the bn254 pairing precompile contract
/// "Elements a * i + b of F_p^2 are encoded as two elements of F_p, (a, b)."
/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-197.md#encoding
function generatorG2() internal pure returns (G2Point memory) {
return G2Point([G2x1, G2x0], [G2y1, G2y0]);
}
// negation of the generator of group G2
/// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + it1).
uint256 internal constant nG2x1 =
11_559_732_032_986_387_107_991_004_021_392_285_783_925_812_861_821_192_530_917_403_151_452_391_805_634;
uint256 internal constant nG2x0 =
10_857_046_999_023_057_135_944_570_762_232_829_481_370_756_359_578_518_086_990_519_993_285_655_852_781;
uint256 internal constant nG2y1 =
17_805_874_995_975_841_540_914_202_342_111_839_520_379_459_829_704_422_454_583_296_818_431_106_115_052;
uint256 internal constant nG2y0 =
13_392_588_948_715_843_804_641_432_497_768_002_650_278_120_570_034_223_513_918_757_245_338_268_106_653;
function negGeneratorG2() internal pure returns (G2Point memory) {
return G2Point([nG2x1, nG2x0], [nG2y1, nG2y0]);
}
bytes32 internal constant powersOfTauMerkleRoot = 0x22c998e49752bbb1918ba87d6d59dd0e83620a311ba91dd4b2cc84990b31b56f;
/**
* @param p Some point in G1.
* @return The negation of `p`, i.e. p.plus(p.negate()) should be zero.
*/
function negate(
G1Point memory p
) internal pure returns (G1Point memory) {
// The prime q in the base field F_q for G1
if (p.X == 0 && p.Y == 0) {
return G1Point(0, 0);
} else {
return G1Point(p.X, FP_MODULUS - (p.Y % FP_MODULUS));
}
}
/**
* @return r the sum of two points of G1
*/
function plus(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) {
uint256[4] memory input;
input[0] = p1.X;
input[1] = p1.Y;
input[2] = p2.X;
input[3] = p2.Y;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 6, input, 0x80, r, 0x40)
// Use "invalid" to make gas estimation work
switch success
case 0 { invalid() }
}
require(success, ECAddFailed());
}
/**
* @notice an optimized ecMul implementation that takes O(log_2(s)) ecAdds
* @param p the point to multiply
* @param s the scalar to multiply by
* @dev this function is only safe to use if the scalar is 9 bits or less
*/
function scalar_mul_tiny(BN254.G1Point memory p, uint16 s) internal view returns (BN254.G1Point memory) {
require(s < 2 ** 9, ScalarTooLarge());
// if s is 1 return p
if (s == 1) {
return p;
}
// the accumulated product to return
BN254.G1Point memory acc = BN254.G1Point(0, 0);
// the 2^n*p to add to the accumulated product in each iteration
BN254.G1Point memory p2n = p;
// value of most significant bit
uint16 m = 1;
// index of most significant bit
uint8 i = 0;
//loop until we reach the most significant bit
while (s >= m) {
unchecked {
// if the current bit is 1, add the 2^n*p to the accumulated product
if ((s >> i) & 1 == 1) {
acc = plus(acc, p2n);
}
// double the 2^n*p for the next iteration
p2n = plus(p2n, p2n);
// increment the index and double the value of the most significant bit
m <<= 1;
++i;
}
}
// return the accumulated product
return acc;
}
/**
* @return r the product of a point on G1 and a scalar, i.e.
* p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all
* points p.
*/
function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) {
uint256[3] memory input;
input[0] = p.X;
input[1] = p.Y;
input[2] = s;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 7, input, 0x60, r, 0x40)
// Use "invalid" to make gas estimation work
switch success
case 0 { invalid() }
}
require(success, ECMulFailed());
}
/**
* @return The result of computing the pairing check
* e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
* For example,
* pairing([P1(), P1().negate()], [P2(), P2()]) should return true.
*/
function pairing(
G1Point memory a1,
G2Point memory a2,
G1Point memory b1,
G2Point memory b2
) internal view returns (bool) {
G1Point[2] memory p1 = [a1, b1];
G2Point[2] memory p2 = [a2, b2];
uint256[12] memory input;
for (uint256 i = 0; i < 2; i++) {
uint256 j = i * 6;
input[j + 0] = p1[i].X;
input[j + 1] = p1[i].Y;
input[j + 2] = p2[i].X[0];
input[j + 3] = p2[i].X[1];
input[j + 4] = p2[i].Y[0];
input[j + 5] = p2[i].Y[1];
}
uint256[1] memory out;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 8, input, mul(12, 0x20), out, 0x20)
// Use "invalid" to make gas estimation work
switch success
case 0 { invalid() }
}
require(success, ECPairingFailed());
return out[0] != 0;
}
/**
* @notice This function is functionally the same as pairing(), however it specifies a gas limit
* the user can set, as a precompile may use the entire gas budget if it reverts.
*/
function safePairing(
G1Point memory a1,
G2Point memory a2,
G1Point memory b1,
G2Point memory b2,
uint256 pairingGas
) internal view returns (bool, bool) {
G1Point[2] memory p1 = [a1, b1];
G2Point[2] memory p2 = [a2, b2];
uint256[12] memory input;
for (uint256 i = 0; i < 2; i++) {
uint256 j = i * 6;
input[j + 0] = p1[i].X;
input[j + 1] = p1[i].Y;
input[j + 2] = p2[i].X[0];
input[j + 3] = p2[i].X[1];
input[j + 4] = p2[i].Y[0];
input[j + 5] = p2[i].Y[1];
}
uint256[1] memory out;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(pairingGas, 8, input, mul(12, 0x20), out, 0x20)
}
//Out is the output of the pairing precompile, either 0 or 1 based on whether the two pairings are equal.
//Success is true if the precompile actually goes through (aka all inputs are valid)
return (success, out[0] != 0);
}
/// @return hashedG1 the keccak256 hash of the G1 Point
/// @dev used for BLS signatures
function hashG1Point(
BN254.G1Point memory pk
) internal pure returns (bytes32 hashedG1) {
assembly {
mstore(0, mload(pk))
mstore(0x20, mload(add(0x20, pk)))
hashedG1 := keccak256(0, 0x40)
}
}
/// @return the keccak256 hash of the G2 Point
/// @dev used for BLS signatures
function hashG2Point(
BN254.G2Point memory pk
) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(pk.X[0], pk.X[1], pk.Y[0], pk.Y[1]));
}
/**
* @notice adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol
*/
function hashToG1(
bytes32 _x
) internal view returns (G1Point memory) {
uint256 beta = 0;
uint256 y = 0;
uint256 x = uint256(_x) % FP_MODULUS;
while (true) {
(beta, y) = findYFromX(x);
// y^2 == beta
if (beta == mulmod(y, y, FP_MODULUS)) {
return G1Point(x, y);
}
x = addmod(x, 1, FP_MODULUS);
}
return G1Point(0, 0);
}
/**
* Given X, find Y
*
* where y = sqrt(x^3 + b)
*
* Returns: (x^3 + b), y
*/
function findYFromX(
uint256 x
) internal view returns (uint256, uint256) {
// beta = (x^3 + b) % p
uint256 beta = addmod(mulmod(mulmod(x, x, FP_MODULUS), x, FP_MODULUS), 3, FP_MODULUS);
// y^2 = x^3 + b
// this acts like: y = sqrt(beta) = beta^((p+1) / 4)
uint256 y = expMod(beta, 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52, FP_MODULUS);
return (beta, y);
}
function expMod(uint256 _base, uint256 _exponent, uint256 _modulus) internal view returns (uint256 retval) {
bool success;
uint256[1] memory output;
uint256[6] memory input;
input[0] = 0x20; // baseLen = new(big.Int).SetBytes(getData(input, 0, 32))
input[1] = 0x20; // expLen = new(big.Int).SetBytes(getData(input, 32, 32))
input[2] = 0x20; // modLen = new(big.Int).SetBytes(getData(input, 64, 32))
input[3] = _base;
input[4] = _exponent;
input[5] = _modulus;
assembly {
success := staticcall(sub(gas(), 2000), 5, input, 0xc0, output, 0x20)
// Use "invalid" to make gas estimation work
switch success
case 0 { invalid() }
}
require(success, ExpModFailed());
return output[0];
}
}
"
},
"src/contracts/libraries/BN254SignatureVerifier.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import {BN254} from "./BN254.sol";
/**
* @title BN254SignatureVerifier
* @notice Library for BN254 signature verification
* @dev Provides unified signature verification with consistent gamma calculation and hash-to-G1 conversion
*/
library BN254SignatureVerifier {
using BN254 for BN254.G1Point;
/**
* @notice Core BN254 signature verification function with optional gas limiting
* @param msgHash The message hash that was signed
* @param signature The BLS signature to verify (G1 point)
* @param pubkeyG1 The G1 component of the public key
* @param pubkeyG2 The G2 component of the public key
* @param useGasLimit Whether to use gas-limited safe pairing
* @param pairingGas Gas limit for pairing (ignored if useGasLimit is false)
* @return success True if verification succeeded (always true if useGasLimit=false due to revert)
* @return pairingSuccessful True if pairing operation completed (only relevant when useGasLimit=true)
*/
function verifySignature(
bytes32 msgHash,
BN254.G1Point memory signature,
BN254.G1Point memory pubkeyG1,
BN254.G2Point memory pubkeyG2,
bool useGasLimit,
uint256 pairingGas
) internal view returns (bool success, bool pairingSuccessful) {
BN254.G1Point memory messagePoint = BN254.hashToG1(msgHash);
uint256 gamma = _calculateGamma(msgHash, pubkeyG1, pubkeyG2, signature);
// Calculate pairing inputs
BN254.G1Point memory leftG1 = signature.plus(pubkeyG1.scalar_mul(gamma));
BN254.G1Point memory rightG1 = messagePoint.plus(BN254.generatorG1().scalar_mul(gamma));
if (useGasLimit) {
// Use safe pairing with gas limit
(pairingSuccessful, success) =
BN254.safePairing(leftG1, BN254.negGeneratorG2(), rightG1, pubkeyG2, pairingGas);
} else {
success = BN254.pairing(leftG1, BN254.negGeneratorG2(), rightG1, pubkeyG2);
if (success) {
pairingSuccessful = true;
}
}
}
/**
* @notice Internal function to calculate gamma value for signature verification
* @param msgHash The message hash
* @param pubkeyG1 The G1 component of the public key
* @param pubkeyG2 The G2 component of the public key
* @param signature The signature point
* @return gamma The calculated gamma value
*/
function _calculateGamma(
bytes32 msgHash,
BN254.G1Point memory pubkeyG1,
BN254.G2Point memory pubkeyG2,
BN254.G1Point memory signature
) internal pure returns (uint256 gamma) {
gamma = uint256(
keccak256(
abi.encodePacked(
msgHash,
pubkeyG1.X,
pubkeyG1.Y,
pubkeyG2.X[0],
pubkeyG2.X[1],
pubkeyG2.Y[0],
pubkeyG2.Y[1],
signature.X,
signature.Y
)
)
) % BN254.FR_MODULUS;
}
}
"
},
"src/contracts/mixins/PermissionControllerMixin.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../interfaces/IPermissionController.sol";
abstract contract PermissionControllerMixin {
/// @dev Thrown when the caller is not allowed to call a function on behalf of an account.
error InvalidPermissions();
/// @notice Pointer to the permission controller contract.
IPermissionController public immutable permissionController;
constructor(
IPermissionController _permissionController
) {
permissionController = _permissionController;
}
/// @notice Checks if the caller (msg.sender) can call on behalf of an account.
modifier checkCanCall(
address account
) {
require(_checkCanCall(account), InvalidPermissions());
_;
}
/**
* @notice Checks if the caller is allowed to call a function on behalf of an account.
* @param account the account to check
* @dev `msg.sender` is the caller to check that can call the function on behalf of `account`.
* @dev Returns a bool, instead of reverting
*/
function _checkCanCall(
address account
) internal returns (bool) {
return permissionController.canCall(account, msg.sender, address(this), msg.sig);
}
}
"
},
"src/contracts/mixins/SignatureUtilsMixin.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
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
bytes32 constant EIP712_DOMAIN_TYPEHASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
/// @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. 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;
/// @notice Initializes the contract with a semantic version string.
/// @param _version The SemVer-formatted version string (e.g., "1.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.
/// 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
) SemVerMixin(_version) {}
/// EXTERNAL FUNCTIONS ///
/// @inheritdoc ISignatureUtilsMixin
function domainSeparator() public view virtual returns (bytes32) {
// forgefmt: disable-next-item
return
keccak256(
abi.encode(
EIP712_DOMAIN_TYPEHASH,
keccak256(bytes("EigenLayer")),
keccak256(bytes(_majorVersion())),
block.chainid,
address(this)
)
);
}
/// INTERNAL HELPERS ///
/// @notice Creates a digest that can be signed using EIP-712.
/// @dev Prepends the EIP-712 prefix ("\x19\x01") and domain separator to the input hash.
/// This follows the EIP-712 specification for creating structured data hashes.
/// See https://eips.ethereum.org/EIPS/eip-712#specification.
/// @param hash The hash of the typed data to be signed.
/// @return The complete digest that should be signed according to EIP-712.
function _calculateSignableDigest(
bytes32 hash
) internal view returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", domainSeparator(), hash));
}
/// @notice Validates a signature against a signer and digest, with an expiry timestamp.
/// @dev Reverts if the signature is invalid or expired. Uses EIP-1271 for smart contract signers.
/// For EOA signers, validates ECDSA signatures directly.
/// For contract signers, calls isValidSignature according to EIP-1271.
/// See https://eips.ethereum.org/EIPS/eip-1271#specification.
/// @param signer The address that should have signed the digest.
/// @param signableDigest The digest that was signed, created via _calculateSignableDigest.
/// @param signature The signature bytes to validate.
/// @param expiry The timestamp after which the signature is no longer valid.
function _checkIsValidSignatureNow(
address signer,
bytes32 signableDigest,
bytes memory signature,
uint256 expiry
) internal view {
// First, check if the signature has expired by comparing the expiry timestamp
// against the current block timestamp.
require(expiry >= block.timestamp, SignatureExpired());
// Next, verify that the signature is valid for the given signer and digest.
// For EOA signers, this performs standard ECDSA signature verification.
// For contract signers, this calls the EIP-1271 isValidSignature method.
require(signer.isValidSignatureNow(signableDigest, signature), InvalidSignature());
}
}
"
},
"src/contracts/interfaces/IPermissionController.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import "./ISemVerMixin.sol";
interface IPermissionControllerErrors {
/// @notice Thrown when a non-admin caller attempts to perform an admin-only action.
error NotAdmin();
/// @notice Thrown when attempting to remove an admin that does not exist.
error AdminNotSet();
/// @notice Thrown when attempting to set an appointee for a function that already has one.
error AppointeeAlreadySet();
/// @notice Thrown when attempting to interact with a non-existent appointee.
error AppointeeNotSet();
/// @notice Thrown when attempting to remove the last remaining admin.
error CannotHaveZeroAdmins();
/// @notice Thrown when attempting to set an admin that is already registered.
error AdminAlreadySet();
/// @notice Thrown when attempting to interact with an admin that is not in pending status.
error AdminNotPending();
/// @notice Thrown when attempting to add an admin that is already pending.
error AdminAlreadyPending();
}
interface IPermissionControllerEvents {
/// @notice Emitted when an appointee is set for an account to handle specific function calls.
event AppointeeSet(address indexed account, address indexed appointee, address target, bytes4 selector);
/// @notice Emitted when an appointee's permission to handle function calls for an account is revoked.
event AppointeeRemoved(address indexed account, address indexed appointee, address target, bytes4 selector);
/// @notice Emitted when an address is set as a pending admin for an account, requiring acceptance.
event PendingAdminAdded(address indexed account, address admin);
/// @notice Emitted when a pending admin status is removed for an account before acceptance.
event PendingAdminRemoved(address indexed account, address admin);
/// @notice Emitted when an address accepts and becomes an active admin for an account.
event AdminSet(address indexed account, address admin);
/// @notice Emitted when an admin's permissions are removed from an account.
event AdminRemoved(address indexed account, address admin);
}
interface IPermissionController is IPermissionControllerErrors, IPermissionControllerEvents, ISemVerMixin {
/**
* @notice Sets a pending admin for an account.
* @param account The account to set the pending admin for.
* @param admin The address to set as pending admin.
* @dev The pending admin must accept the role before becoming an active admin.
* @dev Multiple admins can be set for a single account.
*/
function addPendingAdmin(address account, address admin) external;
/**
* @notice Removes a pending admin from an account before they have accepted the role.
* @param account The account to remove the pending admin from.
* @param admin The pending admin address to remove.
* @dev Only an existing admin of the account can remove a pending admin.
*/
function removePendingAdmin(address account, address admin) external;
/**
* @notice Allows a pending admin to accept their admin role for an account.
* @param account The account to accept the admin role for.
* @dev Only addresses that were previously set as pending admins can accept the role.
*/
function acceptAdmin(
address account
) external;
/**
* @notice Removes an active admin from an account.
* @param account The account to remove the admin from.
* @param admin The admin address to remove.
* @dev Only an existing admin of the account can remove another admin.
* @dev Will revert if removing this admin would leave the account with zero admins.
*/
function removeAdmin(address account, address admin) external;
/**
* @notice Sets an appointee who can call specific functions on behalf of an account.
* @param account The account to set the appointee for.
* @param appointee The address to be given permission.
* @param target The contract address the appointee can interact with.
* @param selector The function selector the appointee can call.
* @dev Only an admin of the account can set appointees.
*/
function setAppointee(address account, address appointee, address target, bytes4 selector) external;
/**
* @notice Removes an appointee's permission to call a specific function.
* @param account The account to remove the appointee from.
* @param appointee The appointee address to remove.
* @param target The contract address to remove permissions for.
* @param selector The function selector to remove permissions for.
* @dev Only an admin of the account can remove appointees.
*/
function removeAppointee(address account, address appointee, address target, bytes4 selector) external;
/**
* @notice Checks if a given address is an admin of an account.
* @param account The account to check admin status for.
* @param caller The address to check.
* @dev If the account has no admins, returns true only if the caller is the account itself.
* @return Returns true if the caller is an admin, false otherwise.
*/
function isAdmin(address account, address caller) external view returns (bool);
/**
* @notice Checks if an address is currently a pending admin for an account.
* @param account The account to check pending admin status for.
* @param pendingAdmin The address to check.
* @return Returns true if the address is a pending admin, false otherwise.
*/
function isPendingAdmin(address account, address pendingAdmin) external view returns (bool);
/**
* @notice Retrieves all active admins for an account.
* @param account The account to get the admins for.
* @dev If the account has no admins, returns an array containing only the account address.
* @return An array of admin addresses.
*/
function getAdmins(
address account
) external view returns (address[] memory);
/**
* @notice Retrieves all pending admins for an account.
* @param account The account to get the pending admins for.
* @return An array of pending admin addresses.
*/
function getPendingAdmins(
address account
) external view returns (address[] memory);
/**
* @notice Checks if a caller has permission to call a specific function.
* @param account The account to check permissions for.
* @param caller The address attempting to make the call.
* @param target The contract address being called.
* @param selector The function selector being called.
* @dev Returns true if the caller is either an admin or an appointed caller.
* @dev Be mindful that upgrades to the contract may invalidate the appointee's permissions.
* This is only possible if a function's selector changes (e.g. if a function's parameters are modified).
* @return Returns true if the caller has permission, false otherwise.
*/
function canCall(address account, address caller, address target, bytes4 selector) external returns (bool);
/**
* @notice Retrieves all permissions granted to an appointee for a given account.
* @param account The account to check appointee permissions for.
* @param appointee The appointee address to check.
* @return Two arrays: target contract addresses and their corresponding function selectors.
*/
function getAppointeePermissions(
address account,
address appointee
) external returns (address[] memory, bytes4[] memory);
/**
* @notice Retrieves all appointees that can call a specific function for an account.
* @param account The account to get appointees for.
* @param target The contract address to check.
* @param selector The function selector to check.
* @dev Does not include admins in the returned list, even though they have calling permission.
* @return An array of appointee addresses.
*/
function getAppointees(address account, address target, bytes4 selector) external returns (address[] memory);
}
"
},
"src/contracts/interfaces/IAllocationManager.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;
import {OperatorSet} from "../libraries/OperatorSetLib.sol";
import "./IPauserRegistry.sol";
import "./IStrategy.sol";
import "./IAVSRegistrar.sol";
import "./ISemVerMixin.sol";
interface IAllocationManagerErrors {
/// Input Validation
/// @dev Thrown when `wadToSlash` is zero or greater than 1e18
error InvalidWadToSlash();
/// @dev Thrown when two array parameters have mismatching lengths.
error InputArrayLengthMismatch();
/// @dev Thrown when the AVSRegistrar is not correctly configured to prevent an AVSRegistrar contract
/// from being used with the wrong AVS
error InvalidAVSRegistrar();
/// @dev Thrown when an invalid strategy is provided.
error InvalidStrategy();
/// @dev Thrown when an invalid redistribution recipient is provided.
error InvalidRedistributionRecipient();
/// Caller
/// @dev Thrown when caller is not authorized to call a function.
error InvalidCaller();
/// Operator Status
/// @dev Thrown when an invalid operator is provided.
error InvalidOperator();
/// @dev Thrown when an invalid avs whose metadata is not registered is provided.
error NonexistentAVSMetadata();
/// @dev Thrown when an operator's allocation delay has yet to be set.
error UninitializedAllocationDelay();
/// @dev Thrown when attempting to slash an operator when they are not slashable.
error OperatorNotSlashable();
/// @dev Thrown when trying to add an operator to a set they are already a member of
error AlreadyMemberOfSet();
/// @dev Thrown when trying to slash/remove an operator from a set they are not a member of
error NotMemberOfSet();
/// Operator Set Status
/// @dev Thrown when an invalid operator set is provided.
error InvalidOperatorSet();
/// @dev Thrown when provided `strategies` are not in ascending order.
error StrategiesMustBeInAscendingOrder();
/// @dev Thrown when trying to add a strategy to an operator set that already contains it.
error StrategyAlreadyInOperatorSet();
/// @dev Thrown when a strategy is referenced that does not belong to an operator set.
error StrategyNotInOperatorSet();
/// Modifying Allocations
/// @dev Thrown when an operator attempts to set their allocation for an operatorSet to the same value
error SameMagnitude();
/// @dev Thrown when an allocation is attempted for a given operator when they have pending allocations or deallocations.
error ModificationAlreadyPending();
/// @dev Thrown when an allocation is attempted that exceeds a given operators total allocatable magnitude.
error InsufficientMagnitude();
}
interface IAllocationManagerTypes {
/**
* @notice Defines allocation information from a strategy to an operator set, for an operator
* @param currentMagnitude the current magnitude allocated from the strategy to the operator set
* @param pendingDiff a pending change in magnitude, if it exists (0 otherwise)
* @param effectBlock the block at which the pending magnitude diff will take effect
*/
struct Allocation {
uint64 currentMagnitude;
int128 pendingDiff;
uint32 effectBlock;
}
/**
* @notice Struct containing allocation delay metadata for a given operator.
* @param delay Current allocation delay
* @param isSet Whether the operator has initially set an allocation delay. Note that this could be false but the
* block.number >= effectBlock in which we consider their delay to be configured and active.
* @param pendingDelay The delay that will take effect after `effectBlock`
* @param effectBlock The block number after which a pending delay will take effect
*/
struct AllocationDelayInfo {
uint32 delay;
bool isSet;
uint32 pendingDelay;
uint32 effectBlock;
}
/**
* @notice Contains registration details for an operator pertaining to an operator set
* @param registered Whether the operator is currently registered for the operator set
* @param slashableUntil If the operator is not registered, they are still slashable until
* this block is reached.
*/
struct RegistrationStatus {
bool registered;
uint32 slashableUntil;
}
/**
* @notice Contains allocation info for a specific strategy
* @param maxMagnitude the maximum magnitude that can be allocated between all operator sets
* @param encumberedMagnitude the currently-allocated magnitude for the strategy
*/
struct StrategyInfo {
uint64 maxMagnitude;
uint64 encumberedMagnitude;
}
/**
* @notice Struct containing parameters to slashing
* @param operator the address to slash
* @param operatorSetId the ID of the operatorSet the operator is being slashed on behalf of
* @param strategies the set of strategies to slash
* @param wadsToSlash the parts in 1e18 to slash, this will be proportional to the operator's
* slashable stake allocation for the operatorSet
* @param description the description of the slashing provided by the AVS for legibility
*/
struct SlashingParams {
address operator;
uint32 operatorSetId;
IStrategy[] strategies;
uint256[] wadsToSlash;
string description;
}
/**
* @notice struct used to modify the allocation of slashable magnitude to an operator set
* @param operatorSet the operator set to modify the allocation for
* @param strategies the strategies to modify allocations for
* @param newMagnitudes the new magnitude to allocate for each strategy to this operator set
*/
struct AllocateParams {
OperatorSet operatorSet;
IStrategy[] strategies;
uint64[] newMagnitudes;
}
/**
* @notice Parameters used to register for an AVS's operator sets
* @param avs the AVS being registered for
* @param operatorSetIds the operator sets within the AVS to register for
* @param data extra data to be passed to the AVS to complete registration
*/
struct RegisterParams {
address avs;
uint32[] operatorSetIds;
bytes data;
}
/**
* @notice Parameters used to deregister from an AVS's operator sets
* @param operator the operator being deregistered
* @param avs the avs being deregistered from
* @param operatorSetIds the operator sets within the AVS being deregistered from
*/
struct DeregisterParams {
address operator;
address avs;
uint32[] operatorSetIds;
}
/**
* @notice Parameters used by an AVS to create new operator sets
* @param operatorSetId the id of the operator set to create
* @param strategies the strategies to add as slashable to the operator set
*/
struct CreateSetParams {
uint32 operatorSetId;
IStrategy[] strategies;
}
}
interface IAllocationManagerEvents is IAllocationManagerTypes {
/// @notice Emitted when operator updates their allocation delay.
event AllocationDelaySet(address operator, uint32 delay, uint32 effectBlock);
/// @notice Emitted when an operator's magnitude is updated for a given operatorSet and strategy
event AllocationUpdated(
address operator, OperatorSet operatorSet, IStrategy strategy, uint64 magnitude, uint32 effectBlock
);
/// @notice Emitted when operator's encumbered magnitude is updated for a given strategy
event EncumberedMagnitudeUpdated(address operator, IStrategy strategy, uint64 encumberedMagnitude);
/// @notice Emitted when an operator's max magnitude is updated for a given strategy
event MaxMagnitudeUpdated(address operator, IStrategy strategy, uint64 maxMagnitude);
/// @notice Emitted when an operator is slashed by an operator set for a strategy
/// `wadSlashed` is the proportion of the operator's total delegated stake that was slashed
event OperatorSlashed(
address operator, OperatorSet operatorSet, IStrategy[] strategies, uint256[] wadSlashed, string description
);
/// @notice Emitted when an AVS configures the address that will handle registration/deregistration
event AVSRegistrarSet(address avs, IAVSRegistrar registrar);
/// @notice Emitted when an AVS updates their metadata URI (Uniform Resource Identifier).
/// @dev The URI is never stored; it is simply emitted through an event for off-chain indexing.
event AVSMetadataURIUpdated(address indexed avs, string metadataURI);
/// @notice E
Submitted on: 2025-09-25 10:04:00
Comments
Log in to comment.
No comments yet.