mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
## Summary This PR removes the old merkle root-based rewards model and completes the migration to EigenLayer Rewards V2 distribution. The old model required operators to claim rewards by providing merkle proofs, while the new model uses `submitRewards` to send rewards directly to EigenLayer's `RewardsCoordinator`. ### Key Changes - **Smart Contracts**: Removed `RewardsRegistry`, `RewardsRegistryStorage`, `IRewardsRegistry`, and `SortedMerkleProof` contracts along with all merkle claim functions from `ServiceManagerBase` - **Substrate Pallets**: Removed merkle proof generation from `external-validators-rewards` pallet and deleted the entire `runtime-api` crate (no longer needed) - **Test Framework**: Removed all RewardsRegistry-related code from deployment scripts, CLI handlers, and TypeScript bindings - **Runtimes**: Cleaned up all three runtimes (testnet, stagenet, mainnet) to remove runtime API implementations and unused imports ### Files Removed **Contracts:** - `contracts/src/middleware/RewardsRegistry.sol` - `contracts/src/middleware/RewardsRegistryStorage.sol` - `contracts/src/interfaces/IRewardsRegistry.sol` - `contracts/src/libraries/SortedMerkleProof.sol` - `contracts/test/RewardsRegistry.t.sol` - `contracts/test/ServiceManagerRewardsRegistry.t.sol` **Substrate:** - `operator/pallets/external-validators-rewards/runtime-api/` (entire crate) **Test Framework:** - `test/suites/rewards-message.test.ts` ### Files Modified **Contracts:** - `ServiceManagerBase.sol` - Removed merkle claim functions - `ServiceManagerBaseStorage.sol` - Removed `operatorSetToRewardsRegistry` mapping - `IServiceManager.sol` - Removed interface members **Substrate:** - `external-validators-rewards` pallet - Removed merkle proof generation, simplified `EraRewardsUtils` struct - All runtime configs - Removed `ExternalValidatorsRewardsApi` implementations **Test Framework:** - Updated deployment scripts, CLI handlers, relayer configs, and TypeScript bindings ### Stats ``` 50 files changed, 966 insertions(+), 4453 deletions(-) ``` ## Test plan - [x] All Rust tests pass (`cargo test`) - [x] All contract tests pass (`forge test`) - [x] TypeScript type checking passes (`bun typecheck`) - [x] Contracts build successfully (`forge build`) - [x] Operator builds successfully (`cargo build --release --features fast-runtime`) - [ ] E2E tests pass (`bun test:e2e`)
116 lines
4.3 KiB
TypeScript
116 lines
4.3 KiB
TypeScript
import * as generated from "contract-bindings";
|
|
import invariant from "tiny-invariant";
|
|
import { type Abi, erc20Abi, getContract, isAddress } from "viem";
|
|
import { z } from "zod";
|
|
import { logger } from "./logger";
|
|
import { createDefaultClient, type ViemClientInterface } from "./viem";
|
|
|
|
const ethAddressRegex = /^0x[a-fA-F0-9]{40}$/;
|
|
const ethAddress = z.string().regex(ethAddressRegex, "Invalid Ethereum address");
|
|
const ethAddressCustom = z.custom<`0x${string}`>(
|
|
(val) => typeof val === "string" && ethAddressRegex.test(val),
|
|
{ message: "Invalid Ethereum address" }
|
|
);
|
|
const DeployedStrategySchema = z.object({
|
|
address: ethAddress,
|
|
underlyingToken: ethAddress,
|
|
tokenCreator: ethAddress
|
|
});
|
|
|
|
const DeploymentsSchema = z.object({
|
|
network: z.string(),
|
|
BeefyClient: ethAddressCustom,
|
|
AgentExecutor: ethAddressCustom,
|
|
Gateway: ethAddressCustom,
|
|
ServiceManager: ethAddressCustom,
|
|
ServiceManagerImplementation: ethAddressCustom,
|
|
DelegationManager: ethAddressCustom,
|
|
StrategyManager: ethAddressCustom,
|
|
AVSDirectory: ethAddressCustom,
|
|
EigenPodManager: ethAddressCustom.optional(),
|
|
EigenPodBeacon: ethAddressCustom.optional(),
|
|
RewardsCoordinator: ethAddressCustom,
|
|
AllocationManager: ethAddressCustom,
|
|
PermissionController: ethAddressCustom,
|
|
ETHPOSDeposit: ethAddressCustom.optional(),
|
|
BaseStrategyImplementation: ethAddressCustom.optional(),
|
|
DeployedStrategies: z.array(DeployedStrategySchema).optional()
|
|
});
|
|
|
|
export type Deployments = z.infer<typeof DeploymentsSchema>;
|
|
|
|
export const parseDeploymentsFile = async (network = "anvil"): Promise<Deployments> => {
|
|
const deploymentsPath = `../contracts/deployments/${network}.json`;
|
|
const deploymentsFile = Bun.file(deploymentsPath);
|
|
if (!(await deploymentsFile.exists())) {
|
|
logger.error(`File ${deploymentsPath} does not exist`);
|
|
throw new Error(`Error reading ${network} deployments file`);
|
|
}
|
|
const deploymentsJson = await deploymentsFile.json();
|
|
logger.info(`Deployments: ${JSON.stringify(deploymentsJson, null, 2)}`);
|
|
try {
|
|
const parsedDeployments = DeploymentsSchema.parse(deploymentsJson);
|
|
logger.debug(`Successfully parsed ${network} deployments file.`);
|
|
return parsedDeployments;
|
|
} catch (error) {
|
|
logger.error(`Failed to parse ${network} deployments file:`, error);
|
|
throw new Error(`Invalid ${network} deployments file format`);
|
|
}
|
|
};
|
|
|
|
// Add to this if we add any new contracts
|
|
const abiMap = {
|
|
BeefyClient: generated.beefyClientAbi,
|
|
AgentExecutor: generated.agentExecutorAbi,
|
|
Gateway: generated.gatewayAbi,
|
|
ServiceManager: generated.dataHavenServiceManagerAbi,
|
|
ServiceManagerImplementation: generated.dataHavenServiceManagerAbi,
|
|
DelegationManager: generated.delegationManagerAbi,
|
|
StrategyManager: generated.strategyManagerAbi,
|
|
AVSDirectory: generated.avsDirectoryAbi,
|
|
EigenPodManager: generated.eigenPodManagerAbi,
|
|
EigenPodBeacon: generated.eigenPodAbi,
|
|
RewardsCoordinator: generated.rewardsCoordinatorAbi,
|
|
AllocationManager: generated.allocationManagerAbi,
|
|
PermissionController: generated.permissionControllerAbi,
|
|
ETHPOSDeposit: generated.iethposDepositAbi,
|
|
BaseStrategyImplementation: generated.strategyBaseTvlLimitsAbi,
|
|
DeployedStrategies: erc20Abi
|
|
} as const satisfies Record<keyof Omit<Deployments, "network">, Abi>;
|
|
|
|
type ContractName = keyof typeof abiMap;
|
|
type AbiFor<C extends ContractName> = (typeof abiMap)[C];
|
|
export type ContractInstance<C extends ContractName> = Awaited<
|
|
ReturnType<typeof getContractInstance<C>>
|
|
>;
|
|
|
|
// TODO: make this work with DeployedStrategies
|
|
export const getContractInstance = async <C extends ContractName>(
|
|
contract: C,
|
|
viemClient?: ViemClientInterface,
|
|
network = "anvil"
|
|
) => {
|
|
const deployments = await parseDeploymentsFile(network);
|
|
const contractAddress = deployments[contract];
|
|
logger.debug(`Contract ${contract} deployed to ${contractAddress}`);
|
|
|
|
const client = viemClient ?? (await createDefaultClient());
|
|
invariant(
|
|
typeof contractAddress === "string" && isAddress(contractAddress),
|
|
`Contract address for ${contract} is not a valid address`
|
|
);
|
|
|
|
const abi: AbiFor<C> = abiMap[contract];
|
|
invariant(abi, `ABI for contract ${contract} not found`);
|
|
|
|
return getContract({
|
|
address: contractAddress,
|
|
abi,
|
|
client
|
|
});
|
|
};
|
|
|
|
export const getAbi = async (contract: string) => {
|
|
const contractInstance = await getContractInstance(contract as ContractName);
|
|
return contractInstance.abi;
|
|
};
|