mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-23 17:28:23 +00:00
fix: add era replay guard for rewards submissions (#477)
## Summary - guard `DataHavenServiceManager.submitRewards` by `(startTimestamp, duration, token)` so each reward window can only be submitted once per token - expose the replay-guard state and error in the interface, add Foundry coverage, wire the missing runtime `std` features, and regenerate the Wagmi/storage/state-diff artifacts - fix the local slash E2E path by aligning the `anvil` Snowbridge `messageOrigin` with `stagenet-local`, refreshing the tracked anvil deployment metadata, and waiting for `ServiceManager.SlashingComplete` ## Testing - `cargo fmt --all -- --check` - `forge test --match-contract RewardsSubmitterTest` - `forge test --match-contract StorageLayoutTest -vvv` - `./scripts/check-storage-layout.sh` - `./scripts/check-storage-layout-negative.sh` - `bun ./scripts/check-generated-state.ts` - `bun generate:wagmi` - `bun test ./e2e/suites/slash.test.ts --timeout 1200000 --test-name-pattern "verify we have the agent origin set|Activate slashing|use sudo to slash operator"` ## Notes - Slash E2E verification reran the previously failing sudo slash path; the long liveness scenario was not rerun end to end.
This commit is contained in:
parent
91bab694d8
commit
edcb13dbbc
13 changed files with 743 additions and 569 deletions
|
|
@ -35,7 +35,7 @@
|
|||
"randaoCommitExpiration": 24,
|
||||
"minNumRequiredSignatures": 2,
|
||||
"startBlock": 1,
|
||||
"messageOrigin": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"messageOrigin": "0x56490bd3f367447bfaf57bb18e7a45e1b2db7d538fe42098e87d2aa106c6afdd",
|
||||
"initialValidatorSetId": 0,
|
||||
"initialValidatorHashes": [
|
||||
"0xaeb47a269393297f4b0a3c9c9cfd00c7a4195255274cf39d83dabc2fcc9ff3d7",
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"Agent": "0xac06641381166cf085281c45292147f833C622d7","AgentOrigin": "0x0000000000000000000000000000000000000000000000000000000000000000"}
|
||||
{"Agent": "0xac06641381166cf085281c45292147f833C622d7","AgentOrigin": "0x56490bd3f367447bfaf57bb18e7a45e1b2db7d538fe42098e87d2aa106c6afdd"}
|
||||
|
|
@ -1 +1 @@
|
|||
{"RewardsAgent": "0xac06641381166cf085281c45292147f833C622d7","AgentOrigin": "0x0000000000000000000000000000000000000000000000000000000000000000"}
|
||||
{"RewardsAgent": "0xac06641381166cf085281c45292147f833C622d7","AgentOrigin": "0x56490bd3f367447bfaf57bb18e7a45e1b2db7d538fe42098e87d2aa106c6afdd"}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0d48ae80f212e436db23a1ba4345bc354b10b072
|
||||
964f27a76c22d4dfdba46a0289eb53b94cbbeb2e
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -83,9 +83,12 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
|
|||
/// `contracts/deployments/<chain>.json`.
|
||||
string private _version;
|
||||
|
||||
/// @notice Tracks whether rewards have already been submitted for a reward window and token.
|
||||
mapping(uint32 => mapping(uint32 => mapping(address => bool))) public rewardsSubmittedForWindow;
|
||||
|
||||
/// @notice Storage gap for upgradeability (must be at end of state variables)
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
uint256[42] private __GAP;
|
||||
uint256[41] private __GAP;
|
||||
|
||||
// ============ Modifiers ============
|
||||
|
||||
|
|
@ -565,6 +568,15 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
|
|||
translatedSubmission.operatorRewards = trimmed;
|
||||
}
|
||||
|
||||
address token = address(submission.token);
|
||||
uint32 startTimestamp = submission.startTimestamp;
|
||||
uint32 duration = submission.duration;
|
||||
require(
|
||||
!rewardsSubmittedForWindow[startTimestamp][duration][token],
|
||||
RewardsAlreadySubmittedForWindow(startTimestamp, duration, token)
|
||||
);
|
||||
rewardsSubmittedForWindow[startTimestamp][duration][token] = true;
|
||||
|
||||
submission.token.safeIncreaseAllowance(address(_REWARDS_COORDINATOR), totalAmount);
|
||||
|
||||
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[] memory submissions =
|
||||
|
|
|
|||
|
|
@ -57,6 +57,9 @@ interface IDataHavenServiceManagerErrors {
|
|||
|
||||
/// @notice Thrown when the caller is not the ProxyAdmin
|
||||
error NotProxyAdmin();
|
||||
|
||||
/// @notice Thrown when rewards for a reward window and token have already been submitted
|
||||
error RewardsAlreadySubmittedForWindow(uint32 startTimestamp, uint32 duration, address token);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -186,6 +189,18 @@ interface IDataHavenServiceManager is
|
|||
address solochainAddress
|
||||
) external view returns (address);
|
||||
|
||||
/**
|
||||
* @notice Returns whether rewards have already been submitted for a reward window and token
|
||||
* @param startTimestamp The reward window start timestamp
|
||||
* @param duration The reward window duration in seconds
|
||||
* @param token The reward token address
|
||||
*/
|
||||
function rewardsSubmittedForWindow(
|
||||
uint32 startTimestamp,
|
||||
uint32 duration,
|
||||
address token
|
||||
) external view returns (bool);
|
||||
|
||||
/**
|
||||
* @notice Initializes the DataHaven Service Manager
|
||||
* @param initialOwner Address of the initial owner (AVS owner)
|
||||
|
|
@ -339,6 +354,7 @@ interface IDataHavenServiceManager is
|
|||
* @dev Strategies must be sorted in ascending order by address
|
||||
* @dev Operators must be sorted in ascending order by address
|
||||
* @dev Token must be pre-approved or held by the ServiceManager
|
||||
* @dev Only one submission is allowed per reward window and token
|
||||
*/
|
||||
function submitRewards(
|
||||
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission calldata submission
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"storage": [
|
||||
{
|
||||
"astId": 138,
|
||||
"astId": 152,
|
||||
"contract": "src/DataHavenServiceManager.sol:DataHavenServiceManager",
|
||||
"label": "_initialized",
|
||||
"offset": 0,
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
"type": "t_uint8"
|
||||
},
|
||||
{
|
||||
"astId": 141,
|
||||
"astId": 155,
|
||||
"contract": "src/DataHavenServiceManager.sol:DataHavenServiceManager",
|
||||
"label": "_initializing",
|
||||
"offset": 1,
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
"type": "t_bool"
|
||||
},
|
||||
{
|
||||
"astId": 671,
|
||||
"astId": 769,
|
||||
"contract": "src/DataHavenServiceManager.sol:DataHavenServiceManager",
|
||||
"label": "__gap",
|
||||
"offset": 0,
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
"type": "t_array(t_uint256)49_storage"
|
||||
},
|
||||
{
|
||||
"astId": 23887,
|
||||
"astId": 103284,
|
||||
"contract": "src/DataHavenServiceManager.sol:DataHavenServiceManager",
|
||||
"label": "snowbridgeInitiator",
|
||||
"offset": 0,
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
"type": "t_address"
|
||||
},
|
||||
{
|
||||
"astId": 23892,
|
||||
"astId": 103289,
|
||||
"contract": "src/DataHavenServiceManager.sol:DataHavenServiceManager",
|
||||
"label": "validatorsAllowlist",
|
||||
"offset": 0,
|
||||
|
|
@ -57,15 +57,15 @@
|
|||
"type": "t_mapping(t_address,t_bool)"
|
||||
},
|
||||
{
|
||||
"astId": 23896,
|
||||
"astId": 103293,
|
||||
"contract": "src/DataHavenServiceManager.sol:DataHavenServiceManager",
|
||||
"label": "_snowbridgeGateway",
|
||||
"offset": 0,
|
||||
"slot": "103",
|
||||
"type": "t_contract(IGatewayV2)23591"
|
||||
"type": "t_contract(IGatewayV2)95551"
|
||||
},
|
||||
{
|
||||
"astId": 23901,
|
||||
"astId": 103298,
|
||||
"contract": "src/DataHavenServiceManager.sol:DataHavenServiceManager",
|
||||
"label": "validatorEthAddressToSolochainAddress",
|
||||
"offset": 0,
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
"type": "t_mapping(t_address,t_address)"
|
||||
},
|
||||
{
|
||||
"astId": 23905,
|
||||
"astId": 103302,
|
||||
"contract": "src/DataHavenServiceManager.sol:DataHavenServiceManager",
|
||||
"label": "validatorSolochainAddressToEthAddress",
|
||||
"offset": 0,
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
"type": "t_mapping(t_address,t_address)"
|
||||
},
|
||||
{
|
||||
"astId": 23908,
|
||||
"astId": 103305,
|
||||
"contract": "src/DataHavenServiceManager.sol:DataHavenServiceManager",
|
||||
"label": "validatorSetSubmitter",
|
||||
"offset": 0,
|
||||
|
|
@ -89,15 +89,15 @@
|
|||
"type": "t_address"
|
||||
},
|
||||
{
|
||||
"astId": 23914,
|
||||
"astId": 103311,
|
||||
"contract": "src/DataHavenServiceManager.sol:DataHavenServiceManager",
|
||||
"label": "strategiesAndMultipliers",
|
||||
"offset": 0,
|
||||
"slot": "107",
|
||||
"type": "t_mapping(t_contract(IStrategy)7471,t_uint96)"
|
||||
"type": "t_mapping(t_contract(IStrategy)26468,t_uint96)"
|
||||
},
|
||||
{
|
||||
"astId": 23917,
|
||||
"astId": 103314,
|
||||
"contract": "src/DataHavenServiceManager.sol:DataHavenServiceManager",
|
||||
"label": "_version",
|
||||
"offset": 0,
|
||||
|
|
@ -105,12 +105,20 @@
|
|||
"type": "t_string_storage"
|
||||
},
|
||||
{
|
||||
"astId": 23922,
|
||||
"astId": 103323,
|
||||
"contract": "src/DataHavenServiceManager.sol:DataHavenServiceManager",
|
||||
"label": "rewardsSubmittedForWindow",
|
||||
"offset": 0,
|
||||
"slot": "109",
|
||||
"type": "t_mapping(t_uint32,t_mapping(t_uint32,t_mapping(t_address,t_bool)))"
|
||||
},
|
||||
{
|
||||
"astId": 103328,
|
||||
"contract": "src/DataHavenServiceManager.sol:DataHavenServiceManager",
|
||||
"label": "__GAP",
|
||||
"offset": 0,
|
||||
"slot": "109",
|
||||
"type": "t_array(t_uint256)42_storage"
|
||||
"slot": "110",
|
||||
"type": "t_array(t_uint256)41_storage"
|
||||
}
|
||||
],
|
||||
"types": {
|
||||
|
|
@ -119,10 +127,10 @@
|
|||
"label": "address",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_array(t_uint256)42_storage": {
|
||||
"t_array(t_uint256)41_storage": {
|
||||
"encoding": "inplace",
|
||||
"label": "uint256[42]",
|
||||
"numberOfBytes": "1344",
|
||||
"label": "uint256[41]",
|
||||
"numberOfBytes": "1312",
|
||||
"base": "t_uint256"
|
||||
},
|
||||
"t_array(t_uint256)49_storage": {
|
||||
|
|
@ -142,12 +150,12 @@
|
|||
"label": "bool",
|
||||
"numberOfBytes": "1"
|
||||
},
|
||||
"t_contract(IGatewayV2)23591": {
|
||||
"t_contract(IGatewayV2)95551": {
|
||||
"encoding": "inplace",
|
||||
"label": "contract IGatewayV2",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_contract(IStrategy)7471": {
|
||||
"t_contract(IStrategy)26468": {
|
||||
"encoding": "inplace",
|
||||
"label": "contract IStrategy",
|
||||
"numberOfBytes": "20"
|
||||
|
|
@ -166,13 +174,27 @@
|
|||
"numberOfBytes": "32",
|
||||
"value": "t_bool"
|
||||
},
|
||||
"t_mapping(t_contract(IStrategy)7471,t_uint96)": {
|
||||
"t_mapping(t_contract(IStrategy)26468,t_uint96)": {
|
||||
"encoding": "mapping",
|
||||
"key": "t_contract(IStrategy)7471",
|
||||
"key": "t_contract(IStrategy)26468",
|
||||
"label": "mapping(contract IStrategy => uint96)",
|
||||
"numberOfBytes": "32",
|
||||
"value": "t_uint96"
|
||||
},
|
||||
"t_mapping(t_uint32,t_mapping(t_address,t_bool))": {
|
||||
"encoding": "mapping",
|
||||
"key": "t_uint32",
|
||||
"label": "mapping(uint32 => mapping(address => bool))",
|
||||
"numberOfBytes": "32",
|
||||
"value": "t_mapping(t_address,t_bool)"
|
||||
},
|
||||
"t_mapping(t_uint32,t_mapping(t_uint32,t_mapping(t_address,t_bool)))": {
|
||||
"encoding": "mapping",
|
||||
"key": "t_uint32",
|
||||
"label": "mapping(uint32 => mapping(uint32 => mapping(address => bool)))",
|
||||
"numberOfBytes": "32",
|
||||
"value": "t_mapping(t_uint32,t_mapping(t_address,t_bool))"
|
||||
},
|
||||
"t_string_storage": {
|
||||
"encoding": "bytes",
|
||||
"label": "string",
|
||||
|
|
@ -183,6 +205,11 @@
|
|||
"label": "uint256",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_uint32": {
|
||||
"encoding": "inplace",
|
||||
"label": "uint32",
|
||||
"numberOfBytes": "4"
|
||||
},
|
||||
"t_uint8": {
|
||||
"encoding": "inplace",
|
||||
"label": "uint8",
|
||||
|
|
|
|||
|
|
@ -224,6 +224,67 @@ contract RewardsSubmitterTest is AVSDeployer {
|
|||
serviceManager.submitRewards(submission2);
|
||||
}
|
||||
|
||||
function test_submitRewards_revertsIfWindowAlreadySubmittedForToken() public {
|
||||
_registerOperator(operator1, operator1);
|
||||
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission =
|
||||
_buildSubmission(1000e18, operator1);
|
||||
|
||||
vm.warp(submission.startTimestamp + submission.duration + 1);
|
||||
|
||||
vm.prank(snowbridgeAgent);
|
||||
serviceManager.submitRewards(submission);
|
||||
|
||||
assertTrue(
|
||||
serviceManager.rewardsSubmittedForWindow(
|
||||
submission.startTimestamp, submission.duration, address(rewardToken)
|
||||
),
|
||||
"replay guard should be set for the submitted window and token"
|
||||
);
|
||||
|
||||
vm.prank(snowbridgeAgent);
|
||||
vm.expectRevert(
|
||||
abi.encodeWithSignature(
|
||||
"RewardsAlreadySubmittedForWindow(uint32,uint32,address)",
|
||||
submission.startTimestamp,
|
||||
submission.duration,
|
||||
address(rewardToken)
|
||||
)
|
||||
);
|
||||
serviceManager.submitRewards(submission);
|
||||
}
|
||||
|
||||
function test_submitRewards_allowsDifferentDurationForSameStartAndToken() public {
|
||||
_registerOperator(operator1, operator1);
|
||||
|
||||
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory firstSubmission =
|
||||
_buildSubmission(1000e18, operator1);
|
||||
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory secondSubmission =
|
||||
_buildSubmission(500e18, operator1);
|
||||
|
||||
secondSubmission.duration = 2 * TEST_CALCULATION_INTERVAL;
|
||||
|
||||
vm.warp(secondSubmission.startTimestamp + secondSubmission.duration + 1);
|
||||
|
||||
vm.prank(snowbridgeAgent);
|
||||
serviceManager.submitRewards(firstSubmission);
|
||||
|
||||
vm.prank(snowbridgeAgent);
|
||||
serviceManager.submitRewards(secondSubmission);
|
||||
|
||||
assertTrue(
|
||||
serviceManager.rewardsSubmittedForWindow(
|
||||
firstSubmission.startTimestamp, firstSubmission.duration, address(rewardToken)
|
||||
),
|
||||
"first window should be tracked independently"
|
||||
);
|
||||
assertTrue(
|
||||
serviceManager.rewardsSubmittedForWindow(
|
||||
secondSubmission.startTimestamp, secondSubmission.duration, address(rewardToken)
|
||||
),
|
||||
"second window should be tracked independently"
|
||||
);
|
||||
}
|
||||
|
||||
function test_submitRewards_withCustomDescription() public {
|
||||
_registerOperator(operator1, operator1);
|
||||
// Build submission with custom description
|
||||
|
|
@ -256,6 +317,9 @@ contract RewardsSubmitterTest is AVSDeployer {
|
|||
|
||||
function test_submitRewards_withDifferentToken() public {
|
||||
_registerOperator(operator1, operator1);
|
||||
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory firstSubmission =
|
||||
_buildSubmission(1000e18, operator1);
|
||||
|
||||
// Deploy a different token
|
||||
ERC20FixedSupply otherToken =
|
||||
new ERC20FixedSupply("Other", "OTHER", 1000000e18, address(this));
|
||||
|
|
@ -285,10 +349,26 @@ contract RewardsSubmitterTest is AVSDeployer {
|
|||
|
||||
vm.warp(submission.startTimestamp + submission.duration + 1);
|
||||
|
||||
vm.prank(snowbridgeAgent);
|
||||
serviceManager.submitRewards(firstSubmission);
|
||||
|
||||
vm.prank(snowbridgeAgent);
|
||||
vm.expectEmit(false, false, false, true);
|
||||
emit IDataHavenServiceManagerEvents.RewardsSubmitted(500e18, 1);
|
||||
serviceManager.submitRewards(submission);
|
||||
|
||||
assertTrue(
|
||||
serviceManager.rewardsSubmittedForWindow(
|
||||
submission.startTimestamp, submission.duration, address(rewardToken)
|
||||
),
|
||||
"original token should be marked as submitted for the window"
|
||||
);
|
||||
assertTrue(
|
||||
serviceManager.rewardsSubmittedForWindow(
|
||||
submission.startTimestamp, submission.duration, address(otherToken)
|
||||
),
|
||||
"different token should be independently tracked for the same window"
|
||||
);
|
||||
}
|
||||
|
||||
function test_submitRewards_translatesSolochainOperatorToEthOperator() public {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ std = [
|
|||
"parity-scale-codec/std",
|
||||
"pallet-external-validators/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"snowbridge-core/std",
|
||||
"snowbridge-outbound-queue-primitives/std",
|
||||
"sp-core/std",
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ std = [
|
|||
"log/std",
|
||||
"pallet-authorship/std",
|
||||
"pallet-balances/std",
|
||||
"pallet-external-validator-slashes/std",
|
||||
"pallet-external-validators-rewards/std",
|
||||
"pallet-timestamp/std",
|
||||
"pallet-evm/std",
|
||||
|
|
|
|||
|
|
@ -2229,6 +2229,17 @@ export const dataHavenServiceManagerAbi = [
|
|||
outputs: [],
|
||||
stateMutability: 'nonpayable',
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
inputs: [
|
||||
{ name: '', internalType: 'uint32', type: 'uint32' },
|
||||
{ name: '', internalType: 'uint32', type: 'uint32' },
|
||||
{ name: '', internalType: 'address', type: 'address' },
|
||||
],
|
||||
name: 'rewardsSubmittedForWindow',
|
||||
outputs: [{ name: '', internalType: 'bool', type: 'bool' }],
|
||||
stateMutability: 'view',
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
inputs: [
|
||||
|
|
@ -2718,6 +2729,15 @@ export const dataHavenServiceManagerAbi = [
|
|||
{ type: 'error', inputs: [], name: 'OperatorAlreadyRegistered' },
|
||||
{ type: 'error', inputs: [], name: 'OperatorNotInAllowlist' },
|
||||
{ type: 'error', inputs: [], name: 'OperatorNotRegistered' },
|
||||
{
|
||||
type: 'error',
|
||||
inputs: [
|
||||
{ name: 'startTimestamp', internalType: 'uint32', type: 'uint32' },
|
||||
{ name: 'duration', internalType: 'uint32', type: 'uint32' },
|
||||
{ name: 'token', internalType: 'address', type: 'address' },
|
||||
],
|
||||
name: 'RewardsAlreadySubmittedForWindow',
|
||||
},
|
||||
{ type: 'error', inputs: [], name: 'SolochainAddressAlreadyAssigned' },
|
||||
{ type: 'error', inputs: [], name: 'StrategyNotInOperatorSet' },
|
||||
{ type: 'error', inputs: [], name: 'UnknownSolochainAddress' },
|
||||
|
|
@ -11029,6 +11049,15 @@ export const readDataHavenServiceManagerOwner =
|
|||
functionName: 'owner',
|
||||
})
|
||||
|
||||
/**
|
||||
* Wraps __{@link readContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"rewardsSubmittedForWindow"`
|
||||
*/
|
||||
export const readDataHavenServiceManagerRewardsSubmittedForWindow =
|
||||
/*#__PURE__*/ createReadContract({
|
||||
abi: dataHavenServiceManagerAbi,
|
||||
functionName: 'rewardsSubmittedForWindow',
|
||||
})
|
||||
|
||||
/**
|
||||
* Wraps __{@link readContract}__ with `abi` set to __{@link dataHavenServiceManagerAbi}__ and `functionName` set to `"snowbridgeGateway"`
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { $ } from "bun";
|
|||
import { Binary, FixedSizeBinary } from "polkadot-api";
|
||||
import { CROSS_CHAIN_TIMEOUTS, getPapiSigner, logger } from "utils";
|
||||
import type { Address } from "viem";
|
||||
import { gatewayAbi } from "../../contract-bindings";
|
||||
import { getContractInstance, parseDeploymentsFile } from "../../utils/contracts";
|
||||
import { waitForDataHavenEvent, waitForEthereumEvent } from "../../utils/events";
|
||||
import { waitFor } from "../../utils/waits";
|
||||
|
|
@ -173,11 +172,11 @@ describe("Should slash an operator", () => {
|
|||
logger.info("Slashes message sent");
|
||||
|
||||
const fromBlock = await publicClient.getBlockNumber();
|
||||
const deployments = await parseDeploymentsFile();
|
||||
const serviceManager = await getContractInstance("ServiceManager");
|
||||
const _ethEvent = await waitForEthereumEvent({
|
||||
client: publicClient,
|
||||
address: deployments.Gateway,
|
||||
abi: gatewayAbi,
|
||||
address: serviceManager.address,
|
||||
abi: serviceManager.abi,
|
||||
eventName: "SlashingComplete",
|
||||
fromBlock: fromBlock > 0n ? fromBlock - 1n : fromBlock,
|
||||
timeout: CROSS_CHAIN_TIMEOUTS.DH_TO_ETH_MS
|
||||
|
|
|
|||
Loading…
Reference in a new issue