Skip to content

Commit

Permalink
feat: service manager payments (#242)
Browse files Browse the repository at this point in the history
* feat: service manager payments

* test: unit tests

* feat: refactor serviceManager interfaces

* chore: requested changes
  • Loading branch information
8sunyuan authored Apr 23, 2024
1 parent 59ebd32 commit e18c19e
Show file tree
Hide file tree
Showing 14 changed files with 869 additions and 52 deletions.
2 changes: 1 addition & 1 deletion lib/eigenlayer-contracts
Submodule eigenlayer-contracts updated 29 files
+38 −0 .github/workflows/run-deploy-scripts.yml
+92 −45 README.md
+0 −2 docs/README.md
+2 −2 docs/core/DelegationManager.md
+2 −2 docs/core/StrategyManager.md
+71 −0 script/admin/mainnet/Mainnet_Unpause_Deposits.s.sol
+0 −1 script/configs/devnet/M2_deploy_from_scratch.anvil.config.json
+57 −0 script/configs/holesky/Deploy_PaymentCoordinator.holesky.config.json
+17 −20 script/deploy/devnet/M2_Deploy_From_Scratch.s.sol
+44 −0 script/deploy/holesky/Deploy_Preprod_PaymentCoordinator.s.sol
+74 −0 script/deploy/holesky/Deploy_Test_PaymentCoordinator.s.sol
+37 −0 script/output/holesky/Deploy_PaymentCoordinator.holesky.config.json
+37 −0 script/output/holesky/Deploy_PaymentCoordinator_Preprod.holesky.config.json
+61 −0 script/output/holesky/M2_deploy_from_scratch.output.json
+39 −0 script/output/holesky/M2_deploy_preprod.output.json
+2 −1 script/output/mainnet/M1_deployment_mainnet_2023_6_9.json
+156 −29 script/utils/ExistingDeploymentParser.sol
+474 −0 src/contracts/core/PaymentCoordinator.sol
+108 −0 src/contracts/core/PaymentCoordinatorStorage.sol
+3 −0 src/contracts/interfaces/IDelegationManager.sol
+288 −0 src/contracts/interfaces/IPaymentCoordinator.sol
+3 −0 src/contracts/interfaces/IStrategyManager.sol
+66 −0 src/test/events/IPaymentCoordinatorEvents.sol
+3 −0 src/test/mocks/DelegationManagerMock.sol
+7 −0 src/test/mocks/StrategyManagerMock.sol
+54 −0 src/test/test-data/paymentCoordinator/processClaimProofs_Root1.json
+54 −0 src/test/test-data/paymentCoordinator/processClaimProofs_Root2.json
+54 −0 src/test/test-data/paymentCoordinator/processClaimProofs_Root3.json
+1,637 −0 src/test/unit/PaymentCoordinatorUnit.t.sol
35 changes: 32 additions & 3 deletions src/ServiceManagerBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
pragma solidity ^0.8.12;

import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";

import {BitmapUtils} from "./libraries/BitmapUtils.sol";
import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol";
import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol";
import {IPaymentCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IPaymentCoordinator.sol";

import {IServiceManager} from "./interfaces/IServiceManager.sol";
import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol";
import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol";
import {BitmapUtils} from "./libraries/BitmapUtils.sol";

/**
* @title Minimal implementation of a ServiceManager-type contract.
Expand All @@ -19,9 +19,10 @@ import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol";
abstract contract ServiceManagerBase is IServiceManager, OwnableUpgradeable {
using BitmapUtils for *;

IAVSDirectory internal immutable _avsDirectory;
IPaymentCoordinator internal immutable _paymentCoordinator;
IRegistryCoordinator internal immutable _registryCoordinator;
IStakeRegistry internal immutable _stakeRegistry;
IAVSDirectory internal immutable _avsDirectory;

/// @notice when applied to a function, only allows the RegistryCoordinator to call it
modifier onlyRegistryCoordinator() {
Expand All @@ -35,10 +36,12 @@ abstract contract ServiceManagerBase is IServiceManager, OwnableUpgradeable {
/// @notice Sets the (immutable) `_registryCoordinator` address
constructor(
IAVSDirectory __avsDirectory,
IPaymentCoordinator ___paymentCoordinator,
IRegistryCoordinator __registryCoordinator,
IStakeRegistry __stakeRegistry
) {
_avsDirectory = __avsDirectory;
_paymentCoordinator = ___paymentCoordinator;
_registryCoordinator = __registryCoordinator;
_stakeRegistry = __stakeRegistry;
_disableInitializers();
Expand All @@ -57,6 +60,32 @@ abstract contract ServiceManagerBase is IServiceManager, OwnableUpgradeable {
_avsDirectory.updateAVSMetadataURI(_metadataURI);
}

/**
* @notice Creates a new range payment on behalf of an AVS, to be split amongst the
* set of stakers delegated to operators who are registered to the `avs`.
* Note that the owner calling this function must have approved the tokens to be transferred to the ServiceManager
* and of course has the required balances.
* @param rangePayments The range payments being created
* @dev Expected to be called by the ServiceManager of the AVS on behalf of which the payment is being made
* @dev The duration of the `rangePayment` cannot exceed `paymentCoordinator.MAX_PAYMENT_DURATION()`
* @dev The tokens are sent to the `PaymentCoordinator` contract
* @dev Strategies must be in ascending order of addresses to check for duplicates
* @dev This function will revert if the `rangePayment` is malformed,
* e.g. if the `strategies` and `weights` arrays are of non-equal lengths
*/
function payForRange(
IPaymentCoordinator.RangePayment[] calldata rangePayments
) public virtual onlyOwner {
for (uint256 i = 0; i < rangePayments.length; ++i) {
// transfer token to ServiceManager and approve PaymentCoordinator to transfer again
// in payForRange() call
rangePayments[i].token.transferFrom(msg.sender, address(this), rangePayments[i].amount);
rangePayments[i].token.approve(address(_paymentCoordinator), rangePayments[i].amount);
}

_paymentCoordinator.payForRange(rangePayments);
}

/**
* @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator registration with the AVS
* @param operator The address of the operator to register.
Expand Down
6 changes: 3 additions & 3 deletions src/ServiceManagerRouter.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.12;

import {IServiceManager} from "./interfaces/IServiceManager.sol";
import {IServiceManagerUI} from "./interfaces/IServiceManagerUI.sol";

/**
* @title Contract that proxies calls to a ServiceManager contract.
Expand All @@ -20,7 +20,7 @@ contract ServiceManagerRouter {
*/
function getRestakeableStrategies(address serviceManager) external view returns (address[] memory) {
bytes memory data = abi.encodeWithSelector(
IServiceManager.getRestakeableStrategies.selector
IServiceManagerUI.getRestakeableStrategies.selector
);
return _makeCall(serviceManager, data);
}
Expand All @@ -32,7 +32,7 @@ contract ServiceManagerRouter {
*/
function getOperatorRestakedStrategies(address serviceManager, address operator) external view returns (address[] memory) {
bytes memory data = abi.encodeWithSelector(
IServiceManager.getOperatorRestakedStrategies.selector,
IServiceManagerUI.getOperatorRestakedStrategies.selector,
operator
);
return _makeCall(serviceManager, data);
Expand Down
57 changes: 15 additions & 42 deletions src/interfaces/IServiceManager.sol
Original file line number Diff line number Diff line change
@@ -1,53 +1,26 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;

import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol";
import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";
import {IPaymentCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IPaymentCoordinator.sol";
import {IServiceManagerUI} from "./IServiceManagerUI.sol";

/**
* @title Minimal interface for a ServiceManager-type contract that forms the single point for an AVS to push updates to EigenLayer
* @author Layr Labs, Inc.
*/
interface IServiceManager {
interface IServiceManager is IServiceManagerUI {
/**
* @notice Updates the metadata URI for the AVS
* @param _metadataURI is the metadata URI for the AVS
* @notice Creates a new range payment on behalf of an AVS, to be split amongst the
* set of stakers delegated to operators who are registered to the `avs`.
* Note that the owner calling this function must have approved the tokens to be transferred to the ServiceManager
* and of course has the required balances.
* @param rangePayments The range payments being created
* @dev Expected to be called by the ServiceManager of the AVS on behalf of which the payment is being made
* @dev The duration of the `rangePayment` cannot exceed `paymentCoordinator.MAX_PAYMENT_DURATION()`
* @dev The tokens are sent to the `PaymentCoordinator` contract
* @dev Strategies must be in ascending order of addresses to check for duplicates
* @dev This function will revert if the `rangePayment` is malformed,
* e.g. if the `strategies` and `weights` arrays are of non-equal lengths
*/
function updateAVSMetadataURI(string memory _metadataURI) external;

/**
* @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator registration with the AVS
* @param operator The address of the operator to register.
* @param operatorSignature The signature, salt, and expiry of the operator's signature.
*/
function registerOperatorToAVS(
address operator,
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
) external;

/**
* @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS
* @param operator The address of the operator to deregister.
*/
function deregisterOperatorFromAVS(address operator) external;

/**
* @notice Returns the list of strategies that the operator has potentially restaked on the AVS
* @param operator The address of the operator to get restaked strategies for
* @dev This function is intended to be called off-chain
* @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness
* of each element in the returned array. The off-chain service should do that validation separately
*/
function getOperatorRestakedStrategies(address operator) external view returns (address[] memory);

/**
* @notice Returns the list of strategies that the AVS supports for restaking
* @dev This function is intended to be called off-chain
* @dev No guarantee is made on uniqueness of each element in the returned array.
* The off-chain service should do that validation separately
*/
function getRestakeableStrategies() external view returns (address[] memory);

/// @notice Returns the EigenLayer AVSDirectory contract.
function avsDirectory() external view returns (address);
function payForRange(IPaymentCoordinator.RangePayment[] calldata rangePayments) external;
}
62 changes: 62 additions & 0 deletions src/interfaces/IServiceManagerUI.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;

import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol";
import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";

/**
* @title Minimal interface for a ServiceManager-type contract that AVS ServiceManager contracts must implement
* for eigenlabs to be able to index their data on the AVS marketplace frontend.
* @author Layr Labs, Inc.
*/
interface IServiceManagerUI {
/**
* Metadata should follow the format outlined by this example.
{
"name": "EigenLabs AVS 1",
"website": "https://www.eigenlayer.xyz/",
"description": "This is my 1st AVS",
"logo": "https://holesky-operator-metadata.s3.amazonaws.com/eigenlayer.png",
"twitter": "https://twitter.com/eigenlayer"
}
* @notice Updates the metadata URI for the AVS
* @param _metadataURI is the metadata URI for the AVS
*/
function updateAVSMetadataURI(string memory _metadataURI) external;

/**
* @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator registration with the AVS
* @param operator The address of the operator to register.
* @param operatorSignature The signature, salt, and expiry of the operator's signature.
*/
function registerOperatorToAVS(
address operator,
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
) external;

/**
* @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS
* @param operator The address of the operator to deregister.
*/
function deregisterOperatorFromAVS(address operator) external;

/**
* @notice Returns the list of strategies that the operator has potentially restaked on the AVS
* @param operator The address of the operator to get restaked strategies for
* @dev This function is intended to be called off-chain
* @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness
* of each element in the returned array. The off-chain service should do that validation separately
*/
function getOperatorRestakedStrategies(address operator) external view returns (address[] memory);

/**
* @notice Returns the list of strategies that the AVS supports for restaking
* @dev This function is intended to be called off-chain
* @dev No guarantee is made on uniqueness of each element in the returned array.
* The off-chain service should do that validation separately
*/
function getRestakeableStrategies() external view returns (address[] memory);

/// @notice Returns the EigenLayer AVSDirectory contract.
function avsDirectory() external view returns (address);
}
66 changes: 66 additions & 0 deletions test/events/IServiceManagerBaseEvents.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.12;

import "eigenlayer-contracts/src/contracts/interfaces/IPaymentCoordinator.sol";

interface IServiceManagerBaseEvents {
/// PaymentCoordinator EVENTS ///

/// @notice emitted when an AVS creates a valid RangePayment
event RangePaymentCreated(
address indexed avs,
uint256 indexed paymentNonce,
bytes32 indexed rangePaymentHash,
IPaymentCoordinator.RangePayment rangePayment
);
/// @notice emitted when a valid RangePayment is created for all stakers by a valid submitter
event RangePaymentForAllCreated(
address indexed submitter,
uint256 indexed paymentNonce,
bytes32 indexed rangePaymentHash,
IPaymentCoordinator.RangePayment rangePayment
);
/// @notice paymentUpdater is responsible for submiting DistributionRoots, only owner can set paymentUpdater
event PaymentUpdaterSet(address indexed oldPaymentUpdater, address indexed newPaymentUpdater);
event PayAllForRangeSubmitterSet(
address indexed payAllForRangeSubmitter,
bool indexed oldValue,
bool indexed newValue
);
event ActivationDelaySet(uint32 oldActivationDelay, uint32 newActivationDelay);
event CalculationIntervalSecondsSet(uint32 oldCalculationIntervalSeconds, uint32 newCalculationIntervalSeconds);
event GlobalCommissionBipsSet(uint16 oldGlobalCommissionBips, uint16 newGlobalCommissionBips);
event ClaimerForSet(address indexed earner, address indexed oldClaimer, address indexed claimer);
/// @notice rootIndex is the specific array index of the newly created root in the storage array
event DistributionRootSubmitted(
uint32 indexed rootIndex,
bytes32 indexed root,
uint32 paymentCalculationEndTimestamp,
uint32 activatedAt
);
/// @notice root is one of the submitted distribution roots that was claimed against
event PaymentClaimed(
bytes32 root,
address indexed earner,
address indexed claimer,
IERC20 indexed token,
uint256 claimedAmount
);



/// TOKEN EVENTS FOR TESTING ///
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);

/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
5 changes: 5 additions & 0 deletions test/integration/CoreRegistration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { AVSDirectory } from "eigenlayer-contracts/src/contracts/core/AVSDirecto
import { IAVSDirectory } from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol";
import { DelegationManager } from "eigenlayer-contracts/src/contracts/core/DelegationManager.sol";
import { IDelegationManager } from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";
import { PaymentCoordinator } from "eigenlayer-contracts/src/contracts/core/PaymentCoordinator.sol";
import { IPaymentCoordinator } from "eigenlayer-contracts/src/contracts/interfaces/IPaymentCoordinator.sol";

contract Test_CoreRegistration is MockAVSDeployer {
// Contracts
Expand Down Expand Up @@ -62,10 +64,13 @@ contract Test_CoreRegistration is MockAVSDeployer {
)
);

// Deploy Mock PaymentCoordinator
paymentCoordinatorMock = new PaymentCoordinatorMock();

// Deploy New ServiceManager & RegistryCoordinator implementations
serviceManagerImplementation = new ServiceManagerMock(
avsDirectory,
paymentCoordinatorMock,
registryCoordinator,
stakeRegistry
);
Expand Down
Loading

0 comments on commit e18c19e

Please sign in to comment.