Merge remote-tracking branch 'origin/main' into fix/replay-reward-submission

# Conflicts:
#	contracts/deployments/state-diff.checksum
#	contracts/deployments/state-diff.json
#	test/contract-bindings/generated.ts
This commit is contained in:
Ahmad Kaouk 2026-04-14 11:03:26 +02:00
commit 3cbf7d432c
No known key found for this signature in database
GPG key ID: CF4E030983820DA8
377 changed files with 9336 additions and 7020 deletions

View file

@ -8,7 +8,7 @@ inputs:
default: "cache"
install-deps:
description: "Whether to install system dependencies. Set to false for self-hosted runners with pre-installed deps"
description: "Whether to install system dependencies via apt. Set to false to install dependencies locally without sudo."
required: false
default: "true"
@ -128,9 +128,9 @@ runs:
shell: bash
run: sudo apt-get update && sudo apt-get install -y libpq-dev libclang-dev
# Auto-install missing dependencies when install-deps is false (for self-hosted runners)
# Auto-install missing dependencies locally when install-deps is false (no sudo required)
# Note: PATH and env vars are already set up in "Setup paths for cached tools" step
- name: Setup system dependencies (self-hosted)
- name: Setup system dependencies (local install)
if: inputs.install-deps == 'false'
shell: bash
run: |
@ -226,8 +226,8 @@ runs:
with:
repo-token: ${{ github.token }}
# Auto-install protoc when install-deps is false (for self-hosted runners)
- name: Setup Protoc (self-hosted)
# Auto-install protoc locally when install-deps is false (no sudo required)
- name: Setup Protoc (local install)
if: inputs.install-deps == 'false'
shell: bash
run: |

View file

@ -20,8 +20,7 @@ jobs:
outputs:
binary-hash: ${{ steps.hash-binary.outputs.datahaven-node-hash }}
name: Build operator binary
runs-on:
group: DH-runners
runs-on: ubuntu-latest
env:
RUSTC_WRAPPER: "sccache"
CARGO_INCREMENTAL: "0"
@ -42,7 +41,6 @@ jobs:
- uses: ./.github/workflows/actions/setup-env
with:
cache-key: BUILD-RELEASE
install-deps: false
- name: Set build flags
run: echo "RUSTFLAGS=${{ env.RUSTFLAGS }} -C linker=clang -C link-arg=-fuse-ld=mold" >> $GITHUB_ENV

View file

@ -10,8 +10,7 @@ on:
jobs:
build-node:
name: Build operator binary
runs-on:
group: DH-runners
runs-on: ubuntu-latest
env:
RUSTC_WRAPPER: "sccache"
CARGO_INCREMENTAL: "0"
@ -29,8 +28,6 @@ jobs:
- uses: ./.github/workflows/actions/setup-env
with:
cache-key: BUILD-RELEASE
install-deps: false
skip-libpq: true
- name: Set build flags
run: echo "RUSTFLAGS=-C linker=clang -C link-arg=-fuse-ld=mold" >> $GITHUB_ENV

View file

@ -34,7 +34,7 @@ permissions:
env:
FOUNDRY_PROFILE: ci
LOG_LEVEL: debug
DOCKER_HOST: unix:///run/user/1020/podman/podman.sock
# DOCKER_HOST: unix:///run/user/1020/podman/podman.sock # was: self-hosted podman socket
KURTOSIS_CORE_IMAGE: docker.io/kurtosistech/core
KURTOSIS_ENGINE_IMAGE: docker.io/kurtosistech/engine
KURTOSIS_VERSION: 1.15.2
@ -42,8 +42,9 @@ env:
jobs:
kurtosis:
runs-on:
group: DH-Testing
runs-on: ubuntu-latest
# was: runs-on:
# group: DH-Testing
name: E2E Tests with Kurtosis Ethereum Network
defaults:
run:
@ -91,27 +92,26 @@ jobs:
fi
kurtosis analytics disable
kurtosis version
- name: Configure Kurtosis cluster = podman
# was: podman cluster config for self-hosted runner
# - name: Configure Kurtosis cluster = podman
# run: |
# CFG_PATH="$(kurtosis config path)"
# mkdir -p "$(dirname "$CFG_PATH")"
# cat > "$CFG_PATH" <<'YML'
# config-version: 6
# should-send-metrics: true
# kurtosis-clusters:
# docker:
# type: "docker"
# podman:
# type: "podman"
# YML
# kurtosis cluster set podman
# kurtosis cluster get
- name: Start Kurtosis engine
run: |
# Get the config path from Kurtosis itself (portable)
CFG_PATH="$(kurtosis config path)"
mkdir -p "$(dirname "$CFG_PATH")"
# Create/update config with a podman cluster entry
cat > "$CFG_PATH" <<'YML'
config-version: 6
should-send-metrics: true
kurtosis-clusters:
docker:
type: "docker"
podman:
type: "podman"
YML
kurtosis cluster set podman
kurtosis cluster get
- name: Start Kurtosis engine with Podman
run: |
kurtosis engine stop
kurtosis clean
kurtosis engine stop || true
kurtosis clean || true
kurtosis engine start
kurtosis engine status
- uses: actions/cache@v4
@ -154,4 +154,5 @@ jobs:
- name: Delete volumes not used
if: always()
run: podman system prune --volumes -f
run: docker system prune --volumes -f
# was: podman system prune --volumes -f

View file

@ -31,8 +31,7 @@ jobs:
retention-days: 1
build-binary:
runs-on:
group: DH-runners
runs-on: ubuntu-latest
needs: ["prepare-sources"]
permissions:
contents: read

View file

@ -27,8 +27,7 @@ env:
jobs:
cargo-fmt:
name: "Check format with rustfmt"
runs-on:
group: DH-runners
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ env.WORKING_DIR }}
@ -38,15 +37,13 @@ jobs:
- uses: ./.github/workflows/actions/setup-env
with:
cache-key: FMT
install-deps: false # Self-hosted runners have deps pre-installed
- name: Run cargo fmt
run: cargo fmt --all -- --check
check-rust-lint:
name: "Check lint with clippy"
runs-on:
group: DH-runners
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ env.WORKING_DIR }}
@ -56,7 +53,6 @@ jobs:
- uses: ./.github/workflows/actions/setup-env
with:
cache-key: LINT
install-deps: false # Self-hosted runners have deps pre-installed
- name: Set build flags
run: echo "RUSTFLAGS=${{ env.RUSTFLAGS }} -C linker=clang -C link-arg=-fuse-ld=mold" >> $GITHUB_ENV
@ -66,8 +62,7 @@ jobs:
check-cargo-sort:
name: "Check Cargo sort"
runs-on:
group: DH-runners
runs-on: ubuntu-latest
defaults:
run:

View file

@ -13,8 +13,7 @@ permissions:
jobs:
rust-tests:
name: Run Operator Rust tests
runs-on:
group: DH-runners
runs-on: ubuntu-latest
env:
RUSTC_WRAPPER: "sccache"
CARGO_INCREMENTAL: "0"
@ -32,7 +31,6 @@ jobs:
- uses: ./.github/workflows/actions/setup-env
with:
cache-key: "TEST"
install-deps: false
- name: Set build flags
run: echo "RUSTFLAGS=${{ env.RUSTFLAGS }} -C linker=clang -C link-arg=-fuse-ld=mold" >> $GITHUB_ENV

View file

@ -15,8 +15,7 @@ permissions:
jobs:
warm-cache:
name: Warm sccache cache
runs-on:
group: DH-runners
runs-on: ubuntu-latest
env:
RUSTC_WRAPPER: "sccache"
CARGO_INCREMENTAL: "0"
@ -34,7 +33,6 @@ jobs:
- uses: ./.github/workflows/actions/setup-env
with:
cache-key: WARM
install-deps: false
- name: Set build flags
run: echo "RUSTFLAGS=${{ env.RUSTFLAGS }} -C linker=clang -C link-arg=-fuse-ld=mold" >> $GITHUB_ENV

View file

@ -26,7 +26,7 @@
},
"avs": {
"avsOwner": "0x976EA74026E726554dB657fA54763abd0C3a0aa9",
"rewardsInitiator": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955",
"snowbridgeInitiator": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955",
"validatorSetSubmitter": "0x976EA74026E726554dB657fA54763abd0C3a0aa9",
"validatorsStrategies": []
},

View file

@ -68,7 +68,7 @@
/// The address of the account that initiates the rewards calculation.
/// This is for the EigenLayer rewards distribution way, using the RewardsCoordinator.
/// But for now, we're not using it, and instead sending the rewards directly.
"rewardsInitiator": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955",
"snowbridgeInitiator": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955",
/// The EigenLayer strategy addresses for the Validators to stake into.
/// The beaconChainETHStrategy is a virtual address representing native beacon chain ETH.
/// All networks:

View file

@ -30,7 +30,7 @@
},
"avs": {
"avsOwner": "0x0000000000000000000000000000000000000000",
"rewardsInitiator": "0x0000000000000000000000000000000000000000",
"snowbridgeInitiator": "0x0000000000000000000000000000000000000000",
"validatorsStrategies": [
"0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0",
"0x93c4b944D05dfe6df7645A86cd2206016c51564D",

View file

@ -32,7 +32,7 @@
},
"avs": {
"avsOwner": "0xe30a38ac89ffE5A86D5389Bfbf70C7EC766FbB6e",
"rewardsInitiator": "0xe30a38ac89ffE5A86D5389Bfbf70C7EC766FbB6e",
"snowbridgeInitiator": "0xe30a38ac89ffE5A86D5389Bfbf70C7EC766FbB6e",
"validatorsStrategies": [
"0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0",
"0xf8a1a66130d614c7360e868576d5e59203475fe0",

View file

@ -32,7 +32,7 @@
},
"avs": {
"avsOwner": "0x0000000000000000000000000000000000000000",
"rewardsInitiator": "0x0000000000000000000000000000000000000000",
"snowbridgeInitiator": "0x0000000000000000000000000000000000000000",
"validatorsStrategies": [
"0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0",
"0xf8a1a66130d614c7360e868576d5e59203475fe0",

View file

@ -1,27 +1 @@
{
"network": "anvil",
"BeefyClient": "0x99bbA657f2BbC93c02D617f8bA121cB8Fc104Acf",
"AgentExecutor": "0x0E801D84Fa97b50751Dbf25036d067dCf18858bF",
"Gateway": "0x9d4454B023096f34B160D6B654540c56A1F81688",
"ServiceManager": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D",
"ServiceManagerImplementation": "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570",
"ProxyAdmin": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788",
"RewardsAgent": "0xac06641381166cf085281c45292147f833C622d7",
"DelegationManager": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
"StrategyManager": "0x9A676e781A523b5d0C0e43731313A708CB607508",
"AVSDirectory": "0x0B306BF915C4d645ff596e518fAf3F9669b97016",
"EigenPodManager": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1",
"EigenPodBeacon": "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1",
"RewardsCoordinator": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE",
"AllocationManager": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
"PermissionController": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c",
"ETHPOSDeposit": "0xC7f2Cf4845C6db0e1a1e91ED41Bcd0FcC1b0E141",
"BaseStrategyImplementation": "0xf5059a5D33d5853360D16C683c16e67980206f36",
"DeployedStrategies": [
{
"address": "0x998abeb3E57409262aE5b751f60747921B33613E",
"underlyingToken": "0x95401dc811bb5740090279Ba06cfA8fcF6113778",
"tokenCreator": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
}
]
}
{"network": "anvil","BeefyClient": "0x99bbA657f2BbC93c02D617f8bA121cB8Fc104Acf","AgentExecutor": "0x0E801D84Fa97b50751Dbf25036d067dCf18858bF","Gateway": "0x9d4454B023096f34B160D6B654540c56A1F81688","ServiceManager": "0x809d550fca64d94Bd9F66E60752A544199cfAC3D","ServiceManagerImplementation": "0x36C02dA8a0983159322a80FFE9F24b1acfF8B570","ProxyAdmin": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788","RewardsAgent": "0xac06641381166cf085281c45292147f833C622d7","DelegationManager": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82","StrategyManager": "0x9A676e781A523b5d0C0e43731313A708CB607508","AVSDirectory": "0x0B306BF915C4d645ff596e518fAf3F9669b97016","EigenPodManager": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1","EigenPodBeacon": "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1","RewardsCoordinator": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE","AllocationManager": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed","PermissionController": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c","ETHPOSDeposit": "0xC7f2Cf4845C6db0e1a1e91ED41Bcd0FcC1b0E141","BaseStrategyImplementation": "0xf5059a5D33d5853360D16C683c16e67980206f36","DeployedStrategies": [{"address": "0x998abeb3E57409262aE5b751f60747921B33613E","underlyingToken": "0x95401dc811bb5740090279Ba06cfA8fcF6113778","tokenCreator": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"}]}

View file

@ -1 +1 @@
a516a36564321527f303743cd049ff7007f5cf34
107e07a8e4ac06299cc380520c6e811bd70c5661

File diff suppressed because one or more lines are too long

View file

@ -18,7 +18,7 @@ contract Config {
// AVS parameters
struct AVSConfig {
address avsOwner;
address rewardsInitiator;
address snowbridgeInitiator;
address[] validatorsStrategies;
address validatorSetSubmitter;
}

View file

@ -44,7 +44,7 @@ import {ValidatorsUtils} from "../../script/utils/ValidatorsUtils.sol";
// Shared structs
struct ServiceManagerInitParams {
address avsOwner;
address rewardsInitiator;
address snowbridgeInitiator;
IRewardsCoordinatorTypes.StrategyAndMultiplier[] validatorsStrategiesAndMultipliers;
address gateway;
address validatorSetSubmitter;
@ -271,7 +271,7 @@ abstract contract DeployBase is Script, DeployParams, Accounts {
// Create service manager initialisation parameters struct
ServiceManagerInitParams memory initParams = ServiceManagerInitParams({
avsOwner: avsConfig.avsOwner,
rewardsInitiator: agentAddress,
snowbridgeInitiator: agentAddress,
validatorsStrategiesAndMultipliers: strategiesAndMultipliers,
gateway: address(gateway),
validatorSetSubmitter: avsConfig.validatorSetSubmitter,

View file

@ -124,7 +124,7 @@ contract DeployLive is DeployBase {
bytes memory initData = abi.encodeWithSelector(
DataHavenServiceManager.initialize.selector,
params.avsOwner,
params.rewardsInitiator,
params.snowbridgeInitiator,
params.validatorsStrategiesAndMultipliers,
params.gateway,
params.validatorSetSubmitter,

View file

@ -205,7 +205,7 @@ contract DeployLocal is DeployBase {
bytes memory initData = abi.encodeWithSelector(
DataHavenServiceManager.initialize.selector,
params.avsOwner,
params.rewardsInitiator,
params.snowbridgeInitiator,
params.validatorsStrategiesAndMultipliers,
params.gateway,
params.validatorSetSubmitter,

View file

@ -74,7 +74,7 @@ contract DeployParams is Script, Config {
} else {
config.avsOwner = vm.parseJsonAddress(configJson, ".avs.avsOwner");
}
config.rewardsInitiator = vm.parseJsonAddress(configJson, ".avs.rewardsInitiator");
config.snowbridgeInitiator = vm.parseJsonAddress(configJson, ".avs.snowbridgeInitiator");
config.validatorsStrategies =
vm.parseJsonAddressArray(configJson, ".avs.validatorsStrategies");

View file

@ -12,7 +12,7 @@ contract DataHavenServiceManagerBadLayout is OwnableUpgradeable {
uint256 public layoutBreaker;
// Original variables (shifted by one slot)
address public rewardsInitiator;
address public snowbridgeInitiator;
mapping(address => bool) public validatorsAllowlist;
IGatewayV2 private _snowbridgeGateway;
mapping(address => address) public validatorEthAddressToSolochainAddress;

View file

@ -58,7 +58,7 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
// ============ State Variables ============
/// @notice The address authorized to initiate rewards submissions
address public rewardsInitiator;
address public snowbridgeInitiator;
/// @inheritdoc IDataHavenServiceManager
mapping(address => bool) public validatorsAllowlist;
@ -93,8 +93,8 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
// ============ Modifiers ============
/// @notice Restricts function to the rewards initiator
modifier onlyRewardsInitiator() {
_checkRewardsInitiator();
modifier onlySnowbridgeInitiator() {
_checkSnowbridgeInitiator();
_;
}
@ -132,14 +132,13 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
require(msg.sender == proxyAdmin, NotProxyAdmin());
}
function _checkRewardsInitiator() internal view {
require(msg.sender == rewardsInitiator, OnlyRewardsInitiator());
function _checkSnowbridgeInitiator() internal view {
require(msg.sender == snowbridgeInitiator, OnlyRewardsInitiator());
}
function _checkValidator() internal view {
OperatorSet memory operatorSet = OperatorSet({avs: address(this), id: VALIDATORS_SET_ID});
require(
_ALLOCATION_MANAGER.isMemberOfOperatorSet(msg.sender, operatorSet),
_ALLOCATION_MANAGER.isMemberOfOperatorSet(msg.sender, _validatorsOperatorSet()),
CallerIsNotValidator()
);
}
@ -169,21 +168,21 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
/// @inheritdoc IDataHavenServiceManager
function initialize(
address initialOwner,
address _rewardsInitiator,
address _snowbridgeInitiator,
IRewardsCoordinatorTypes.StrategyAndMultiplier[] memory validatorsStrategiesAndMultipliers,
address _snowbridgeGatewayAddress,
address _validatorSetSubmitter,
string memory initialVersion
) public virtual initializer {
require(initialOwner != address(0), ZeroAddress());
require(_rewardsInitiator != address(0), ZeroAddress());
require(_snowbridgeInitiator != address(0), ZeroAddress());
require(_snowbridgeGatewayAddress != address(0), ZeroAddress());
require(bytes(initialVersion).length > 0, EmptyVersion());
__Ownable_init();
_transferOwnership(initialOwner);
rewardsInitiator = _rewardsInitiator;
emit RewardsInitiatorSet(address(0), _rewardsInitiator);
snowbridgeInitiator = _snowbridgeInitiator;
emit SnowbridgeInitiatorSet(address(0), _snowbridgeInitiator);
// Set version from parameter (allows flexibility per deployment environment)
_version = initialVersion;
@ -340,7 +339,7 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
address oldSolochainAddress = validatorEthAddressToSolochainAddress[msg.sender];
require(oldSolochainAddress != solochainAddress, SolochainAddressAlreadyAssigned());
address existingEthOperator = validatorSolochainAddressToEthAddress[solochainAddress];
address existingEthOperator = _consumeExpiredSolochainMapping(solochainAddress);
require(existingEthOperator == address(0), SolochainAddressAlreadyAssigned());
delete validatorSolochainAddressToEthAddress[oldSolochainAddress];
@ -383,10 +382,8 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
);
address solochainAddress = _toAddress(data);
require(
validatorSolochainAddressToEthAddress[solochainAddress] == address(0),
SolochainAddressAlreadyAssigned()
);
address existingEthOperator = _consumeExpiredSolochainMapping(solochainAddress);
require(existingEthOperator == address(0), SolochainAddressAlreadyAssigned());
validatorEthAddressToSolochainAddress[operator] = solochainAddress;
validatorSolochainAddressToEthAddress[solochainAddress] = operator;
@ -407,9 +404,7 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
validatorEthAddressToSolochainAddress[operator] != address(0), OperatorNotRegistered()
);
address oldSolochainAddress = validatorEthAddressToSolochainAddress[operator];
delete validatorEthAddressToSolochainAddress[operator];
delete validatorSolochainAddressToEthAddress[oldSolochainAddress];
emit OperatorDeregistered(operator, operatorSetIds[0]);
}
@ -437,6 +432,13 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
address validator
) external onlyOwner {
validatorsAllowlist[validator] = false;
if (validatorEthAddressToSolochainAddress[validator] != address(0)) {
uint32[] memory operatorSetIds = new uint32[](1);
operatorSetIds[0] = VALIDATORS_SET_ID;
_deregisterOperatorFromOperatorSets(validator, operatorSetIds);
}
emit ValidatorRemovedFromAllowlist(validator);
}
@ -526,7 +528,7 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
function submitRewards(
uint32 eraIndex,
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission calldata submission
) external override onlyRewardsInitiator {
) external override onlySnowbridgeInitiator {
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory translatedSubmission =
submission;
@ -536,9 +538,8 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
uint256 totalAmount = 0;
uint256 resolvedCount = 0;
for (uint256 i = 0; i < len; i++) {
address ethOp = validatorSolochainAddressToEthAddress[
translatedSubmission.operatorRewards[i].operator
];
address ethOp =
_resolveSlashableEthOperator(translatedSubmission.operatorRewards[i].operator);
if (ethOp == address(0)) continue;
translated[resolvedCount] = translatedSubmission.operatorRewards[i];
translated[resolvedCount].operator = ethOp;
@ -556,7 +557,17 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
if (resolvedCount == 0) return;
_sortOperatorRewards(translatedSubmission.operatorRewards);
uint256 uniqueCount = _sortAndMergeDuplicateOperators(translatedSubmission.operatorRewards);
// Shrink the array to the unique count if duplicates were merged
if (uniqueCount < translatedSubmission.operatorRewards.length) {
IRewardsCoordinatorTypes.OperatorReward[] memory trimmed =
new IRewardsCoordinatorTypes.OperatorReward[](uniqueCount);
for (uint256 i = 0; i < uniqueCount; i++) {
trimmed[i] = translatedSubmission.operatorRewards[i];
}
translatedSubmission.operatorRewards = trimmed;
}
address token = address(submission.token);
require(
@ -577,13 +588,13 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
}
/// @inheritdoc IDataHavenServiceManager
function setRewardsInitiator(
address newRewardsInitiator
function setSnowbridgeInitiator(
address newSnowbridgeInitiator
) external override onlyOwner {
require(newRewardsInitiator != address(0), ZeroAddress());
address oldInitiator = rewardsInitiator;
rewardsInitiator = newRewardsInitiator;
emit RewardsInitiatorSet(oldInitiator, newRewardsInitiator);
require(newSnowbridgeInitiator != address(0), ZeroAddress());
address oldInitiator = snowbridgeInitiator;
snowbridgeInitiator = newSnowbridgeInitiator;
emit SnowbridgeInitiatorSet(oldInitiator, newSnowbridgeInitiator);
}
// ============ AVS Management Functions ============
@ -600,6 +611,13 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
address operator,
uint32[] calldata operatorSetIds
) external onlyOwner {
_deregisterOperatorFromOperatorSets(operator, operatorSetIds);
}
function _deregisterOperatorFromOperatorSets(
address operator,
uint32[] memory operatorSetIds
) internal {
IAllocationManagerTypes.DeregisterParams memory params =
IAllocationManagerTypes.DeregisterParams({
operator: operator, avs: address(this), operatorSetIds: operatorSetIds
@ -615,9 +633,9 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
*/
function slashValidatorsOperator(
SlashingRequest[] calldata slashings
) external onlyRewardsInitiator {
) external onlySnowbridgeInitiator {
for (uint256 i = 0; i < slashings.length; i++) {
address ethOperator = validatorSolochainAddressToEthAddress[slashings[i].operator];
address ethOperator = _resolveSlashableEthOperator(slashings[i].operator);
if (ethOperator == address(0)) continue;
IAllocationManagerTypes.SlashingParams memory slashingParams =
IAllocationManagerTypes.SlashingParams({
@ -653,14 +671,20 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
// ============ Internal Functions ============
/**
* @notice Sorts operator rewards array by operator address in ascending order using insertion sort
* @dev Insertion sort is optimal for small arrays (validator set capped at 32)
* @param rewards The operator rewards array to sort in-place
* @notice Sorts operator rewards by address and merges consecutive duplicates
* @dev After solochainETH address translation, multiple solochain addresses may map to the
* same ETH operator (e.g. operator deregistered and re-registered with a new solochain
* address within the same reward window). EigenLayer's RewardsCoordinator requires
* strictly ascending unique operators, so duplicates must be merged.
* @param rewards The operator rewards array to sort and merge in-place
* @return uniqueCount The number of unique operators after merging
*/
function _sortOperatorRewards(
function _sortAndMergeDuplicateOperators(
IRewardsCoordinatorTypes.OperatorReward[] memory rewards
) private pure {
) private pure returns (uint256) {
uint256 len = rewards.length;
// Insertion sort (optimal for small arrays; validator set capped at 32)
for (uint256 i = 1; i < len; i++) {
IRewardsCoordinatorTypes.OperatorReward memory key = rewards[i];
uint256 j = i;
@ -670,6 +694,19 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
}
rewards[j] = key;
}
// Merge consecutive duplicates
if (len <= 1) return len;
uint256 write = 0;
for (uint256 read = 1; read < len; read++) {
if (rewards[read].operator == rewards[write].operator) {
rewards[write].amount += rewards[read].amount;
} else {
write++;
rewards[write] = rewards[read];
}
}
return write + 1;
}
/**
@ -707,4 +744,32 @@ contract DataHavenServiceManager is OwnableUpgradeable, IAVSRegistrar, IDataHave
}
return opA < opB;
}
function _validatorsOperatorSet() internal view returns (OperatorSet memory) {
return OperatorSet({avs: address(this), id: VALIDATORS_SET_ID});
}
function _resolveSlashableEthOperator(
address solochainAddress
) internal view returns (address) {
address ethOperator = validatorSolochainAddressToEthAddress[solochainAddress];
if (ethOperator == address(0)) return address(0);
if (!_ALLOCATION_MANAGER.isOperatorSlashable(ethOperator, _validatorsOperatorSet())) {
return address(0);
}
return ethOperator;
}
function _consumeExpiredSolochainMapping(
address solochainAddress
) internal returns (address) {
address existingEthOperator = validatorSolochainAddressToEthAddress[solochainAddress];
if (existingEthOperator == address(0)) return address(0);
if (_ALLOCATION_MANAGER.isOperatorSlashable(existingEthOperator, _validatorsOperatorSet()))
{
return existingEthOperator;
}
delete validatorSolochainAddressToEthAddress[solochainAddress];
return address(0);
}
}

View file

@ -94,10 +94,10 @@ interface IDataHavenServiceManagerEvents {
/// @param operatorCount The number of operators that received rewards
event RewardsSubmitted(uint256 totalAmount, uint256 operatorCount);
/// @notice Emitted when the rewards initiator address is updated
/// @param oldInitiator The previous rewards initiator address
/// @param newInitiator The new rewards initiator address
event RewardsInitiatorSet(address indexed oldInitiator, address indexed newInitiator);
/// @notice Emitted when the snowbridge initiator address is updated
/// @param oldInitiator The previous snowbridge initiator address
/// @param newInitiator The new snowbridge initiator address
event SnowbridgeInitiatorSet(address indexed oldInitiator, address indexed newInitiator);
/// @notice Emitted when a validator updates their solochain address
/// @param validator Address of the validator
@ -202,7 +202,7 @@ interface IDataHavenServiceManager is
/**
* @notice Initializes the DataHaven Service Manager
* @param initialOwner Address of the initial owner (AVS owner)
* @param rewardsInitiator Address authorized to initiate rewards
* @param snowbridgeInitiator Address authorized to initiate rewards
* @param validatorsStrategiesAndMultipliers Array of strategy-multiplier pairs for the validators
* operator set. Each multiplier must be non-zero.
* @param _snowbridgeGatewayAddress Address of the Snowbridge Gateway
@ -211,7 +211,7 @@ interface IDataHavenServiceManager is
*/
function initialize(
address initialOwner,
address rewardsInitiator,
address snowbridgeInitiator,
IRewardsCoordinatorTypes.StrategyAndMultiplier[] memory validatorsStrategiesAndMultipliers,
address _snowbridgeGatewayAddress,
address _validatorSetSubmitter,
@ -274,8 +274,10 @@ interface IDataHavenServiceManager is
) external;
/**
* @notice Removes a validator from the allowlist
* @notice Removes a validator from the allowlist and revokes its active validator membership
* @param validator Address of the validator to remove
* @dev If the validator is currently registered in the validators operator set,
* this also force-deregisters it from EigenLayer for `VALIDATORS_SET_ID`.
*/
function removeValidatorFromAllowlist(
address validator
@ -359,11 +361,11 @@ interface IDataHavenServiceManager is
) external;
/**
* @notice Set the rewards initiator address authorized to submit rewards
* @param initiator The address of the rewards initiator (Snowbridge Agent)
* @notice Set the snowbridge initiator address authorized to submit rewards
* @param initiator The address of the snowbridge initiator (Snowbridge Agent)
* @dev Only callable by the owner
*/
function setRewardsInitiator(
function setSnowbridgeInitiator(
address initiator
) external;

View file

@ -43,7 +43,7 @@
{
"astId": 23887,
"contract": "src/DataHavenServiceManager.sol:DataHavenServiceManager",
"label": "rewardsInitiator",
"label": "snowbridgeInitiator",
"offset": 0,
"slot": "101",
"type": "t_address"

View file

@ -6,6 +6,7 @@ import {DataHavenServiceManager} from "../src/DataHavenServiceManager.sol";
import {
IAllocationManagerTypes
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol";
import {Test} from "forge-std/Test.sol";
contract OperatorAddressMappingsTest is AVSDeployer {
@ -20,7 +21,7 @@ contract OperatorAddressMappingsTest is AVSDeployer {
// Configure the rewards initiator (not strictly needed for these tests,
// but keeps setup consistent with other suites).
vm.prank(avsOwner);
serviceManager.setRewardsInitiator(snowbridgeAgent);
serviceManager.setSnowbridgeInitiator(snowbridgeAgent);
}
function _registerOperator(
@ -158,8 +159,8 @@ contract OperatorAddressMappingsTest is AVSDeployer {
);
assertEq(
serviceManager.validatorSolochainAddressToEthAddress(solo1),
address(0),
"reverse mapping should be cleared"
operator1,
"reverse mapping should remain slashable until deallocation delay passes"
);
}
@ -172,6 +173,107 @@ contract OperatorAddressMappingsTest is AVSDeployer {
serviceManager.deregisterOperator(operator1, address(serviceManager), operatorSetIds);
}
function test_removeValidatorFromAllowlist_deregistersRegisteredOperator() public {
address solo1 = address(0xBEEF);
_registerOperator(operator1, solo1);
assertEq(
serviceManager.validatorEthAddressToSolochainAddress(operator1),
solo1,
"forward mapping should be set before removal"
);
assertTrue(
serviceManager.validatorsAllowlist(operator1), "operator should start allowlisted"
);
assertTrue(
allocationManager.isMemberOfOperatorSet(
operator1,
OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()})
),
"operator should start in validator set"
);
vm.prank(avsOwner);
serviceManager.removeValidatorFromAllowlist(operator1);
assertFalse(
serviceManager.validatorsAllowlist(operator1),
"operator should be removed from allowlist"
);
assertFalse(
allocationManager.isMemberOfOperatorSet(
operator1,
OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()})
),
"operator should be removed from validator set"
);
assertEq(
serviceManager.validatorEthAddressToSolochainAddress(operator1),
address(0),
"forward mapping should be cleared"
);
assertEq(
serviceManager.validatorSolochainAddressToEthAddress(solo1),
operator1,
"reverse mapping should remain slashable until deallocation delay passes"
);
}
function test_removeValidatorFromAllowlist_succeedsForUnregisteredValidator() public {
vm.prank(avsOwner);
serviceManager.addValidatorToAllowlist(operator1);
assertTrue(
serviceManager.validatorsAllowlist(operator1), "operator should start allowlisted"
);
vm.prank(avsOwner);
serviceManager.removeValidatorFromAllowlist(operator1);
assertFalse(
serviceManager.validatorsAllowlist(operator1),
"operator should be removed from allowlist"
);
assertEq(
serviceManager.validatorEthAddressToSolochainAddress(operator1),
address(0),
"forward mapping should remain empty"
);
}
function test_registerOperator_reclaimsExpiredSolochainMapping() public {
address solo1 = address(0xBEEF);
_registerOperator(operator1, solo1);
vm.prank(avsOwner);
serviceManager.removeValidatorFromAllowlist(operator1);
vm.roll(block.number + uint32(7 days) + 1);
vm.prank(avsOwner);
serviceManager.addValidatorToAllowlist(operator2);
vm.prank(operator2);
delegationManager.registerAsOperator(address(0), 0, "");
uint32[] memory operatorSetIds = new uint32[](1);
operatorSetIds[0] = serviceManager.VALIDATORS_SET_ID();
IAllocationManagerTypes.RegisterParams memory registerParams =
IAllocationManagerTypes.RegisterParams({
avs: address(serviceManager),
operatorSetIds: operatorSetIds,
data: abi.encodePacked(solo1)
});
vm.prank(operator2);
allocationManager.registerForOperatorSets(operator2, registerParams);
assertEq(
serviceManager.validatorSolochainAddressToEthAddress(solo1),
operator2,
"expired reverse mapping should be reclaimed by the new operator"
);
}
function test_updateSolochainAddressForValidator_revertsIfSameAddress() public {
address solo1 = address(0xBEEF);

View file

@ -14,12 +14,14 @@ import {
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol";
import {StdStorage, stdStorage} from "forge-std/StdStorage.sol";
import {AVSDeployer} from "./utils/AVSDeployer.sol";
import {ERC20FixedSupply} from "./utils/ERC20FixedSupply.sol";
import {IDataHavenServiceManagerEvents} from "../src/interfaces/IDataHavenServiceManager.sol";
contract RewardsSubmitterTest is AVSDeployer {
using SafeERC20 for IERC20;
using stdStorage for StdStorage;
// Test addresses
address public snowbridgeAgent = address(uint160(uint256(keccak256("snowbridgeAgent"))));
@ -40,7 +42,7 @@ contract RewardsSubmitterTest is AVSDeployer {
// Configure the rewards initiator
vm.prank(avsOwner);
serviceManager.setRewardsInitiator(snowbridgeAgent);
serviceManager.setSnowbridgeInitiator(snowbridgeAgent);
// Fund the service manager with reward tokens
IERC20(address(rewardToken)).safeTransfer(address(serviceManager), 100000e18);
@ -106,21 +108,21 @@ contract RewardsSubmitterTest is AVSDeployer {
// ============ Configuration Tests ============
function test_setRewardsInitiator() public {
function test_setSnowbridgeInitiator() public {
address newInitiator = address(0x123);
vm.prank(avsOwner);
vm.expectEmit(true, true, false, false);
emit IDataHavenServiceManagerEvents.RewardsInitiatorSet(snowbridgeAgent, newInitiator);
serviceManager.setRewardsInitiator(newInitiator);
emit IDataHavenServiceManagerEvents.SnowbridgeInitiatorSet(snowbridgeAgent, newInitiator);
serviceManager.setSnowbridgeInitiator(newInitiator);
assertEq(serviceManager.rewardsInitiator(), newInitiator);
assertEq(serviceManager.snowbridgeInitiator(), newInitiator);
}
function test_setRewardsInitiator_revertsIfNotOwner() public {
function test_setSnowbridgeInitiator_revertsIfNotOwner() public {
vm.prank(operator1);
vm.expectRevert(bytes("Ownable: caller is not the owner"));
serviceManager.setRewardsInitiator(address(0x123));
serviceManager.setSnowbridgeInitiator(address(0x123));
}
// ============ Access Control Tests ============
@ -407,6 +409,137 @@ contract RewardsSubmitterTest is AVSDeployer {
serviceManager.submitRewards(_eraIndexForStart(submission.startTimestamp), submission);
}
function test_submitRewards_afterAllowlistRemovalStillTranslatesDuringDeallocationDelay()
public
{
address solochainOperator = address(0xBEEF);
_registerOperator(operator1, solochainOperator);
vm.prank(avsOwner);
serviceManager.removeValidatorFromAllowlist(operator1);
uint256 rewardAmount = 1000e18;
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission =
_buildSubmission(rewardAmount, solochainOperator);
vm.warp(submission.startTimestamp + submission.duration + 1);
IRewardsCoordinatorTypes.OperatorReward[] memory expectedOperatorRewards =
new IRewardsCoordinatorTypes.OperatorReward[](1);
expectedOperatorRewards[0] =
IRewardsCoordinatorTypes.OperatorReward({operator: operator1, amount: rewardAmount});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory expectedSubmission =
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({
strategiesAndMultipliers: submission.strategiesAndMultipliers,
token: submission.token,
operatorRewards: expectedOperatorRewards,
startTimestamp: submission.startTimestamp,
duration: submission.duration,
description: submission.description
});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[] memory submissions =
new IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[](1);
submissions[0] = expectedSubmission;
OperatorSet memory operatorSet =
OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()});
vm.expectCall(
address(rewardsCoordinator),
abi.encodeCall(
IRewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission,
(operatorSet, submissions)
)
);
vm.prank(snowbridgeAgent);
serviceManager.submitRewards(_eraIndexForStart(submission.startTimestamp), submission);
}
function test_submitRewards_mergesDuplicateTranslatedOperators() public {
// Register operator1 with solochain1
address solochain1 = address(0xAA01);
address solochain2 = address(0xAA02);
_registerOperator(operator1, solochain1);
// Simulate the scenario where solochain2 also maps to operator1
// (e.g. operator deregistered and re-registered with a new solochain address
// within the same reward window, so the pallet accumulated points under both)
stdstore.target(address(serviceManager))
.sig("validatorSolochainAddressToEthAddress(address)").with_key(solochain2)
.checked_write(operator1);
// Build submission with two entries for different solochain addresses
// that both map to the same ETH operator
uint256 amount1 = 600e18;
uint256 amount2 = 400e18;
uint256 totalAmount = amount1 + amount2;
IRewardsCoordinatorTypes.StrategyAndMultiplier[] memory strategiesAndMultipliers =
new IRewardsCoordinatorTypes.StrategyAndMultiplier[](deployedStrategies.length);
for (uint256 i = 0; i < deployedStrategies.length; i++) {
strategiesAndMultipliers[i] = IRewardsCoordinatorTypes.StrategyAndMultiplier({
strategy: deployedStrategies[i], multiplier: uint96((i + 1) * 1e18)
});
}
IRewardsCoordinatorTypes.OperatorReward[] memory operatorRewards =
new IRewardsCoordinatorTypes.OperatorReward[](2);
operatorRewards[0] =
IRewardsCoordinatorTypes.OperatorReward({operator: solochain1, amount: amount1});
operatorRewards[1] =
IRewardsCoordinatorTypes.OperatorReward({operator: solochain2, amount: amount2});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory submission =
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({
strategiesAndMultipliers: strategiesAndMultipliers,
token: IERC20(address(rewardToken)),
operatorRewards: operatorRewards,
startTimestamp: GENESIS_REWARDS_TIMESTAMP,
duration: TEST_CALCULATION_INTERVAL,
description: "DataHaven rewards"
});
vm.warp(submission.startTimestamp + submission.duration + 1);
// Expect the RewardsCoordinator to receive a single merged entry
IRewardsCoordinatorTypes.OperatorReward[] memory expectedOperatorRewards =
new IRewardsCoordinatorTypes.OperatorReward[](1);
expectedOperatorRewards[0] =
IRewardsCoordinatorTypes.OperatorReward({operator: operator1, amount: totalAmount});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission memory expectedSubmission =
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission({
strategiesAndMultipliers: strategiesAndMultipliers,
token: submission.token,
operatorRewards: expectedOperatorRewards,
startTimestamp: submission.startTimestamp,
duration: submission.duration,
description: submission.description
});
IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[] memory submissions =
new IRewardsCoordinatorTypes.OperatorDirectedRewardsSubmission[](1);
submissions[0] = expectedSubmission;
OperatorSet memory operatorSet =
OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()});
vm.expectCall(
address(rewardsCoordinator),
abi.encodeCall(
IRewardsCoordinator.createOperatorDirectedOperatorSetRewardsSubmission,
(operatorSet, submissions)
)
);
// Event should emit with total amount and ORIGINAL operator count (2)
vm.prank(snowbridgeAgent);
vm.expectEmit(false, false, false, true);
emit IDataHavenServiceManagerEvents.RewardsSubmitted(totalAmount, 2);
serviceManager.submitRewards(_eraIndexForStart(submission.startTimestamp), submission);
}
function test_submitRewards_sortsTranslatedOperatorsByAddress() public {
(address ethLow, address ethHigh) =
operator1 < operator2 ? (operator1, operator2) : (operator2, operator1);

View file

@ -34,7 +34,7 @@ contract SlashingTest is AVSDeployer {
// Configure the rewards initiator (because only the reward agent can submit slashing request)
vm.prank(avsOwner);
serviceManager.setRewardsInitiator(snowbridgeAgent);
serviceManager.setSnowbridgeInitiator(snowbridgeAgent);
vm.prank(operator);
delegationManager.registerAsOperator(address(0), 0, "");
@ -93,7 +93,7 @@ contract SlashingTest is AVSDeployer {
// Configure the rewards initiator (because only the reward agent can submit slashing request)
vm.prank(avsOwner);
serviceManager.setRewardsInitiator(snowbridgeAgent);
serviceManager.setSnowbridgeInitiator(snowbridgeAgent);
vm.prank(operator);
delegationManager.registerAsOperator(address(0), 0, "");
@ -147,7 +147,7 @@ contract SlashingTest is AVSDeployer {
function test_fulfilSlashingRequest_skipsUnknownSolochainAddress() public {
// Configure the rewards initiator (because only the reward agent can submit slashing request)
vm.prank(avsOwner);
serviceManager.setRewardsInitiator(snowbridgeAgent);
serviceManager.setSnowbridgeInitiator(snowbridgeAgent);
address unknownSolochainOperator = address(0xDEAD);
DataHavenServiceManager.SlashingRequest[] memory slashings =
@ -165,4 +165,60 @@ contract SlashingTest is AVSDeployer {
emit IDataHavenServiceManagerEvents.SlashingComplete();
serviceManager.slashValidatorsOperator(slashings);
}
function test_fulfilSlashingRequest_afterAllowlistRemovalStillResolvesDuringDeallocationDelay()
public
{
address solochainOperator = address(0xBEEF);
vm.prank(avsOwner);
serviceManager.addValidatorToAllowlist(operator);
vm.prank(avsOwner);
serviceManager.setSnowbridgeInitiator(snowbridgeAgent);
vm.prank(operator);
delegationManager.registerAsOperator(address(0), 0, "");
uint32[] memory operatorSetIds = new uint32[](1);
operatorSetIds[0] = serviceManager.VALIDATORS_SET_ID();
IAllocationManagerTypes.RegisterParams memory registerParams =
IAllocationManagerTypes.RegisterParams({
avs: address(serviceManager),
operatorSetIds: operatorSetIds,
data: abi.encodePacked(solochainOperator)
});
vm.prank(operator);
allocationManager.registerForOperatorSets(operator, registerParams);
vm.prank(avsOwner);
serviceManager.removeValidatorFromAllowlist(operator);
DataHavenServiceManager.SlashingRequest[] memory slashings =
new DataHavenServiceManager.SlashingRequest[](1);
uint256[] memory wadsToSlash = new uint256[](3);
wadsToSlash[0] = 1e16;
wadsToSlash[1] = 1e16;
wadsToSlash[2] = 1e16;
OperatorSet memory operatorSet =
OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()});
IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet);
slashings[0] = IDataHavenServiceManager.SlashingRequest(
solochainOperator, strategies, wadsToSlash, "Testing slashing"
);
uint256[] memory wadsToSlashed = new uint256[](3);
vm.prank(snowbridgeAgent);
vm.expectEmit();
emit IAllocationManagerEvents.OperatorSlashed(
operator, operatorSet, strategies, wadsToSlashed, "Testing slashing"
);
vm.expectEmit();
emit IDataHavenServiceManagerEvents.SlashingComplete();
serviceManager.slashValidatorsOperator(slashings);
}
}

View file

@ -519,6 +519,33 @@ contract ValidatorSetSelectionTest is SnowbridgeAndAVSDeployer {
);
}
function test_removeValidatorFromAllowlist_excludesOperatorFromValidatorSetMessage() public {
_setupMultipliers(_uniformMultipliers());
address op1 = vm.addr(601);
address solochain1 = address(uint160(0x4001));
_registerOperator(op1, solochain1, _uniformStakes(100 ether));
address op2 = vm.addr(602);
address solochain2 = address(uint160(0x4002));
_registerOperator(op2, solochain2, _uniformStakes(200 ether));
_advancePastAllocationConfigDelay();
_allocateForOperator(op1);
_allocateForOperator(op2);
_advancePastAllocationEffect();
vm.prank(avsOwner);
serviceManager.removeValidatorFromAllowlist(op2);
address[] memory expected = new address[](1);
expected[0] = solochain1;
assertEq(
serviceManager.buildNewValidatorSetMessageForEra(0), _buildExpectedMessage(expected, 0)
);
}
// Test #6: A zero multiplier is accepted and causes that strategy's stake to contribute
// no weight. The operator is still included if other strategies have non-zero multipliers.
function test_zeroMultiplier_accepted_contributesNoWeight() public {

View file

@ -194,7 +194,7 @@ contract ValidatorSetSubmitterTest is SnowbridgeAndAVSDeployer {
abi.encodeWithSelector(
DataHavenServiceManager.initialize.selector,
avsOwner,
rewardsInitiator,
snowbridgeInitiator,
emptyStrategies,
address(snowbridgeGatewayMock),
address(0),

View file

@ -20,18 +20,18 @@ contract StorageLayoutTest is AVSDeployer {
function test_upgradePreservesState() public {
// 1. Populate state
address testValidator = address(0x1234);
address newRewardsInitiator = address(0x9999);
address newSnowbridgeInitiator = address(0x9999);
address testSubmitter = address(0x5678);
vm.startPrank(avsOwner);
serviceManager.addValidatorToAllowlist(testValidator);
serviceManager.setRewardsInitiator(newRewardsInitiator);
serviceManager.setSnowbridgeInitiator(newSnowbridgeInitiator);
serviceManager.setValidatorSetSubmitter(testSubmitter);
vm.stopPrank();
// 2. Record state before upgrade
bool allowlistBefore = serviceManager.validatorsAllowlist(testValidator);
address rewardsInitiatorBefore = serviceManager.rewardsInitiator();
address snowbridgeInitiatorBefore = serviceManager.snowbridgeInitiator();
address ownerBefore = serviceManager.owner();
address gatewayBefore = serviceManager.snowbridgeGateway();
address submitterBefore = serviceManager.validatorSetSubmitter();
@ -51,9 +51,9 @@ contract StorageLayoutTest is AVSDeployer {
"validatorsAllowlist should be preserved"
);
assertEq(
serviceManager.rewardsInitiator(),
rewardsInitiatorBefore,
"rewardsInitiator should be preserved"
serviceManager.snowbridgeInitiator(),
snowbridgeInitiatorBefore,
"snowbridgeInitiator should be preserved"
);
assertEq(serviceManager.owner(), ownerBefore, "owner should be preserved");
assertEq(

View file

@ -67,7 +67,8 @@ contract AVSDeployer is Test {
address public proxyAdminOwner = address(uint160(uint256(keccak256("proxyAdminOwner"))));
address public regularDeployer = address(uint160(uint256(keccak256("regularDeployer"))));
address public avsOwner = address(uint160(uint256(keccak256("avsOwner"))));
address public rewardsInitiator = address(uint160(uint256(keccak256("rewardsInitiator"))));
address public snowbridgeInitiator =
address(uint160(uint256(keccak256("snowbridgeInitiator"))));
address public pauser = address(uint160(uint256(keccak256("pauser"))));
address public unpauser = address(uint160(uint256(keccak256("unpauser"))));
address public rewardsUpdater = address(uint160(uint256(keccak256("rewardsUpdater"))));
@ -246,7 +247,7 @@ contract AVSDeployer is Test {
abi.encodeWithSelector(
DataHavenServiceManager.initialize.selector,
avsOwner,
rewardsInitiator,
snowbridgeInitiator,
defaultStrategyAndMultipliers,
address(snowbridgeGatewayMock),
avsOwner,

3486
operator/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,12 +5,11 @@ edition = "2021"
homepage = "https://datahaven.xyz/"
license = "GPL-3"
repository = "https://github.com/datahavenxyz/datahaven"
version = "0.26.0"
version = "0.30.0"
[workspace]
members = [
"node",
"pallets/outbound-commitment-store",
"pallets/*",
"precompiles/*",
"primitives/bridge",
@ -40,11 +39,11 @@ pallet-evm-precompile-proxy = { path = "./precompiles/proxy", default-features =
pallet-evm-precompile-referenda = { path = "./precompiles/referenda", default-features = false }
pallet-evm-precompile-registry = { path = "./precompiles/precompile-registry", default-features = false }
pallet-external-validator-slashes = { path = "./pallets/external-validator-slashes", default-features = false }
pallet-grandpa-benchmarking = { path = "./pallets/grandpa-benchmarking", default-features = false }
pallet-external-validators = { path = "./pallets/external-validators", default-features = false }
pallet-external-validators-rewards = { path = "./pallets/external-validators-rewards", default-features = false }
pallet-outbound-commitment-store = { path = "./pallets/outbound-commitment-store", default-features = false }
pallet-proxy-genesis-companion = { path = "./pallets/proxy-genesis-companion", default-features = false }
pallet-grandpa-benchmarking = { path = "./pallets/grandpa-benchmarking", default-features = false }
pallet-session-benchmarking = { path = "./pallets/session-benchmarking", default-features = false }
# Crates.io (wasm)
@ -57,7 +56,7 @@ async-trait = { version = "0.1.42" }
blake2-rfc = { version = "0.2.18", default-features = false }
byte-slice-cast = { version = "1.2.1", default-features = false }
clap = { version = "4.5.10", features = ["derive", "env"] }
codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" }
codec = { version = "3.7.4", default-features = false, package = "parity-scale-codec" }
ethabi = { version = "2.0.0", default-features = false, package = "ethabi-decode" }
ethbloom = { version = "0.14.1", default-features = false }
ethereum-types = { version = "0.15.1", default-features = false }
@ -83,7 +82,7 @@ openssl-sys = { version = "0.9", features = [
"vendored",
] } # This is just to set the "vendored" feature required for the crossbuild, so that OpenSSL builds from source
parity-bytes = { version = "0.1.2", default-features = false }
parity-scale-codec = { version = "3.0.0", default-features = false, features = [
parity-scale-codec = { version = "3.7.4", default-features = false, features = [
"derive",
] }
paste = "1.0.14"
@ -108,117 +107,117 @@ tracing-subscriber = { version = "=0.3.19", features = [
url = "2.2.2"
# Polkadot SDK
cumulus-primitives-core = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
frame-executive = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
frame-metadata-hash-extension = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
frame-support = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
frame-support-test = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
frame-system = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
frame-system-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
frame-try-runtime = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
mmr-gadget = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
mmr-rpc = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-assets = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-authorship = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-babe = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-beefy = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-beefy-mmr = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-collator-selection = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2412-6", default-features = false }
pallet-collective = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-conviction-voting = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-identity = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-im-online = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-message-queue = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-migrations = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-mmr = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-nfts = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-offences = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-parameters = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-referenda = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-safe-mode = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-scheduler = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-session = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-treasury = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-tx-pause = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-whitelist = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
pallet-xcm = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
parachain-info = { package = "staging-parachain-info", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2412-6", default-features = false }
parachains-common = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2412-6", default-features = false }
polkadot-parachain-primitives = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
polkadot-primitives = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-cli = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-consensus = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-consensus-babe = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-consensus-beefy = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-consensus-beefy-rpc = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-consensus-manual-seal = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-executor = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-network = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-network-sync = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-offchain = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-rpc = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-service = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-telemetry = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sc-transaction-pool-api = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-api = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-application-crypto = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-arithmetic = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-block-builder = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-blockchain = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-consensus = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-consensus-babe = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-consensus-beefy = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-consensus-slots = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-core = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-crypto-hashing = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-genesis-builder = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-inherents = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-io = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-keystore = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-mmr-primitives = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-offchain = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-runtime-interface = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-session = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-staking = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-std = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-storage = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-timestamp = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-tracing = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-trie = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
sp-version = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
substrate-build-script-utils = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
substrate-frame-rpc-system = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
substrate-prometheus-endpoint = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
xcm = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false, package = "staging-xcm" }
xcm-builder = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false, package = "staging-xcm-builder" }
xcm-executor = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false, package = "staging-xcm-executor" }
cumulus-primitives-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
frame-executive = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
frame-metadata-hash-extension = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
frame-support-test = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
frame-system-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
frame-try-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
mmr-gadget = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
mmr-rpc = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-assets = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-authorship = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-babe = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-beefy = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-beefy-mmr = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-collator-selection = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "stable2503", default-features = false }
pallet-collective = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-conviction-voting = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-identity = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-im-online = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-message-queue = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-migrations = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-mmr = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-nfts = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-offences = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-parameters = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-referenda = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-safe-mode = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-scheduler = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-session = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-staking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-treasury = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-tx-pause = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-whitelist = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
pallet-xcm = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
parachain-info = { package = "staging-parachain-info", git = "https://github.com/paritytech/polkadot-sdk.git", branch = "stable2503", default-features = false }
parachains-common = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "stable2503", default-features = false }
polkadot-parachain-primitives = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
polkadot-primitives = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-cli = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-consensus = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-consensus-babe = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-consensus-beefy = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-consensus-beefy-rpc = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-consensus-manual-seal = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-executor = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-network = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-network-sync = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-offchain = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-rpc = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-service = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-telemetry = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sc-transaction-pool-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-application-crypto = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-arithmetic = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-block-builder = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-blockchain = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-consensus = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-consensus-babe = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-consensus-beefy = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-consensus-slots = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-crypto-hashing = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-genesis-builder = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-inherents = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-keystore = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-mmr-primitives = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-offchain = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-runtime-interface = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-session = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-staking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-storage = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-timestamp = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-tracing = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-trie = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
sp-version = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
substrate-build-script-utils = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
substrate-frame-rpc-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
substrate-prometheus-endpoint = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
xcm = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false, package = "staging-xcm" }
xcm-builder = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false, package = "staging-xcm-builder" }
xcm-executor = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false, package = "staging-xcm-executor" }
# Snowbridge
bp-relayers = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
bp-relayers = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
bridge-hub-common = { path = "primitives/snowbridge/bridge-hub-common", default-features = false }
snowbridge-beacon-primitives = { path = "primitives/snowbridge/beacon", default-features = false }
snowbridge-core = { path = "primitives/snowbridge/core", default-features = false }
@ -231,7 +230,7 @@ snowbridge-pallet-ethereum-client = { path = "pallets/ethereum-client", default-
snowbridge-pallet-ethereum-client-fixtures = { path = "pallets/ethereum-client/fixtures", default-features = false }
snowbridge-pallet-inbound-queue-v2 = { path = "pallets/inbound-queue-v2", default-features = false }
snowbridge-pallet-inbound-queue-v2-fixtures = { path = "pallets/inbound-queue-v2/fixtures", default-features = false }
snowbridge-pallet-outbound-queue = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
snowbridge-pallet-outbound-queue = { git = "https://github.com/paritytech/polkadot-sdk", branch = "stable2503", default-features = false }
snowbridge-pallet-outbound-queue-v2 = { path = "pallets/outbound-queue-v2", default-features = false }
snowbridge-pallet-system = { path = "pallets/system", default-features = false }
snowbridge-pallet-system-v2 = { path = "pallets/system-v2", default-features = false }
@ -240,74 +239,77 @@ snowbridge-test-utils = { path = "primitives/snowbridge/test-utils", default-fea
snowbridge-verification-primitives = { path = "primitives/snowbridge/verification", default-features = false }
# Frontier (wasm)
evm = { git = "https://github.com/rust-ethereum/evm", rev = "6d86fe2d3bcc14887c2575f62958a67ac2d523db", default-features = false }
fp-account = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
fp-evm = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
fp-rpc = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
fp-self-contained = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
fp-storage = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
pallet-base-fee = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
pallet-dynamic-fee = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
pallet-ethereum = { git = "https://github.com/polkadot-evm/frontier/", branch = "stable2412", default-features = false }
pallet-evm = { git = "https://github.com/polkadot-evm/frontier/", branch = "stable2412", default-features = false }
pallet-evm-chain-id = { git = "https://github.com/polkadot-evm/frontier/", branch = "stable2412", default-features = false }
pallet-evm-precompile-blake2 = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
pallet-evm-precompile-bn128 = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
pallet-evm-precompile-modexp = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
pallet-evm-precompile-sha3fips = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
pallet-evm-precompile-simple = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
pallet-hotfix-sufficients = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
precompile-utils = { git = "https://github.com/polkadot-evm/frontier/", branch = "stable2412", default-features = false }
precompile-utils-macro = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
evm = { version = "0.43.2", default-features = false }
fp-account = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
fp-evm = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
fp-rpc = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
fp-self-contained = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
fp-storage = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
pallet-base-fee = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
pallet-dynamic-fee = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
pallet-ethereum = { git = "https://github.com/polkadot-evm/frontier/", branch = "stable2503", default-features = false }
pallet-evm = { git = "https://github.com/polkadot-evm/frontier/", branch = "stable2503", default-features = false }
pallet-evm-chain-id = { git = "https://github.com/polkadot-evm/frontier/", branch = "stable2503", default-features = false }
pallet-evm-precompile-blake2 = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
pallet-evm-precompile-bn128 = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
pallet-evm-precompile-modexp = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
pallet-evm-precompile-sha3fips = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
pallet-evm-precompile-simple = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
pallet-hotfix-sufficients = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
precompile-utils = { git = "https://github.com/polkadot-evm/frontier/", branch = "stable2503", default-features = false }
precompile-utils-macro = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
ethereum = { version = "0.18.2", default-features = false, features = [
"with-scale",
] }
# Frontier (client)
fc-api = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
fc-cli = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
fc-consensus = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
fc-db = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412" }
fc-mapping-sync = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
fc-rpc = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
fc-rpc-core = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
fc-storage = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false }
fc-api = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
fc-cli = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
fc-consensus = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
fc-db = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503" }
fc-mapping-sync = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
fc-rpc = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
fc-rpc-core = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
fc-storage = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2503", default-features = false }
# StorageHub
## Runtime
pallet-bucket-nfts = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
pallet-cr-randomness = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
pallet-file-system = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
pallet-file-system-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
pallet-payment-streams = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
pallet-payment-streams-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
pallet-proofs-dealer = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
pallet-proofs-dealer-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
pallet-randomness = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
pallet-storage-providers = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
pallet-storage-providers-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shp-constants = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shp-data-price-updater = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shp-file-key-verifier = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shp-file-metadata = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shp-forest-verifier = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shp-traits = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shp-treasury-funding = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
pallet-bucket-nfts = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
pallet-cr-randomness = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
pallet-file-system = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
pallet-file-system-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
pallet-payment-streams = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
pallet-payment-streams-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
pallet-proofs-dealer = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
pallet-proofs-dealer-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
pallet-randomness = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
pallet-storage-providers = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
pallet-storage-providers-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shp-constants = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shp-data-price-updater = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shp-file-key-verifier = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shp-file-metadata = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shp-forest-verifier = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shp-traits = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shp-treasury-funding = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
## Client
shc-actors-derive = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shc-actors-framework = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shc-blockchain-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shc-client = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shc-common = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shc-file-manager = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shc-file-transfer-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shc-fisherman-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shc-forest-manager = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shc-indexer-db = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shc-indexer-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shc-rpc = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shp-opaque = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shp-tx-implicits-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shp-types = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
shc-actors-derive = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shc-actors-framework = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shc-blockchain-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shc-client = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shc-common = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shc-file-manager = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shc-file-transfer-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shc-fisherman-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shc-forest-manager = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shc-indexer-db = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shc-indexer-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shc-rpc = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shp-opaque = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shp-tx-implicits-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
shp-types = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
## Precompiles
pallet-evm-precompile-file-system = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
pallet-evm-precompile-file-system = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.5.1", default-features = false }
# Static linking

View file

@ -18,7 +18,7 @@
#![allow(unused_imports)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use sp_std::marker::PhantomData;
use core::marker::PhantomData;
/// Weights for `{{pallet}}`.
pub struct WeightInfo<T>(PhantomData<T>);

View file

@ -147,6 +147,7 @@ pub fn create_benchmark_extrinsic<RuntimeApi>(
frame_system::CheckWeight::<runtime::Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<runtime::Runtime>::from(0),
frame_metadata_hash_extension::CheckMetadataHash::<runtime::Runtime>::new(false),
frame_system::WeightReclaim::<runtime::Runtime>::new(),
);
let raw_payload = runtime::SignedPayload::from_raw(
@ -162,6 +163,7 @@ pub fn create_benchmark_extrinsic<RuntimeApi>(
(),
(),
None,
(),
),
);
let signature = raw_payload.using_encoded(|e| sender.sign(e));

View file

@ -427,78 +427,72 @@ pub fn run() -> sc_cli::Result<()> {
};
match config.network.network_backend {
// TODO: Litep2p becomes standard with Polkadot SDK stable2412-7 (should move None to other arm)
// cfr. https://github.com/paritytech/polkadot-sdk/releases/tag/polkadot-stable2412-7
Some(sc_network::config::NetworkBackendType::Libp2p) | None => {
match config.chain_spec {
ref spec if spec.is_mainnet() => {
service::new_full::<
datahaven_mainnet_runtime::Runtime,
datahaven_mainnet_runtime::RuntimeApi,
sc_network::NetworkWorker<_, _>,
>(
config, cli.eth, role_options, indexer_options, sealing_mode
)
.await
}
ref spec if spec.is_testnet() => {
service::new_full::<
datahaven_testnet_runtime::Runtime,
datahaven_testnet_runtime::RuntimeApi,
sc_network::NetworkWorker<_, _>,
>(
config, cli.eth, role_options, indexer_options, sealing_mode
)
.await
}
_ => {
service::new_full::<
datahaven_stagenet_runtime::Runtime,
datahaven_stagenet_runtime::RuntimeApi,
sc_network::NetworkWorker<_, _>,
>(
config, cli.eth, role_options, indexer_options, sealing_mode
)
.await
}
sc_network::config::NetworkBackendType::Libp2p => match config.chain_spec {
ref spec if spec.is_mainnet() => {
service::new_full::<
datahaven_mainnet_runtime::Runtime,
datahaven_mainnet_runtime::RuntimeApi,
sc_network::NetworkWorker<_, _>,
>(
config, cli.eth, role_options, indexer_options, sealing_mode
)
.await
}
.map_err(sc_cli::Error::Service)
}
Some(sc_network::config::NetworkBackendType::Litep2p) => {
match config.chain_spec {
ref spec if spec.is_mainnet() => {
service::new_full::<
datahaven_mainnet_runtime::Runtime,
datahaven_mainnet_runtime::RuntimeApi,
sc_network::Litep2pNetworkBackend,
>(
config, cli.eth, role_options, indexer_options, sealing_mode
)
.await
}
ref spec if spec.is_testnet() => {
service::new_full::<
datahaven_testnet_runtime::Runtime,
datahaven_testnet_runtime::RuntimeApi,
sc_network::Litep2pNetworkBackend,
>(
config, cli.eth, role_options, indexer_options, sealing_mode
)
.await
}
_ => {
service::new_full::<
datahaven_stagenet_runtime::Runtime,
datahaven_stagenet_runtime::RuntimeApi,
sc_network::Litep2pNetworkBackend,
>(
config, cli.eth, role_options, indexer_options, sealing_mode
)
.await
}
ref spec if spec.is_testnet() => {
service::new_full::<
datahaven_testnet_runtime::Runtime,
datahaven_testnet_runtime::RuntimeApi,
sc_network::NetworkWorker<_, _>,
>(
config, cli.eth, role_options, indexer_options, sealing_mode
)
.await
}
_ => {
service::new_full::<
datahaven_stagenet_runtime::Runtime,
datahaven_stagenet_runtime::RuntimeApi,
sc_network::NetworkWorker<_, _>,
>(
config, cli.eth, role_options, indexer_options, sealing_mode
)
.await
}
.map_err(sc_cli::Error::Service)
}
.map_err(sc_cli::Error::Service),
sc_network::config::NetworkBackendType::Litep2p => match config.chain_spec {
ref spec if spec.is_mainnet() => {
service::new_full::<
datahaven_mainnet_runtime::Runtime,
datahaven_mainnet_runtime::RuntimeApi,
sc_network::Litep2pNetworkBackend,
>(
config, cli.eth, role_options, indexer_options, sealing_mode
)
.await
}
ref spec if spec.is_testnet() => {
service::new_full::<
datahaven_testnet_runtime::Runtime,
datahaven_testnet_runtime::RuntimeApi,
sc_network::Litep2pNetworkBackend,
>(
config, cli.eth, role_options, indexer_options, sealing_mode
)
.await
}
_ => {
service::new_full::<
datahaven_stagenet_runtime::Runtime,
datahaven_stagenet_runtime::RuntimeApi,
sc_network::Litep2pNetworkBackend,
>(
config, cli.eth, role_options, indexer_options, sealing_mode
)
.await
}
}
.map_err(sc_cli::Error::Service),
}
})
}

View file

@ -40,7 +40,7 @@ use sc_consensus_beefy::communication::notification::{
};
use sc_consensus_manual_seal::rpc::{EngineCommand, ManualSeal, ManualSealApiServer};
use sc_network_sync::SyncingService;
use sc_transaction_pool::{ChainApi, Pool};
use sc_transaction_pool::ChainApi;
use sc_transaction_pool_api::TransactionPool;
use shc_client::types::FileStorageT;
use shc_common::traits::StorageEnableRuntime;
@ -54,7 +54,7 @@ use shc_rpc::StorageHubClientRpcConfig;
use sp_consensus_babe::{BabeApi, SlotDuration};
use sp_consensus_beefy::AuthorityIdBound;
use sp_core::H256;
use sp_runtime::traits::BlakeTwo256;
use sp_runtime::traits::{BlakeTwo256, Block as BlockT, Header as HeaderT};
use std::collections::BTreeMap;
use std::sync::Arc;
@ -69,9 +69,10 @@ pub struct BeefyDeps<AuthorityId: AuthorityIdBound> {
}
/// Full client dependencies.
pub struct FullDeps<P, B, AuthorityId: AuthorityIdBound, A: ChainApi, FL, FS, Runtime>
pub struct FullDeps<P, B, AuthorityId: AuthorityIdBound, FL, FS, Runtime>
where
Runtime: StorageEnableRuntime,
FS: ForestStorageHandler<Runtime> + Clone + Send + Sync + 'static,
{
/// The client instance to use.
pub client: Arc<StorageHubClient<Runtime::RuntimeApi>>,
@ -80,7 +81,7 @@ where
/// BEEFY dependencies.
pub beefy: BeefyDeps<AuthorityId>,
/// Graph pool instance.
pub graph: Arc<Pool<A>>,
pub graph: Arc<P>,
/// Backend used by the node.
pub backend: Arc<B>,
/// Network service
@ -112,8 +113,8 @@ where
}
/// Instantiate all full RPC extensions.
pub fn create_full<P, BE, AuthorityId, A, FL, FSH, Runtime>(
deps: FullDeps<P, BE, AuthorityId, A, FL, FSH, Runtime>,
pub fn create_full<P, BE, AuthorityId, FL, FSH, Runtime>(
deps: FullDeps<P, BE, AuthorityId, FL, FSH, Runtime>,
subscription_task_executor: sc_rpc::SubscriptionTaskExecutor,
pubsub_notification_sinks: Arc<
fc_mapping_sync::EthereumBlockNotificationSinks<
@ -122,11 +123,10 @@ pub fn create_full<P, BE, AuthorityId, A, FL, FSH, Runtime>(
>,
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
where
P: TransactionPool<Block = Block> + 'static,
P: TransactionPool<Block = Block, Hash = <Block as BlockT>::Hash> + 'static,
BE: Backend<Block> + Send + Sync + 'static,
BE::State: StateBackend<BlakeTwo256>,
AuthorityId: AuthorityIdBound,
A: ChainApi<Block = Block> + 'static,
Runtime: StorageEnableRuntime,
Runtime::RuntimeApi: StorageEnableRuntimeApi<
RuntimeApi: mmr_rpc::MmrRuntimeApi<
@ -139,7 +139,7 @@ where
>,
StorageHubClient<Runtime::RuntimeApi>: StorageProvider<Block, BE>,
FL: FileStorageT,
FSH: ForestStorageHandler<Runtime> + Send + Sync + 'static,
FSH: ForestStorageHandler<Runtime> + Clone + Send + Sync + 'static,
{
use mmr_rpc::{Mmr, MmrApiServer};
use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer};
@ -225,7 +225,7 @@ where
};
module.merge(
Eth::<_, _, _, _, _, _, _, DefaultEthConfig<StorageHubClient<Runtime::RuntimeApi>, BE>>::new(
Eth::<_, _, _, _, _, _, DefaultEthConfig<StorageHubClient<Runtime::RuntimeApi>, BE>>::new(
Arc::clone(&client),
Arc::clone(&pool),
graph.clone(),

View file

@ -572,7 +572,7 @@ where
Vec::default(),
));
let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) =
let (network, system_rpc_tx, tx_handler_controller, sync_service) =
sc_service::build_network(sc_service::BuildNetworkParams {
config: &config,
net_config,
@ -698,7 +698,7 @@ where
let deps = crate::rpc::FullDeps {
client: client.clone(),
pool: pool.clone(),
graph: pool.pool().clone(),
graph: pool.clone(),
beefy: BeefyDeps::<BeefyId> {
beefy_finality_proof_stream: beefy_rpc_links
.from_voter_justif_stream
@ -996,7 +996,6 @@ where
.await?;
}
network_starter.start_network();
Ok(task_manager)
}
@ -1375,7 +1374,6 @@ where
.with_blockchain(
client.clone(),
keystore.clone(),
Arc::new(rpc_handlers),
rocks_db_path.clone(),
maintenance_mode,
)

View file

@ -20,7 +20,6 @@ frame-support = { workspace = true }
frame-system = { workspace = true }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
snowbridge-core = { workspace = true }
snowbridge-outbound-queue-primitives = { workspace = true }
@ -48,7 +47,6 @@ std = [
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
"xcm/std",
"xcm-builder/std",
"pallet-balances/std",

View file

@ -29,7 +29,9 @@
//! It uses a dedicated Ethereum sovereign account to hold locked tokens during transfers.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::vec;
use frame_support::{
pallet_prelude::*,
traits::{
@ -41,7 +43,6 @@ use snowbridge_core::TokenId;
use snowbridge_outbound_queue_primitives::v2::{Command, Message as OutboundMessage, SendMessage};
use sp_core::{H160, H256};
use sp_runtime::{traits::Saturating, BoundedVec};
use sp_std::vec;
pub use pallet::*;

View file

@ -166,9 +166,12 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
(BOB, INITIAL_BALANCE),
(CHARLIE, INITIAL_BALANCE),
];
pallet_balances::GenesisConfig::<Test> { balances }
.assimilate_storage(&mut t)
.unwrap();
pallet_balances::GenesisConfig::<Test> {
balances,
dev_accounts: Default::default(),
}
.assimilate_storage(&mut t)
.unwrap();
let mut ext: sp_io::TestExternalities = t.into();
ext.execute_with(|| {

View file

@ -28,7 +28,6 @@ frame-system = { workspace = true }
sp-core = { workspace = true }
sp-io = { optional = true, workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
pallet-timestamp = { optional = true, workspace = true }
snowbridge-beacon-primitives = { workspace = true }
@ -77,7 +76,6 @@ std = [
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
'frame-benchmarking/std',
]
try-runtime = [

View file

@ -20,7 +20,6 @@ snowbridge-beacon-primitives = { workspace = true }
snowbridge-core = { workspace = true }
snowbridge-inbound-queue-primitives = { workspace = true }
sp-core = { workspace = true }
sp-std = { workspace = true }
[features]
default = ["std"]
@ -33,5 +32,4 @@ std = [
"snowbridge-core/std",
"snowbridge-inbound-queue-primitives/std",
"sp-core/std",
"sp-std/std",
]

View file

@ -3,7 +3,9 @@
// Generated, do not edit!
// See README.md for instructions to generate
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::{boxed::Box, vec};
use hex_literal::hex;
use snowbridge_beacon_primitives::{
types::deneb, AncestryProof, BeaconHeader, ExecutionProof, NextSyncCommitteeUpdate,
@ -11,7 +13,6 @@ use snowbridge_beacon_primitives::{
};
use snowbridge_inbound_queue_primitives::{EventProof, InboundQueueFixture, Log, Proof};
use sp_core::U256;
use sp_std::{boxed::Box, vec};
const SC_SIZE: usize = 512;
const SC_BITS_SIZE: usize = 64;

View file

@ -2,8 +2,9 @@
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use crate::{
decompress_sync_committee_bits, Config, CurrentSyncCommittee, Pallet as EthereumBeaconClient,
Update, ValidatorsRoot, Vec,
Update, ValidatorsRoot,
};
use alloc::vec::Vec;
use snowbridge_beacon_primitives::PublicKeyPrepared;
use sp_core::H256;

View file

@ -4,6 +4,7 @@ use super::*;
use frame_support::ensure;
use snowbridge_beacon_primitives::ExecutionProof;
use alloc::vec::Vec;
use snowbridge_beacon_primitives::merkle_proof::{generalized_index_length, subtree_index};
use snowbridge_ethereum::Receipt;
use snowbridge_inbound_queue_primitives::{

View file

@ -16,6 +16,7 @@
//!
//! * [`Call::submit`]: Submit a finalized beacon header with an optional sync committee update
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
pub mod config;
pub mod functions;
@ -38,6 +39,7 @@ mod tests_electra;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
use alloc::boxed::Box;
use frame_support::{
dispatch::{DispatchResult, PostDispatchInfo},
pallet_prelude::OptionQuery,
@ -53,7 +55,6 @@ use snowbridge_beacon_primitives::{
};
use snowbridge_core::{BasicOperatingMode, RingBufferMap};
use sp_core::H256;
use sp_std::prelude::*;
pub use weights::WeightInfo;
use functions::{
@ -71,6 +72,7 @@ pub const LOG_TARGET: &str = "ethereum-client";
pub mod pallet {
use super::*;
use alloc::vec::Vec;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;

View file

@ -2,11 +2,11 @@
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use crate as ethereum_beacon_client;
use crate::config;
use alloc::default::Default;
use frame_support::{derive_impl, dispatch::DispatchResult, parameter_types};
use pallet_timestamp;
use snowbridge_beacon_primitives::{Fork, ForkVersions};
use snowbridge_inbound_queue_primitives::{Log, Proof};
use sp_std::default::Default;
use std::{fs::File, path::PathBuf};
type Block = frame_system::mocking::MockBlock<Test>;
@ -136,6 +136,10 @@ parameter_types! {
},
electra: Fork {
version: [5, 0, 0, 0], // 0x05000000
epoch: 0,
},
fulu: Fork {
version: [6, 0, 0, 0], // 0x06000000
epoch: 80000000000,
}
};

View file

@ -2,11 +2,11 @@
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use crate as ethereum_beacon_client;
use crate::config;
use alloc::default::Default;
use frame_support::{derive_impl, dispatch::DispatchResult, parameter_types};
use pallet_timestamp;
use snowbridge_beacon_primitives::{Fork, ForkVersions};
use snowbridge_inbound_queue_primitives::{Log, Proof};
use sp_std::default::Default;
use std::{fs::File, path::PathBuf};
type Block = frame_system::mocking::MockBlock<Test>;
@ -117,6 +117,10 @@ parameter_types! {
electra: Fork {
version: [5, 0, 0, 0], // 0x05000000
epoch: 0,
},
fulu: Fork {
version: [6, 0, 0, 0], // 0x06000000
epoch: 0,
}
};
}

View file

@ -248,6 +248,10 @@ fn compute_fork_version() {
version: [0, 0, 0, 5],
epoch: 50,
},
fulu: Fork {
version: [0, 0, 0, 6],
epoch: 60,
},
};
new_tester().execute_with(|| {
assert_eq!(

View file

@ -247,6 +247,10 @@ fn compute_fork_version() {
version: [0, 0, 0, 5],
epoch: 50,
},
fulu: Fork {
version: [0, 0, 0, 6],
epoch: 60,
},
};
new_tester().execute_with(|| {
assert_eq!(
@ -281,6 +285,10 @@ fn compute_fork_version() {
EthereumBeaconClient::select_fork_version(&mock_fork_versions, 50),
[0, 0, 0, 5]
);
assert_eq!(
EthereumBeaconClient::select_fork_version(&mock_fork_versions, 60),
[0, 0, 0, 6]
);
});
}

View file

@ -29,7 +29,7 @@
#![allow(unused_imports)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use sp_std::marker::PhantomData;
use core::marker::PhantomData;
/// Weight functions needed for ethereum_beacon_client.
pub trait WeightInfo {

View file

@ -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",

View file

@ -35,20 +35,24 @@ const MAX_SLASHES: u32 = 1000;
mod benchmarks {
use super::*;
fn dummy_slash<T: Config>(slash_id: T::SlashId) -> Slash<T::AccountId, T::SlashId> {
let dummy = || T::AccountId::decode(&mut TrailingZeroInput::zeroes()).unwrap();
Slash {
validator: dummy(),
reporters: vec![],
slash_id,
percentage: Perbill::from_percent(1),
confirmed: false,
offence_kind: OffenceKind::LivenessOffence,
}
}
#[benchmark]
fn cancel_deferred_slash(s: Linear<1, MAX_SLASHES>) -> Result<(), BenchmarkError> {
let mut existing_slashes = Vec::new();
let era = T::EraIndexProvider::active_era().index;
let dummy = || T::AccountId::decode(&mut TrailingZeroInput::zeroes()).unwrap();
for _ in 0..MAX_SLASHES {
existing_slashes.push(Slash {
validator: dummy(),
reporters: vec![],
slash_id: One::one(),
percentage: Perbill::from_percent(1),
confirmed: false,
offence_kind: OffenceKind::LivenessOffence,
});
existing_slashes.push(dummy_slash::<T>(One::one()));
}
Slashes::<T>::insert(
era.saturating_add(T::SlashDeferDuration::get())
@ -102,35 +106,55 @@ mod benchmarks {
#[benchmark]
fn process_slashes_queue(s: Linear<1, 200>) -> Result<(), BenchmarkError> {
let mut queue = VecDeque::new();
let dummy = || T::AccountId::decode(&mut TrailingZeroInput::zeroes()).unwrap();
let first_batch = (0..s)
.map(|_| dummy_slash::<T>(One::one()))
.collect::<Vec<_>>();
let second_batch = vec![dummy_slash::<T>(One::one())];
for _ in 0..(s + 1) {
queue.push_back(Slash {
validator: dummy(),
reporters: vec![],
slash_id: One::one(),
percentage: Perbill::from_percent(1),
confirmed: false,
offence_kind: OffenceKind::LivenessOffence,
});
}
UnreportedSlashesQueue::<T>::set(queue);
assert!(ExternalValidatorSlashes::<T>::unsent_queue_push((
1,
first_batch
)));
assert!(ExternalValidatorSlashes::<T>::unsent_queue_push((
2,
second_batch
)));
let processed;
#[block]
{
processed = Pallet::<T>::process_slashes_queue(s).unwrap();
processed = match Pallet::<T>::process_slashes_queue() {
crate::ProcessSlashesQueueOutcome::Sent(count) => count,
crate::ProcessSlashesQueueOutcome::Empty
| crate::ProcessSlashesQueueOutcome::Requeued(_) => {
return Err(BenchmarkError::Stop("unexpected slashes queue outcome"))
}
};
}
assert_eq!(UnreportedSlashesQueue::<T>::get().len(), 1);
assert_eq!(ExternalValidatorSlashes::<T>::unsent_queue_len(), 1);
assert_eq!(processed, s);
Ok(())
}
#[benchmark]
fn retry_unsent_slash_era() -> Result<(), BenchmarkError> {
let batch = vec![dummy_slash::<T>(One::one())];
assert!(ExternalValidatorSlashes::<T>::unsent_queue_push((1, batch)));
let origin =
T::GovernanceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
#[extrinsic_call]
_(origin as T::RuntimeOrigin, 1u32);
assert!(ExternalValidatorSlashes::<T>::unsent_queue_is_empty());
Ok(())
}
#[benchmark]
fn set_slashing_mode() -> Result<(), BenchmarkError> {
#[extrinsic_call]

View file

@ -31,7 +31,7 @@ extern crate alloc;
use pallet_external_validators::apply;
use snowbridge_outbound_queue_primitives::SendError;
use {
alloc::{collections::vec_deque::VecDeque, string::String, vec, vec::Vec},
alloc::{string::String, vec, vec::Vec},
frame_support::{pallet_prelude::*, traits::DefensiveSaturating},
frame_system::pallet_prelude::*,
log::log,
@ -132,10 +132,21 @@ pub mod pallet {
},
/// The slashes message was sent correctly.
SlashesMessageSent { message_id: H256 },
/// The slashes message failed to send and the batch was moved to the back
/// of the queue for retry.
SlashesMessageSendFailed { era: EraIndex, count: u32 },
/// A queued slashes batch was retried manually and sent successfully.
SlashesMessageRetried {
message_id: H256,
era: EraIndex,
count: u32,
},
/// We injected a slash
SlashInjected { slash_id: T::SlashId, era: u32 },
/// Number of slashes processed
SlashAddedToQueue { number: u32, era: u32 },
/// The unsent queue is full; this slash era could not be enqueued.
UnsentQueueFull { era: EraIndex },
}
#[pallet::config]
@ -199,6 +210,9 @@ pub mod pallet {
/// The weight information of this pallet.
type WeightInfo: WeightInfo;
/// Origin for governance calls such as retrying an unsent slash batch.
type GovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
}
#[pallet::error]
@ -226,6 +240,10 @@ pub mod pallet {
/// No PendingOffenceKind found for (session, validator) — offence was not
/// reported through EquivocationReportWrapper, so the offence kind is unknown.
MissingOffenceKind,
/// The specified era is not in the unsent slash queue.
EraNotInUnsentQueue,
/// The message delivery still failed on retry.
MessageSendFailed,
}
#[apply(derive_storage_traits)]
@ -269,12 +287,26 @@ pub mod pallet {
pub type Slashes<T: Config> =
StorageMap<_, Twox64Concat, EraIndex, Vec<Slash<T::AccountId, T::SlashId>>, ValueQuery>;
/// All unreported slashes that will be processed in the future.
/// Maximum number of unsent slash batches in the retry ring buffer.
pub const UNSENT_QUEUE_CAPACITY: u32 = 64;
/// Ring buffer of slash batches whose outbound message still needs to be sent.
/// Each slot stores the original slash era together with a bounded-size batch
/// of slash records. Retries keep the original era so the outbound message id
/// remains stable across later blocks and eras.
#[pallet::storage]
#[pallet::unbounded]
#[pallet::getter(fn unreported_slashes)]
pub type UnreportedSlashesQueue<T: Config> =
StorageValue<_, VecDeque<Slash<T::AccountId, T::SlashId>>, ValueQuery>;
pub type UnsentSlashBatch<T: Config> =
StorageMap<_, Twox64Concat, u32, (EraIndex, Vec<Slash<T::AccountId, T::SlashId>>)>;
/// Ring buffer head: next slot to be processed by `on_initialize`.
#[pallet::storage]
pub type UnsentSlashHead<T: Config> = StorageValue<_, u32, ValueQuery>;
/// Ring buffer tail: next slot to write a new entry into.
/// When head == tail the buffer is empty.
#[pallet::storage]
pub type UnsentSlashTail<T: Config> = StorageValue<_, u32, ValueQuery>;
// Turns slashing on or off
#[pallet::storage]
@ -415,6 +447,44 @@ pub mod pallet {
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::retry_unsent_slash_era())]
pub fn retry_unsent_slash_era(origin: OriginFor<T>, era_index: EraIndex) -> DispatchResult {
T::GovernanceOrigin::ensure_origin(origin)?;
let head = UnsentSlashHead::<T>::get();
let tail = UnsentSlashTail::<T>::get();
let mut found = None;
let mut slot = head;
while slot != tail {
if let Some(entry @ (idx, _)) = UnsentSlashBatch::<T>::get(slot) {
if idx == era_index {
found = Some((slot, entry));
break;
}
}
slot = (slot + 1) % UNSENT_QUEUE_CAPACITY;
}
let (slot, (era, slashes)) = found.ok_or(Error::<T>::EraNotInUnsentQueue)?;
let count = slashes.len() as u32;
let slashes_to_send = slashes
.iter()
.map(Self::slash_to_send_data)
.collect::<Vec<_>>();
let message_id = Self::send_slashes_message(&slashes_to_send, era)
.ok_or(Error::<T>::MessageSendFailed)?;
Self::unsent_queue_remove_slot(slot);
Self::deposit_event(Event::<T>::SlashesMessageRetried {
message_id,
era,
count,
});
Ok(())
}
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::set_slashing_mode())]
pub fn set_slashing_mode(origin: OriginFor<T>, mode: SlashingModeOption) -> DispatchResult {
@ -429,12 +499,12 @@ pub mod pallet {
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
let processed = Self::process_slashes_queue(T::QueuedSlashesProcessedPerBlock::get());
if let Some(p) = processed {
T::WeightInfo::process_slashes_queue(p)
} else {
T::WeightInfo::process_slashes_queue(0)
match Self::process_slashes_queue() {
ProcessSlashesQueueOutcome::Empty => T::WeightInfo::process_slashes_queue(0),
ProcessSlashesQueueOutcome::Sent(count)
| ProcessSlashesQueueOutcome::Requeued(count) => {
T::WeightInfo::process_slashes_queue(count)
}
}
}
}
@ -655,70 +725,65 @@ where
impl<T: Config> Pallet<T> {
fn add_era_slashes_to_queue(active_era: EraIndex) {
let mut slashes: VecDeque<_> = Slashes::<T>::get(active_era).into();
let slashes = Slashes::<T>::get(active_era);
if slashes.is_empty() {
return;
}
let len = slashes.len();
let batch_size = T::QueuedSlashesProcessedPerBlock::get().max(1) as usize;
let mut enqueued = 0u32;
UnreportedSlashesQueue::<T>::mutate(|queue| queue.append(&mut slashes));
for batch in slashes.chunks(batch_size) {
if Self::unsent_queue_push((active_era, batch.to_vec())) {
enqueued = enqueued.saturating_add(batch.len() as u32);
} else {
log::warn!(
target: "ext_validators_slashes",
"Unsent slash queue full, cannot enqueue era {active_era}",
);
Self::deposit_event(Event::<T>::UnsentQueueFull { era: active_era });
break;
}
}
if len > 0 {
if enqueued > 0 {
Self::deposit_event(Event::<T>::SlashAddedToQueue {
number: len as u32,
number: enqueued,
era: active_era,
});
}
}
/// Returns number of slashes that were sent to ethereum.
fn process_slashes_queue(amount: u32) -> Option<u32> {
let mut slashes_to_send: Vec<SlashData<T::AccountId>> = vec![];
let era_index = T::EraIndexProvider::active_era().index;
fn slash_to_send_data(slash: &Slash<T::AccountId, T::SlashId>) -> SlashData<T::AccountId> {
// Keep the original slash batch intact until delivery succeeds so failed
// batches can be moved to the back of the queue instead of being dropped.
let max_wad = T::MaxSlashWad::get();
let wad_to_slash = (slash.percentage.deconstruct() as u128)
.saturating_mul(max_wad)
.checked_div(1_000_000_000u128)
.unwrap_or(0)
.min(max_wad);
UnreportedSlashesQueue::<T>::mutate(|queue| {
for _ in 0..amount {
let Some(slash) = queue.pop_front() else {
// no more slashes to process in the queue
break;
};
// Convert Perbill to EigenLayer WAD format with linear mapping.
// Perbill(100%) → MaxSlashWad (e.g. 5% WAD = 5e16).
// Formula: perbill_inner * MaxSlashWad / 1e9
// Clamp to MaxSlashWad to guard against overflow if governance
// sets MaxSlashWad high enough for saturating_mul to hit u128::MAX.
let max_wad = T::MaxSlashWad::get();
let wad_to_slash = (slash.percentage.deconstruct() as u128)
.saturating_mul(max_wad)
.checked_div(1_000_000_000u128)
.unwrap_or(0)
.min(max_wad);
slashes_to_send.push(SlashData {
validator: slash.validator,
wad_to_slash,
description: slash.offence_kind.to_description(),
});
}
});
if slashes_to_send.is_empty() {
return None;
SlashData {
validator: slash.validator.clone(),
wad_to_slash,
description: slash.offence_kind.to_description(),
}
}
let slashes_count = slashes_to_send.len() as u32;
fn send_slashes_message(
slashes_to_send: &[SlashData<T::AccountId>],
era_index: EraIndex,
) -> Option<H256> {
let outbound =
T::SendMessage::build(&slashes_to_send.to_vec(), era_index).or_else(|| {
log::warn!(target: "ext_validators_slashes", "Failed to build outbound message");
None
})?;
let outbound = match T::SendMessage::build(&slashes_to_send, era_index) {
Some(send_msg) => send_msg,
None => {
log::error!(target: "ext_validators_slashes", "Failed to build outbound message");
return None;
}
};
// Validate and deliver the message
let ticket = T::SendMessage::validate(outbound)
.map_err(|e| {
log::error!(
log::warn!(
target: "ext_validators_slashes",
"Failed to validate outbound message: {:?}",
e
@ -726,20 +791,126 @@ impl<T: Config> Pallet<T> {
})
.ok()?;
let message_id = T::SendMessage::deliver(ticket)
T::SendMessage::deliver(ticket)
.map_err(|e| {
log::error!(
log::warn!(
target: "ext_validators_slashes",
"Failed to deliver outbound message: {:?}",
e
);
})
.ok()?;
Self::deposit_event(Event::<T>::SlashesMessageSent { message_id });
Some(slashes_count)
.ok()
}
#[allow(dead_code)]
pub(crate) fn unsent_queue_is_empty() -> bool {
UnsentSlashHead::<T>::get() == UnsentSlashTail::<T>::get()
}
#[allow(dead_code)]
pub(crate) fn unsent_queue_len() -> u32 {
let head = UnsentSlashHead::<T>::get();
let tail = UnsentSlashTail::<T>::get();
tail.wrapping_sub(head) % UNSENT_QUEUE_CAPACITY
}
pub(crate) fn unsent_queue_push(
entry: (EraIndex, Vec<Slash<T::AccountId, T::SlashId>>),
) -> bool {
let head = UnsentSlashHead::<T>::get();
let tail = UnsentSlashTail::<T>::get();
let next_tail = (tail + 1) % UNSENT_QUEUE_CAPACITY;
if next_tail == head {
return false;
}
UnsentSlashBatch::<T>::insert(tail, entry);
UnsentSlashTail::<T>::put(next_tail);
true
}
fn unsent_queue_remove_slot(slot: u32) {
let tail = UnsentSlashTail::<T>::get();
let mut cur = slot;
loop {
let next = (cur + 1) % UNSENT_QUEUE_CAPACITY;
if next == tail {
break;
}
if let Some(entry) = UnsentSlashBatch::<T>::get(next) {
UnsentSlashBatch::<T>::insert(cur, entry);
}
cur = next;
}
UnsentSlashBatch::<T>::remove(cur);
let new_tail = if tail == 0 {
UNSENT_QUEUE_CAPACITY - 1
} else {
tail - 1
};
UnsentSlashTail::<T>::put(new_tail);
let head = UnsentSlashHead::<T>::get();
if head == tail {
UnsentSlashHead::<T>::put(new_tail);
}
}
/// Retry contract shared with rewards:
/// - process the current head batch,
/// - if send succeeds, remove it from the queue,
/// - if send fails, move the same batch to the back so later slash batches can progress.
pub(crate) fn process_slashes_queue() -> ProcessSlashesQueueOutcome {
let head = UnsentSlashHead::<T>::get();
let tail = UnsentSlashTail::<T>::get();
if head == tail {
return ProcessSlashesQueueOutcome::Empty;
}
let Some((era_index, slashes)) = UnsentSlashBatch::<T>::get(head) else {
UnsentSlashHead::<T>::put((head + 1) % UNSENT_QUEUE_CAPACITY);
return ProcessSlashesQueueOutcome::Empty;
};
let slashes_count = slashes.len() as u32;
let slashes_to_send = slashes
.iter()
.map(Self::slash_to_send_data)
.collect::<Vec<_>>();
match Self::send_slashes_message(&slashes_to_send, era_index) {
Some(message_id) => {
UnsentSlashBatch::<T>::remove(head);
UnsentSlashHead::<T>::put((head + 1) % UNSENT_QUEUE_CAPACITY);
Self::deposit_event(Event::<T>::SlashesMessageSent { message_id });
ProcessSlashesQueueOutcome::Sent(slashes_count)
}
None => {
UnsentSlashBatch::<T>::remove(head);
UnsentSlashHead::<T>::put((head + 1) % UNSENT_QUEUE_CAPACITY);
UnsentSlashBatch::<T>::insert(tail, (era_index, slashes));
UnsentSlashTail::<T>::put((tail + 1) % UNSENT_QUEUE_CAPACITY);
log::warn!(
target: "ext_validators_slashes",
"Failed to send {slashes_count} slash entries for era {era_index}, moved batch to back of queue",
);
Self::deposit_event(Event::<T>::SlashesMessageSendFailed {
era: era_index,
count: slashes_count,
});
ProcessSlashesQueueOutcome::Requeued(slashes_count)
}
}
}
}
pub(crate) enum ProcessSlashesQueueOutcome {
Empty,
Sent(u32),
Requeued(u32),
}
/// A pending slash record. The value of the slash has been computed but not applied yet,

View file

@ -134,7 +134,9 @@ thread_local! {
pub static SENT_ETHEREUM_MESSAGE_NONCE: RefCell<u64> = const { RefCell::new(0) };
pub static MOCK_REPORT_OFFENCE_SHOULD_FAIL: RefCell<bool> = const { RefCell::new(false) };
pub static MOCK_REPORT_OFFENCE_CALLED: RefCell<bool> = const { RefCell::new(false) };
pub static MOCK_SEND_MESSAGE_SHOULD_FAIL: RefCell<bool> = const { RefCell::new(false) };
pub static LAST_SENT_SLASHES: RefCell<Vec<crate::SlashData<AccountId>>> = RefCell::new(Vec::new());
pub static LAST_BUILT_ERA: RefCell<Option<EraIndex>> = const { RefCell::new(None) };
}
impl MockEraIndexProvider {
@ -171,6 +173,7 @@ impl pallet_session::Config for Test {
type ValidatorIdOf = ConvertInto;
type NextSessionRotation = pallet_session::PeriodicSessions<Period, Offset>;
type WeightInfo = ();
type DisablingStrategy = ();
}
sp_runtime::impl_opaque_keys! {
@ -221,19 +224,32 @@ impl MockOkOutboundQueue {
pub fn last_sent_slashes() -> Vec<crate::SlashData<AccountId>> {
LAST_SENT_SLASHES.with(|r| r.borrow().clone())
}
pub fn last_built_era() -> Option<EraIndex> {
LAST_BUILT_ERA.with(|r| *r.borrow())
}
pub fn set_should_fail(fail: bool) {
MOCK_SEND_MESSAGE_SHOULD_FAIL.with(|r| *r.borrow_mut() = fail);
}
}
impl crate::SendMessage<AccountId> for MockOkOutboundQueue {
type Ticket = ();
type Message = ();
fn build(slashes: &Vec<crate::SlashData<AccountId>>, _: u32) -> Option<Self::Ticket> {
fn build(slashes: &Vec<crate::SlashData<AccountId>>, era: u32) -> Option<Self::Ticket> {
LAST_SENT_SLASHES.with(|r| *r.borrow_mut() = slashes.clone());
LAST_BUILT_ERA.with(|r| *r.borrow_mut() = Some(era));
Some(())
}
fn validate(_: Self::Ticket) -> Result<Self::Ticket, SendError> {
Ok(())
}
fn deliver(_: Self::Ticket) -> Result<H256, SendError> {
Ok(H256::zero())
if MOCK_SEND_MESSAGE_SHOULD_FAIL.with(|r| *r.borrow()) {
Err(SendError::MessageTooLarge)
} else {
Ok(H256::zero())
}
}
}
@ -270,6 +286,7 @@ impl external_validator_slashes::Config for Test {
type QueuedSlashesProcessedPerBlock = ConstU32<20>;
type WeightInfo = ();
type SendMessage = MockOkOutboundQueue;
type GovernanceOrigin = frame_system::EnsureRoot<u64>;
}
pub struct FullIdentificationOf;
@ -285,6 +302,9 @@ impl pallet_session::historical::Config for Test {
}
// Build genesis storage according to the mock runtime.
pub fn new_test_ext() -> sp_io::TestExternalities {
MOCK_SEND_MESSAGE_SHOULD_FAIL.with(|r| *r.borrow_mut() = false);
LAST_SENT_SLASHES.with(|r| r.borrow_mut().clear());
LAST_BUILT_ERA.with(|r| *r.borrow_mut() = None);
system::GenesisConfig::<Test>::default()
.build_storage()
.unwrap()

View file

@ -28,6 +28,40 @@ use {
sp_staking::offence::ReportOffence,
};
fn queued_slash_ids() -> Vec<u32> {
let mut queued = Vec::new();
let mut slot = UnsentSlashHead::<Test>::get();
let tail = UnsentSlashTail::<Test>::get();
while slot != tail {
if let Some((_, batch)) = UnsentSlashBatch::<Test>::get(slot) {
queued.extend(batch.into_iter().map(|slash| slash.slash_id));
}
slot = (slot + 1) % UNSENT_QUEUE_CAPACITY;
}
queued
}
fn queued_batch_eras() -> Vec<u32> {
let mut queued = Vec::new();
let mut slot = UnsentSlashHead::<Test>::get();
let tail = UnsentSlashTail::<Test>::get();
while slot != tail {
if let Some((era, _)) = UnsentSlashBatch::<Test>::get(slot) {
queued.push(era);
}
slot = (slot + 1) % UNSENT_QUEUE_CAPACITY;
}
queued
}
fn unsent_queue_len() -> u32 {
ExternalValidatorSlashes::unsent_queue_len()
}
#[test]
fn root_can_inject_manual_offence() {
new_test_ext().execute_with(|| {
@ -574,14 +608,228 @@ fn test_on_offence_defer_period_0_messages_get_queued() {
assert_eq!(Slashes::<Test>::get(get_slashing_era(1)).len(), 25);
start_era(2, 2, 2);
assert_eq!(UnreportedSlashesQueue::<Test>::get().len(), 25);
assert_eq!(unsent_queue_len(), 2);
assert_eq!(queued_batch_eras(), vec![2, 2]);
// this triggers on_initialize
run_block();
assert_eq!(UnreportedSlashesQueue::<Test>::get().len(), 5);
assert_eq!(unsent_queue_len(), 1);
assert_eq!(queued_slash_ids(), (20..25).collect::<Vec<_>>());
run_block();
assert_eq!(UnreportedSlashesQueue::<Test>::get().len(), 0);
assert!(ExternalValidatorSlashes::unsent_queue_is_empty());
});
}
#[test]
fn failed_slashes_batch_is_moved_to_back_of_queue() {
new_test_ext().execute_with(|| {
crate::mock::DeferPeriodGetter::with_defer_period(0);
MockOkOutboundQueue::set_should_fail(true);
start_era(0, 0, 0);
start_era(1, 1, 1);
for i in 0..25 {
PendingOffenceKind::<Test>::insert(0, 3 + i, OffenceKind::LivenessOffence);
Pallet::<Test>::on_offence(
&[OffenceDetails {
offender: (3 + i, ()),
reporters: vec![],
}],
&[Perbill::from_percent(75)],
0,
);
}
start_era(2, 2, 2);
assert_eq!(queued_slash_ids(), (0..25).collect::<Vec<_>>());
assert_eq!(queued_batch_eras(), vec![2, 2]);
run_block();
assert_eq!(unsent_queue_len(), 2);
assert_eq!(
queued_slash_ids(),
(20..25).chain(0..20).collect::<Vec<_>>()
);
System::assert_has_event(RuntimeEvent::ExternalValidatorSlashes(
crate::Event::SlashesMessageSendFailed { era: 2, count: 20 },
));
});
}
#[test]
fn failed_slashes_batch_retries_after_send_is_reenabled() {
new_test_ext().execute_with(|| {
crate::mock::DeferPeriodGetter::with_defer_period(0);
MockOkOutboundQueue::set_should_fail(true);
start_era(0, 0, 0);
start_era(1, 1, 1);
for i in 0..25 {
PendingOffenceKind::<Test>::insert(0, 3 + i, OffenceKind::LivenessOffence);
Pallet::<Test>::on_offence(
&[OffenceDetails {
offender: (3 + i, ()),
reporters: vec![],
}],
&[Perbill::from_percent(75)],
0,
);
}
start_era(2, 2, 2);
run_block();
assert_eq!(
queued_slash_ids(),
(20..25).chain(0..20).collect::<Vec<_>>()
);
start_era(3, 3, 3);
MockOkOutboundQueue::set_should_fail(false);
run_block();
assert_eq!(unsent_queue_len(), 1);
assert_eq!(queued_slash_ids(), (0..20).collect::<Vec<_>>());
assert_eq!(MockOkOutboundQueue::last_sent_slashes().len(), 5);
assert_eq!(MockOkOutboundQueue::last_built_era(), Some(2));
System::assert_has_event(RuntimeEvent::ExternalValidatorSlashes(
crate::Event::SlashesMessageSent {
message_id: Default::default(),
},
));
run_block();
assert!(ExternalValidatorSlashes::unsent_queue_is_empty());
});
}
#[test]
fn retry_extrinsic_succeeds_for_matching_era() {
new_test_ext().execute_with(|| {
crate::mock::DeferPeriodGetter::with_defer_period(0);
start_era(0, 0, 0);
start_era(1, 1, 1);
for i in 0..25 {
PendingOffenceKind::<Test>::insert(0, 3 + i, OffenceKind::LivenessOffence);
Pallet::<Test>::on_offence(
&[OffenceDetails {
offender: (3 + i, ()),
reporters: vec![],
}],
&[Perbill::from_percent(75)],
0,
);
}
start_era(2, 2, 2);
start_era(5, 5, 5);
assert_ok!(ExternalValidatorSlashes::retry_unsent_slash_era(
RuntimeOrigin::root(),
2,
));
assert_eq!(unsent_queue_len(), 1);
assert_eq!(queued_slash_ids(), (20..25).collect::<Vec<_>>());
assert_eq!(MockOkOutboundQueue::last_built_era(), Some(2));
});
}
#[test]
fn retry_extrinsic_errors_when_era_not_queued() {
new_test_ext().execute_with(|| {
assert_noop!(
ExternalValidatorSlashes::retry_unsent_slash_era(RuntimeOrigin::root(), 2),
Error::<Test>::EraNotInUnsentQueue
);
});
}
#[test]
fn retry_extrinsic_requires_root() {
new_test_ext().execute_with(|| {
assert_noop!(
ExternalValidatorSlashes::retry_unsent_slash_era(RuntimeOrigin::signed(1), 2),
sp_runtime::DispatchError::BadOrigin
);
});
}
#[test]
fn retry_extrinsic_preserves_failed_batch_when_send_still_fails() {
new_test_ext().execute_with(|| {
crate::mock::DeferPeriodGetter::with_defer_period(0);
MockOkOutboundQueue::set_should_fail(true);
start_era(0, 0, 0);
start_era(1, 1, 1);
for i in 0..25 {
PendingOffenceKind::<Test>::insert(0, 3 + i, OffenceKind::LivenessOffence);
Pallet::<Test>::on_offence(
&[OffenceDetails {
offender: (3 + i, ()),
reporters: vec![],
}],
&[Perbill::from_percent(75)],
0,
);
}
start_era(2, 2, 2);
let before = queued_slash_ids();
assert_noop!(
ExternalValidatorSlashes::retry_unsent_slash_era(RuntimeOrigin::root(), 2),
Error::<Test>::MessageSendFailed
);
assert_eq!(queued_slash_ids(), before);
assert_eq!(unsent_queue_len(), 2);
});
}
#[test]
fn unsent_queue_full_emits_event() {
new_test_ext().execute_with(|| {
crate::mock::DeferPeriodGetter::with_defer_period(0);
for i in 0..63u32 {
let slash = Slash {
validator: 1000 + i as u64,
reporters: vec![],
slash_id: i,
percentage: Perbill::from_percent(1),
confirmed: true,
offence_kind: OffenceKind::LivenessOffence,
};
assert!(ExternalValidatorSlashes::unsent_queue_push((
1,
vec![slash]
)));
}
Slashes::<Test>::insert(
2,
vec![Slash {
validator: 5000u64,
reporters: vec![],
slash_id: 999,
percentage: Perbill::from_percent(10),
confirmed: true,
offence_kind: OffenceKind::LivenessOffence,
}],
);
start_era(2, 2, 2);
assert_eq!(unsent_queue_len(), 63);
assert_eq!(Slashes::<Test>::get(2).len(), 1);
});
}
@ -628,14 +876,13 @@ fn test_on_offence_defer_period_0_messages_get_queued_across_eras() {
}
assert_eq!(Slashes::<Test>::get(get_slashing_era(1)).len(), 25);
start_era(2, 2, 2);
assert_eq!(UnreportedSlashesQueue::<Test>::get().len(), 25);
assert_eq!(unsent_queue_len(), 2);
// this triggers on_initialize
run_block();
assert_eq!(UnreportedSlashesQueue::<Test>::get().len(), 5);
assert_eq!(unsent_queue_len(), 1);
assert_eq!(queued_slash_ids(), (20..25).collect::<Vec<_>>());
// We have 5 non-dispatched, which should accumulate
// We shoulld have 30 after we initialie era 3
for i in 0..25 {
PendingOffenceKind::<Test>::insert(2, 3 + i, OffenceKind::LivenessOffence);
Pallet::<Test>::on_offence(
@ -651,15 +898,20 @@ fn test_on_offence_defer_period_0_messages_get_queued_across_eras() {
}
start_era(3, 3, 3);
assert_eq!(UnreportedSlashesQueue::<Test>::get().len(), 30);
assert_eq!(unsent_queue_len(), 3);
assert_eq!(queued_batch_eras(), vec![2, 3, 3]);
// this triggers on_initialize
run_block();
assert_eq!(UnreportedSlashesQueue::<Test>::get().len(), 10);
assert_eq!(unsent_queue_len(), 2);
assert_eq!(queued_batch_eras(), vec![3, 3]);
// this triggers on_initialize
run_block();
assert_eq!(UnreportedSlashesQueue::<Test>::get().len(), 0);
assert_eq!(unsent_queue_len(), 1);
run_block();
assert!(ExternalValidatorSlashes::unsent_queue_is_empty());
});
}

View file

@ -57,6 +57,7 @@ pub trait WeightInfo {
fn force_inject_slash() -> Weight;
fn root_test_send_msg_to_eth() -> Weight;
fn process_slashes_queue(s: u32, ) -> Weight;
fn retry_unsent_slash_era() -> Weight;
fn set_slashing_mode() -> Weight;
}
@ -136,6 +137,11 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
.saturating_add(Weight::from_parts(0, 42).saturating_mul(s.into()))
}
fn retry_unsent_slash_era() -> Weight {
// Same as the success path for one queued batch.
Self::process_slashes_queue(10)
}
fn set_slashing_mode() -> Weight {
Weight::from_parts(7_402_000, 3601)
.saturating_add(T::DbWeight::get().reads(1_u64))
@ -221,6 +227,10 @@ impl WeightInfo for () {
.saturating_add(Weight::from_parts(0, 42).saturating_mul(s.into()))
}
fn retry_unsent_slash_era() -> Weight {
Self::process_slashes_queue(10)
}
fn set_slashing_mode() -> Weight {
Weight::from_parts(7_402_000, 3601)
.saturating_add(RocksDbWeight::get().reads(1_u64))

View file

@ -22,7 +22,6 @@ frame-system = { workspace = true }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
sp-staking = { workspace = true }
sp-std = { workspace = true }
frame-benchmarking = { workspace = true }
@ -30,12 +29,12 @@ pallet-authorship = { workspace = true }
pallet-balances = { workspace = true, optional = true }
pallet-external-validators = { workspace = true }
pallet-session = { workspace = true, features = [ "historical" ] }
pallet-timestamp = { workspace = true }
snowbridge-core = { workspace = true }
snowbridge-outbound-queue-primitives = { workspace = true }
[dev-dependencies]
pallet-timestamp = { workspace = true }
sp-io = { workspace = true }
[features]
@ -58,7 +57,6 @@ std = [
"sp-io/std",
"sp-runtime/std",
"sp-staking/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",

View file

@ -23,8 +23,7 @@ use crate::Pallet as ExternalValidatorsRewards;
use {
crate::types::BenchmarkHelper,
frame_benchmarking::{account, v2::*, BenchmarkError},
frame_support::traits::{Currency, EnsureOrigin},
sp_std::prelude::*,
frame_support::traits::{Currency, EnsureOrigin, Hooks},
};
const SEED: u32 = 0;
@ -43,15 +42,20 @@ fn create_funded_user<T: Config + pallet_balances::Config>(
user
}
/// Helper: insert a single entry into the ring buffer at slot 0.
fn push_unsent_entry<T: Config>(era_index: u32, timestamp: u32, inflation: u128) {
ExternalValidatorsRewards::<T>::unsent_queue_push((era_index, timestamp, inflation));
/// Helper: insert a single queued window into the ring buffer at slot 0.
fn push_unsent_entry<T: Config>(window_start: u32, window_index: u32, duration: u32) {
ExternalValidatorsRewards::<T>::unsent_queue_push(QueuedRewardsWindow {
window_start,
window_index,
duration,
});
}
#[allow(clippy::multiple_bound_locations)]
#[benchmarks(where T: pallet_balances::Config)]
#[benchmarks(where T: pallet_balances::Config + pallet_timestamp::Config<Moment = u64>)]
mod benchmarks {
use super::*;
use alloc::collections::BTreeMap;
// worst case for the end of an era.
#[benchmark]
@ -68,6 +72,14 @@ mod benchmarks {
T::BenchmarkHelper::setup();
<RewardPointsForEra<T>>::insert(1u32, era_reward_points);
pallet_timestamp::Now::<T>::put(35_000u64);
#[cfg(test)]
crate::mock::Mock::mutate(|mock| {
mock.active_era = Some(pallet_external_validators::traits::ActiveEraInfo {
index: 1,
start: Some(30_000),
});
});
#[block]
{
@ -77,42 +89,79 @@ mod benchmarks {
Ok(())
}
/// Helper to populate reward points for an era with 1000 validators.
fn setup_era_reward_points<T: Config + pallet_balances::Config>(era_index: u32) {
let mut era_reward_points = EraRewardPoints::default();
era_reward_points.total = 20 * 1000;
/// Helper to populate persisted state for a closed window with 1000 operators.
fn setup_window_reward_state<T: Config + pallet_balances::Config>(
window_start: u32,
inflation_amount: u128,
) {
let mut operator_points = BTreeMap::new();
for i in 0..1000 {
let account_id = create_funded_user::<T>("candidate", i, 100);
era_reward_points.individual.insert(account_id, 20);
let _ = create_funded_user::<T>("candidate", i, 100);
operator_points.insert(sp_core::H160::from_low_u64_be(i as u64 + 1), 20);
}
<RewardPointsForEra<T>>::insert(era_index, era_reward_points);
<WindowOperatorPoints<T>>::insert(window_start, operator_points);
<WindowInflationAmount<T>>::insert(window_start, inflation_amount);
}
// on_initialize: unsent queue is empty (2 reads for head+tail)
#[benchmark]
fn process_unsent_reward_eras_empty() -> Result<(), BenchmarkError> {
// Ensure queue is empty (default state: head == tail == 0)
assert!(ExternalValidatorsRewards::<T>::unsent_queue_is_empty());
// Exercise the "empty slot at head" fallback path that returns the empty weight.
<UnsentRewardHead<T>>::put(0);
<UnsentRewardTail<T>>::put(1);
frame_system::Pallet::<T>::set_block_number(1u32.into());
#[block]
{
ExternalValidatorsRewards::<T>::process_unsent_reward_eras();
<ExternalValidatorsRewards<T> as Hooks<
frame_system::pallet_prelude::BlockNumberFor<T>,
>>::on_initialize(1u32.into());
}
Ok(())
}
// on_initialize: oldest entry has pruned reward points
#[benchmark]
fn process_unsent_reward_eras_expired() -> Result<(), BenchmarkError> {
// Push an entry whose reward points do NOT exist in storage
push_unsent_entry::<T>(999, 0, 42);
fn process_closed_windows_idle() -> Result<(), BenchmarkError> {
pallet_timestamp::Now::<T>::put(35_000u64);
#[block]
{
ExternalValidatorsRewards::<T>::process_unsent_reward_eras();
ExternalValidatorsRewards::<T>::process_closed_windows(35, 0, 10);
}
Ok(())
}
#[benchmark]
fn process_closed_windows_processed() -> Result<(), BenchmarkError> {
frame_system::Pallet::<T>::set_block_number(0u32.into());
T::BenchmarkHelper::setup();
setup_window_reward_state::<T>(20, 42);
<NextWindowToSubmit<T>>::put(20);
pallet_timestamp::Now::<T>::put(35_000u64);
#[block]
{
ExternalValidatorsRewards::<T>::process_closed_windows(35, 0, 10);
}
Ok(())
}
// on_initialize: oldest queued window no longer has persisted state
#[benchmark]
fn process_unsent_reward_eras_expired() -> Result<(), BenchmarkError> {
push_unsent_entry::<T>(999, 99, 10);
frame_system::Pallet::<T>::set_block_number(1u32.into());
#[block]
{
<ExternalValidatorsRewards<T> as Hooks<
frame_system::pallet_prelude::BlockNumberFor<T>,
>>::on_initialize(1u32.into());
}
// Entry should have been removed
@ -126,13 +175,16 @@ mod benchmarks {
fn process_unsent_reward_eras_success() -> Result<(), BenchmarkError> {
frame_system::Pallet::<T>::set_block_number(0u32.into());
T::BenchmarkHelper::setup();
setup_era_reward_points::<T>(1);
setup_window_reward_state::<T>(0, 42);
push_unsent_entry::<T>(1, 0, 42);
push_unsent_entry::<T>(0, 0, 10);
frame_system::Pallet::<T>::set_block_number(1u32.into());
#[block]
{
ExternalValidatorsRewards::<T>::process_unsent_reward_eras();
<ExternalValidatorsRewards<T> as Hooks<
frame_system::pallet_prelude::BlockNumberFor<T>,
>>::on_initialize(1u32.into());
}
assert!(ExternalValidatorsRewards::<T>::unsent_queue_is_empty());
@ -145,32 +197,35 @@ mod benchmarks {
fn process_unsent_reward_eras_failed() -> Result<(), BenchmarkError> {
frame_system::Pallet::<T>::set_block_number(0u32.into());
T::BenchmarkHelper::setup();
setup_era_reward_points::<T>(1);
setup_window_reward_state::<T>(0, 42);
push_unsent_entry::<T>(1, 0, 42);
push_unsent_entry::<T>(0, 0, 10);
frame_system::Pallet::<T>::set_block_number(1u32.into());
#[block]
{
ExternalValidatorsRewards::<T>::process_unsent_reward_eras();
<ExternalValidatorsRewards<T> as Hooks<
frame_system::pallet_prelude::BlockNumberFor<T>,
>>::on_initialize(1u32.into());
}
Ok(())
}
// Governance extrinsic: retry a specific unsent era
// Governance extrinsic: retry a specific unsent window
#[benchmark]
fn retry_unsent_reward_era() -> Result<(), BenchmarkError> {
fn retry_unsent_reward_window() -> Result<(), BenchmarkError> {
frame_system::Pallet::<T>::set_block_number(0u32.into());
T::BenchmarkHelper::setup();
setup_era_reward_points::<T>(1);
setup_window_reward_state::<T>(0, 42);
push_unsent_entry::<T>(1, 0, 42);
push_unsent_entry::<T>(0, 0, 10);
let origin =
T::GovernanceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
#[extrinsic_call]
_(origin as T::RuntimeOrigin, 1u32);
_(origin as T::RuntimeOrigin, 0u32);
assert!(ExternalValidatorsRewards::<T>::unsent_queue_is_empty());

View file

@ -18,6 +18,7 @@
//! Storage will be cleared after a period of time.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(test)]
mod mock;
@ -33,18 +34,18 @@ pub mod weights;
pub use pallet::*;
use alloc::vec::Vec;
use {
crate::types::{EraRewardsUtils, HandleInflation, SendMessage},
frame_support::traits::{Get, ValidatorSet},
crate::types::{HandleInflation, RewardsPeriodUtils, SendMessage},
frame_support::traits::{Get, UnixTime, ValidatorSet},
pallet_external_validators::traits::{ExternalIndexProvider, OnEraEnd, OnEraStart},
parity_scale_codec::{Decode, Encode},
parity_scale_codec::{Decode, Encode, MaxEncodedLen},
sp_core::{H160, H256},
sp_runtime::{
traits::{Hash, Zero},
traits::{Hash, SaturatedConversion},
Perbill,
},
sp_staking::SessionIndex,
sp_std::vec::Vec,
};
/// Trait for checking if a validator has been slashed in a given era
@ -65,14 +66,14 @@ pub mod pallet {
use sp_runtime::PerThing;
pub use crate::weights::WeightInfo;
use alloc::collections::BTreeMap;
use {
super::*, frame_support::pallet_prelude::*, frame_system::pallet_prelude::OriginFor,
pallet_external_validators::traits::EraIndexProvider, sp_runtime::Saturating,
sp_std::collections::btree_map::BTreeMap,
};
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
pub type RewardPoints = u32;
pub type EraIndex = u32;
@ -159,6 +160,17 @@ pub mod pallet {
/// Ethereum Sovereign Account where rewards will be minted
type RewardsEthereumSovereignAccount: Get<Self::AccountId>;
/// EigenLayer rewards window genesis timestamp (seconds).
type RewardsWindowGenesisTimestamp: Get<u32>;
/// EigenLayer rewards window duration (seconds).
/// Must be a positive multiple of EigenLayer `CALCULATION_INTERVAL_SECONDS`
/// and not exceed EigenLayer `MAX_REWARDS_DURATION`.
type RewardsWindowDuration: Get<u32>;
/// Unix time provider used to place points and submissions in aligned windows.
type UnixTime: UnixTime;
/// The weight information of this pallet.
type WeightInfo: WeightInfo;
@ -181,53 +193,139 @@ pub mod pallet {
#[pallet::hooks]
impl<T: Config> Hooks<frame_system::pallet_prelude::BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_n: frame_system::pallet_prelude::BlockNumberFor<T>) -> Weight {
Self::process_unsent_reward_eras()
let head = UnsentRewardHead::<T>::get();
let tail = UnsentRewardTail::<T>::get();
if head != tail {
return Self::process_unsent_reward_eras_from(head, tail);
}
let (genesis, interval) = Self::rewards_window_config();
let now = Self::now_seconds();
Self::process_closed_windows(now, genesis, interval)
}
fn on_runtime_upgrade() -> Weight {
let on_chain = Pallet::<T>::on_chain_storage_version();
if on_chain >= STORAGE_VERSION {
return T::DbWeight::get().reads(1);
}
let cutover_era = T::EraIndexProvider::active_era().index.saturating_add(1);
// This upgrade intentionally drops any pre-window retry state and
// defers window accounting until the next full era so the current
// in-flight era is not partially accounted under mixed semantics.
WindowModeStartsAtEra::<T>::put(cutover_era);
NextWindowToSubmit::<T>::kill();
UnsentRewardHead::<T>::put(0);
UnsentRewardTail::<T>::put(0);
STORAGE_VERSION.put::<Pallet<T>>();
log::info!(
target: "ext_validators_rewards",
"Migrated rewards pallet to storage version 2. Window mode will start at era {}.",
cutover_era,
);
T::DbWeight::get().reads_writes(2, 5)
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
let on_chain = Pallet::<T>::on_chain_storage_version();
let active_era = T::EraIndexProvider::active_era().index;
let needs_upgrade = on_chain < STORAGE_VERSION;
Ok((needs_upgrade, active_era).encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
let (needs_upgrade, active_era): (bool, EraIndex) = Decode::decode(&mut &state[..])
.map_err(|_| "Failed to decode pre-upgrade state")?;
if !needs_upgrade {
frame_support::ensure!(
Pallet::<T>::on_chain_storage_version() >= STORAGE_VERSION,
"Storage version regressed on a no-op upgrade",
);
return Ok(());
}
frame_support::ensure!(
Pallet::<T>::on_chain_storage_version() == STORAGE_VERSION,
"Rewards pallet storage version was not upgraded",
);
frame_support::ensure!(
WindowModeStartsAtEra::<T>::get() == active_era.saturating_add(1),
"Window cutover era was not initialized correctly",
);
frame_support::ensure!(
NextWindowToSubmit::<T>::get().is_none(),
"NextWindowToSubmit was not reset",
);
frame_support::ensure!(
UnsentRewardHead::<T>::get() == 0,
"UnsentRewardHead was not reset",
);
frame_support::ensure!(
UnsentRewardTail::<T>::get() == 0,
"UnsentRewardTail was not reset",
);
Ok(())
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Governance escape hatch: manually retry sending a rewards message for
/// an era that is stuck in the unsent queue.
/// a closed window that is stuck in the unsent queue.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::retry_unsent_reward_era())]
pub fn retry_unsent_reward_era(
pub fn retry_unsent_reward_window(
origin: OriginFor<T>,
era_index: EraIndex,
window_start: u32,
) -> DispatchResult {
T::GovernanceOrigin::ensure_origin(origin)?;
// Scan the ring buffer for the requested era
// Scan the ring buffer for the requested window
let head = UnsentRewardHead::<T>::get();
let tail = UnsentRewardTail::<T>::get();
let mut found = None;
let mut slot = head;
while slot != tail {
if let Some(entry @ (idx, _, _)) = UnsentRewardEra::<T>::get(slot) {
if idx == era_index {
if let Some(entry) = UnsentRewardWindow::<T>::get(slot) {
if entry.window_start == window_start {
found = Some((slot, entry));
break;
}
}
slot = (slot + 1) % UNSENT_QUEUE_CAPACITY;
}
let (slot, (_, timestamp, inflation)) = found.ok_or(Error::<T>::EraNotInUnsentQueue)?;
let (slot, window) = found.ok_or(Error::<T>::WindowNotInUnsentQueue)?;
let reward_points = RewardPointsForEra::<T>::get(era_index);
let info = reward_points
.generate_era_rewards_info(era_index, inflation, timestamp)
.ok_or(Error::<T>::RewardPointsPruned)?;
let info = Self::window_rewards_info(
window.window_start,
window.window_index,
window.duration,
)
.ok_or(Error::<T>::WindowRewardsMissing)?;
let message_id =
Self::send_rewards_message(&info).ok_or(Error::<T>::MessageSendFailed)?;
Self::clear_window(window.window_start);
Self::unsent_queue_remove_slot(slot);
Self::deposit_event(Event::RewardsMessageRetried {
Self::deposit_event(Event::RewardsWindowRetried {
message_id,
era_index,
window_start: window.window_start,
window_index: window.window_index,
total_points: info.total_points,
inflation_amount: inflation,
inflation_amount: info.inflation_amount,
});
Ok(())
@ -237,34 +335,50 @@ pub mod pallet {
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// The rewards message was sent correctly.
RewardsMessageSent {
/// The rewards window submission was sent correctly.
RewardsWindowSubmitted {
message_id: H256,
era_index: EraIndex,
window_start: u32,
window_index: u32,
total_points: u128,
inflation_amount: u128,
},
/// The rewards message failed to send; era queued for retry.
RewardsMessageSendFailed { era_index: EraIndex },
/// A previously failed rewards message was retried and sent successfully.
RewardsMessageRetried {
/// Window submission failed on the initial attempt.
RewardsWindowSubmissionFailed {
window_start: u32,
window_index: u32,
},
/// Closed window had no distributable rewards and was skipped.
RewardsWindowSkipped {
window_start: u32,
window_index: u32,
},
/// A previously failed rewards window was retried and sent successfully.
RewardsWindowRetried {
message_id: H256,
era_index: EraIndex,
window_start: u32,
window_index: u32,
total_points: u128,
inflation_amount: u128,
},
/// An unsent era was dropped because its reward points have been pruned.
UnsentEraExpired { era_index: EraIndex },
/// The unsent queue is full; this era could not be enqueued for retry.
UnsentQueueFull { era_index: EraIndex },
/// A queued window was dropped because its stored rewards data is no longer available.
UnsentWindowExpired {
window_start: u32,
window_index: u32,
},
/// The unsent queue is full; this failed window could not be enqueued for retry.
UnsentWindowQueueFull {
window_start: u32,
window_index: u32,
},
}
#[pallet::error]
pub enum Error<T> {
/// The specified era is not in the unsent queue.
EraNotInUnsentQueue,
/// Reward points for the era have been pruned from storage.
RewardPointsPruned,
/// The specified window is not in the unsent queue.
WindowNotInUnsentQueue,
/// Rewards data for the window is no longer available.
WindowRewardsMissing,
/// The message delivery still failed on retry.
MessageSendFailed,
}
@ -276,43 +390,6 @@ pub mod pallet {
pub individual: BTreeMap<AccountId, RewardPoints>,
}
impl<AccountId: Ord + sp_runtime::traits::Debug + Parameter> EraRewardPoints<AccountId> {
/// Generate utils needed for EigenLayer rewards submission:
/// - total_points: number of total points of the era_index specified.
/// - individual_points: (address, points) tuples for each validator.
/// - inflation_amount: total inflation tokens to distribute.
/// - era_start_timestamp: timestamp when the era started (seconds since Unix epoch).
pub fn generate_era_rewards_info(
&self,
era_index: EraIndex,
inflation_amount: u128,
era_start_timestamp: u32,
) -> Option<EraRewardsUtils> {
let mut individual_points = Vec::with_capacity(self.individual.len());
for (account_id, reward_points) in self.individual.iter() {
// Convert AccountId to H160 for EigenLayer rewards submission.
// In DataHaven, AccountId is H160, so encode() produces exactly 20 bytes.
individual_points
.push((H160::from_slice(&account_id.encode()[..20]), *reward_points));
}
let total_points: u128 = individual_points.iter().map(|(_, pts)| *pts as u128).sum();
if total_points.is_zero() {
return None;
}
Some(EraRewardsUtils {
era_index,
era_start_timestamp,
total_points,
individual_points,
inflation_amount,
})
}
}
impl<AccountId> Default for EraRewardPoints<AccountId> {
fn default() -> Self {
EraRewardPoints {
@ -342,23 +419,40 @@ pub mod pallet {
pub type BlocksProducedInEra<T: Config> =
StorageMap<_, Twox64Concat, EraIndex, u32, ValueQuery>;
/// Per-window operator points accumulated from session-end rewards.
#[pallet::storage]
#[pallet::unbounded]
pub type WindowOperatorPoints<T: Config> =
StorageMap<_, Twox64Concat, u32, BTreeMap<H160, RewardPoints>, ValueQuery>;
/// Total inflation allocated to a given aligned window.
#[pallet::storage]
pub type WindowInflationAmount<T: Config> = StorageMap<_, Twox64Concat, u32, u128, ValueQuery>;
/// Pointer to the next window start to submit.
#[pallet::storage]
pub type NextWindowToSubmit<T: Config> = StorageValue<_, u32, OptionQuery>;
/// Era at which window mode becomes active after a live upgrade.
/// `0` means window mode is active immediately (fresh chains/tests).
#[pallet::storage]
pub type WindowModeStartsAtEra<T: Config> = StorageValue<_, EraIndex, ValueQuery>;
/// Maximum number of unsent reward entries in the ring buffer.
pub const UNSENT_QUEUE_CAPACITY: u32 = 64;
/// Ring buffer of eras whose rewards messages failed to send.
/// Each slot stores (era_index, era_start_timestamp, scaled_inflation).
/// Metadata for a failed rewards window kept in the retry ring buffer.
#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, PartialEq, Eq, TypeInfo, Clone, Copy)]
pub struct QueuedRewardsWindow {
pub window_start: u32,
pub window_index: u32,
pub duration: u32,
}
/// Ring buffer of windows whose rewards messages failed to send.
/// Keyed by slot index [0, UNSENT_QUEUE_CAPACITY).
#[pallet::storage]
pub type UnsentRewardEra<T: Config> = StorageMap<
_,
Twox64Concat,
u32,
(
EraIndex,
/* era_start_timestamp */ u32,
/* scaled_inflation */ u128,
),
>;
pub type UnsentRewardWindow<T: Config> = StorageMap<_, Twox64Concat, u32, QueuedRewardsWindow>;
/// Ring buffer head: next slot to be processed by `on_initialize`.
#[pallet::storage]
@ -370,23 +464,231 @@ pub mod pallet {
pub type UnsentRewardTail<T: Config> = StorageValue<_, u32, ValueQuery>;
impl<T: Config> Pallet<T> {
fn now_seconds() -> u32 {
T::UnixTime::now().as_secs().saturated_into::<u32>()
}
fn window_mode_is_active(era_index: EraIndex) -> bool {
let start_era = WindowModeStartsAtEra::<T>::get();
start_era == 0 || era_index >= start_era
}
fn rewards_window_config() -> (u32, u32) {
(
T::RewardsWindowGenesisTimestamp::get(),
T::RewardsWindowDuration::get(),
)
}
fn window_start_for(timestamp: u32, genesis: u32, interval: u32) -> u32 {
genesis + (timestamp.saturating_sub(genesis) / interval) * interval
}
fn window_index_for(window_start: u32, genesis: u32, interval: u32) -> u32 {
window_start.saturating_sub(genesis) / interval
}
fn account_to_h160(account_id: &T::AccountId) -> H160 {
H160::from_slice(&account_id.encode()[..20])
}
fn clear_window(window_start: u32) {
WindowInflationAmount::<T>::remove(window_start);
WindowOperatorPoints::<T>::remove(window_start);
}
fn window_rewards_info(
window_start: u32,
window_index: u32,
duration: u32,
) -> Option<RewardsPeriodUtils> {
let inflation_amount = WindowInflationAmount::<T>::get(window_start);
let operator_points = WindowOperatorPoints::<T>::get(window_start);
let total_points: u128 = operator_points.values().map(|p| *p as u128).sum();
if total_points == 0 || inflation_amount == 0 {
return None;
}
Some(RewardsPeriodUtils {
period_index: window_index,
period_start: window_start,
duration,
total_points,
individual_points: operator_points.into_iter().collect(),
inflation_amount,
})
}
fn allocate_era_inflation_to_windows(
era_start: u32,
era_end: u32,
inflation_amount: u128,
genesis: u32,
interval: u32,
) {
if era_end <= era_start {
return;
}
let mut window_start = Self::window_start_for(era_start, genesis, interval);
if NextWindowToSubmit::<T>::get().is_none() {
NextWindowToSubmit::<T>::put(window_start);
}
if inflation_amount == 0 {
return;
}
let era_duration = era_end.saturating_sub(era_start).max(1);
let mut allocated = 0u128;
let mut last_window = None;
while window_start < era_end {
let window_end = window_start.saturating_add(interval);
let overlap_start = era_start.max(window_start);
let overlap_end = era_end.min(window_end);
if overlap_end > overlap_start {
let overlap = overlap_end.saturating_sub(overlap_start);
let portion =
inflation_amount.saturating_mul(overlap as u128) / (era_duration as u128);
if portion > 0 {
WindowInflationAmount::<T>::mutate(window_start, |current| {
*current = current.saturating_add(portion);
});
allocated = allocated.saturating_add(portion);
}
last_window = Some(window_start);
}
window_start = window_start.saturating_add(interval);
}
let remainder = inflation_amount.saturating_sub(allocated);
if remainder > 0 {
if let Some(last_window) = last_window {
WindowInflationAmount::<T>::mutate(last_window, |current| {
*current = current.saturating_add(remainder);
});
}
}
}
pub(crate) fn process_closed_windows(now: u32, genesis: u32, interval: u32) -> Weight {
let Some(mut next_window) = NextWindowToSubmit::<T>::get() else {
return T::WeightInfo::process_closed_windows_idle();
};
if next_window.saturating_add(interval) > now {
return T::WeightInfo::process_closed_windows_idle();
}
let inflation_amount = WindowInflationAmount::<T>::get(next_window);
let operator_points = WindowOperatorPoints::<T>::get(next_window);
let total_points: u128 = operator_points.values().map(|p| *p as u128).sum();
let window_index = Self::window_index_for(next_window, genesis, interval);
if total_points == 0 || inflation_amount == 0 {
Self::clear_window(next_window);
Self::deposit_event(Event::RewardsWindowSkipped {
window_start: next_window,
window_index,
});
next_window = next_window.saturating_add(interval);
NextWindowToSubmit::<T>::put(next_window);
return T::WeightInfo::process_closed_windows_processed();
}
let utils = RewardsPeriodUtils {
period_index: window_index,
period_start: next_window,
duration: interval,
total_points,
individual_points: operator_points.into_iter().collect(),
inflation_amount,
};
match Self::send_rewards_message(&utils) {
Some(message_id) => {
Self::deposit_event(Event::RewardsWindowSubmitted {
message_id,
window_start: next_window,
window_index,
total_points,
inflation_amount,
});
}
None => {
Self::deposit_event(Event::RewardsWindowSubmissionFailed {
window_start: next_window,
window_index,
});
let queued_window = QueuedRewardsWindow {
window_start: next_window,
window_index,
duration: interval,
};
if Self::unsent_queue_push(queued_window) {
next_window = next_window.saturating_add(interval);
NextWindowToSubmit::<T>::put(next_window);
return T::WeightInfo::process_closed_windows_processed();
}
log::error!(
target: "ext_validators_rewards",
"Unsent reward queue full, cannot enqueue window {}",
next_window,
);
Self::deposit_event(Event::UnsentWindowQueueFull {
window_start: next_window,
window_index,
});
return T::WeightInfo::process_closed_windows_processed();
}
}
Self::clear_window(next_window);
next_window = next_window.saturating_add(interval);
NextWindowToSubmit::<T>::put(next_window);
T::WeightInfo::process_closed_windows_processed()
}
/// Reward validators. Does not check if the validators are valid, caller needs to make sure of that.
pub fn reward_by_ids(points: impl IntoIterator<Item = (T::AccountId, RewardPoints)>) {
let active_era = T::EraIndexProvider::active_era();
let now = Self::now_seconds();
let (genesis, interval) = Self::rewards_window_config();
let window_start = Self::window_start_for(now, genesis, interval);
let window_mode_active = Self::window_mode_is_active(active_era.index);
RewardPointsForEra::<T>::mutate(active_era.index, |era_rewards| {
for (validator, points) in points.into_iter() {
(*era_rewards.individual.entry(validator.clone()).or_default())
.saturating_accrue(points);
era_rewards.total.saturating_accrue(points);
if window_mode_active {
let operator = Self::account_to_h160(&validator);
WindowOperatorPoints::<T>::mutate(window_start, |operators| {
operators
.entry(operator)
.and_modify(|existing| *existing = existing.saturating_add(points))
.or_insert(points);
});
}
}
})
}
/// Helper to build, validate and deliver an outbound message.
/// Logs any error and returns None on failure.
fn send_rewards_message(info: &EraRewardsUtils) -> Option<H256> {
let outbound = T::SendMessage::build(info).or_else(|| {
fn send_rewards_message(utils: &RewardsPeriodUtils) -> Option<H256> {
let outbound = T::SendMessage::build(utils).or_else(|| {
log::error!(target: "ext_validators_rewards", "Failed to build outbound message");
None
})?;
@ -428,9 +730,9 @@ pub mod pallet {
tail.wrapping_sub(head) % UNSENT_QUEUE_CAPACITY
}
/// Push a new entry into the ring buffer.
/// Push a new window into the ring buffer.
/// Returns `true` on success, `false` if the buffer is full.
pub(crate) fn unsent_queue_push(entry: (EraIndex, u32, u128)) -> bool {
pub(crate) fn unsent_queue_push(entry: QueuedRewardsWindow) -> bool {
let head = UnsentRewardHead::<T>::get();
let tail = UnsentRewardTail::<T>::get();
let next_tail = (tail + 1) % UNSENT_QUEUE_CAPACITY;
@ -438,7 +740,7 @@ pub mod pallet {
// Buffer full
return false;
}
UnsentRewardEra::<T>::insert(tail, entry);
UnsentRewardWindow::<T>::insert(tail, entry);
UnsentRewardTail::<T>::put(next_tail);
true
}
@ -455,13 +757,13 @@ pub mod pallet {
break;
}
// Move next → cur
if let Some(entry) = UnsentRewardEra::<T>::get(next) {
UnsentRewardEra::<T>::insert(cur, entry);
if let Some(entry) = UnsentRewardWindow::<T>::get(next) {
UnsentRewardWindow::<T>::insert(cur, entry);
}
cur = next;
}
// Remove the now-duplicate last entry and shrink tail
UnsentRewardEra::<T>::remove(cur);
UnsentRewardWindow::<T>::remove(cur);
let new_tail = if tail == 0 {
UNSENT_QUEUE_CAPACITY - 1
} else {
@ -487,66 +789,71 @@ pub mod pallet {
// ── Core retry logic ──────────────────────────────────────────────
/// Process at most one unsent reward era per block.
/// Process at most one unsent reward window per block.
/// On failure the head pointer advances to the next entry so a single
/// stuck era does not block retries for subsequent eras.
pub(crate) fn process_unsent_reward_eras() -> Weight {
let head = UnsentRewardHead::<T>::get();
let tail = UnsentRewardTail::<T>::get();
/// stuck window does not block retries for subsequent windows.
fn process_unsent_reward_eras_from(head: u32, tail: u32) -> Weight {
if head == tail {
return T::WeightInfo::process_unsent_reward_eras_empty();
}
let Some((era_index, timestamp, inflation)) = UnsentRewardEra::<T>::get(head) else {
let Some(window) = UnsentRewardWindow::<T>::get(head) else {
// Slot unexpectedly empty — advance head past it
UnsentRewardHead::<T>::put((head + 1) % UNSENT_QUEUE_CAPACITY);
return T::WeightInfo::process_unsent_reward_eras_empty();
};
// Check if reward points are still available
let reward_points = RewardPointsForEra::<T>::get(era_index);
let info =
match reward_points.generate_era_rewards_info(era_index, inflation, timestamp) {
Some(info) => info,
None => {
// Reward points have been pruned — discard this entry
log::warn!(
target: "ext_validators_rewards",
"Unsent era {era_index} expired: reward points pruned",
);
UnsentRewardEra::<T>::remove(head);
UnsentRewardHead::<T>::put((head + 1) % UNSENT_QUEUE_CAPACITY);
Self::deposit_event(Event::UnsentEraExpired { era_index });
return T::WeightInfo::process_unsent_reward_eras_expired();
}
};
let info = match Self::window_rewards_info(
window.window_start,
window.window_index,
window.duration,
) {
Some(info) => info,
None => {
log::warn!(
target: "ext_validators_rewards",
"Unsent window {} expired: rewards state missing",
window.window_start,
);
Self::clear_window(window.window_start);
UnsentRewardWindow::<T>::remove(head);
UnsentRewardHead::<T>::put((head + 1) % UNSENT_QUEUE_CAPACITY);
Self::deposit_event(Event::UnsentWindowExpired {
window_start: window.window_start,
window_index: window.window_index,
});
return T::WeightInfo::process_unsent_reward_eras_expired();
}
};
// Attempt to resend
match Self::send_rewards_message(&info) {
Some(message_id) => {
UnsentRewardEra::<T>::remove(head);
Self::clear_window(window.window_start);
UnsentRewardWindow::<T>::remove(head);
UnsentRewardHead::<T>::put((head + 1) % UNSENT_QUEUE_CAPACITY);
Self::deposit_event(Event::RewardsMessageRetried {
Self::deposit_event(Event::RewardsWindowRetried {
message_id,
era_index,
window_start: window.window_start,
window_index: window.window_index,
total_points: info.total_points,
inflation_amount: inflation,
inflation_amount: info.inflation_amount,
});
T::WeightInfo::process_unsent_reward_eras_success()
}
None => {
// Move the failed entry to the back of the queue so the
// next block tries a different era (avoids head-of-line
// next block tries a different window (avoids head-of-line
// blocking). The entry is not lost — it will be retried
// after all other pending entries.
UnsentRewardEra::<T>::remove(head);
UnsentRewardWindow::<T>::remove(head);
UnsentRewardHead::<T>::put((head + 1) % UNSENT_QUEUE_CAPACITY);
UnsentRewardEra::<T>::insert(tail, (era_index, timestamp, inflation));
UnsentRewardWindow::<T>::insert(tail, window);
UnsentRewardTail::<T>::put((tail + 1) % UNSENT_QUEUE_CAPACITY);
log::warn!(
target: "ext_validators_rewards",
"Retry for unsent era {era_index} still failing, moved to back of queue",
"Retry for unsent window {} still failing, moved to back of queue",
window.window_start,
);
T::WeightInfo::process_unsent_reward_eras_failed()
}
@ -870,17 +1177,27 @@ pub mod pallet {
RewardPointsForEra::<T>::remove(era_index_to_delete);
BlocksProducedInEra::<T>::remove(era_index_to_delete);
// Proactively clean up any unsent entries whose reward points
// have been pruned (this era and any older ones still lingering).
// Proactively clean up any unsent entries whose window state has
// been removed while they were waiting for retry.
let head = UnsentRewardHead::<T>::get();
let mut tail = UnsentRewardTail::<T>::get();
let mut slot = head;
while slot != tail {
if let Some((idx, _, _)) = UnsentRewardEra::<T>::get(slot) {
if idx <= era_index_to_delete {
if let Some(window) = UnsentRewardWindow::<T>::get(slot) {
if Self::window_rewards_info(
window.window_start,
window.window_index,
window.duration,
)
.is_none()
{
Self::clear_window(window.window_start);
Self::unsent_queue_remove_slot(slot);
tail = UnsentRewardTail::<T>::get();
Self::deposit_event(Event::UnsentEraExpired { era_index: idx });
Self::deposit_event(Event::UnsentWindowExpired {
window_start: window.window_start,
window_index: window.window_index,
});
// Don't advance slot — next entry slid into this position
continue;
}
@ -892,6 +1209,15 @@ pub mod pallet {
impl<T: Config> OnEraEnd for Pallet<T> {
fn on_era_end(era_index: EraIndex) {
if !Self::window_mode_is_active(era_index) {
log::info!(
target: "ext_validators_rewards",
"Skipping transition-era rewards for era {} until window mode cutover",
era_index,
);
return;
}
// Calculate performance-scaled inflation based on blocks produced.
let base_inflation = T::EraInflationProvider::get();
let scaled_inflation = Self::calculate_scaled_inflation(era_index, base_inflation);
@ -937,55 +1263,22 @@ pub mod pallet {
.map(|ms| (ms / 1000) as u32)
.unwrap_or(0);
// Generate era rewards utils with the actual rewards amount (post-treasury split).
// This ensures the message to EigenLayer matches the actual minted rewards.
let info = match RewardPointsForEra::<T>::get(&era_index).generate_era_rewards_info(
era_index,
mint_result.rewards_amount,
let (genesis, interval) = Self::rewards_window_config();
let now = Self::now_seconds();
// Allocate the rewards amount (post-treasury split) to aligned windows.
Self::allocate_era_inflation_to_windows(
era_start_timestamp,
) {
Some(info) => info,
None => {
// Returns None when total_points is zero or no validators have rewards
log::error!(
target: "ext_validators_rewards",
"Failed to generate era rewards info (no rewards to distribute)"
);
return;
}
};
now,
mint_result.rewards_amount,
genesis,
interval,
);
frame_system::Pallet::<T>::register_extra_weight_unchecked(
T::WeightInfo::on_era_end(),
DispatchClass::Mandatory,
);
match Self::send_rewards_message(&info) {
Some(message_id) => {
Self::deposit_event(Event::RewardsMessageSent {
message_id,
era_index,
total_points: info.total_points,
inflation_amount: mint_result.rewards_amount,
});
}
None => {
// Message failed — queue for automatic retry via on_initialize
if Self::unsent_queue_push((
era_index,
era_start_timestamp,
mint_result.rewards_amount,
)) {
Self::deposit_event(Event::RewardsMessageSendFailed { era_index });
} else {
log::error!(
target: "ext_validators_rewards",
"Unsent reward queue full, cannot enqueue era {era_index}",
);
Self::deposit_event(Event::UnsentQueueFull { era_index });
}
}
}
}
}
}

View file

@ -123,10 +123,10 @@ impl mock_data::Config for Test {}
pub struct MockOkOutboundQueue;
impl crate::types::SendMessage for MockOkOutboundQueue {
type Ticket = crate::types::EraRewardsUtils;
type Message = crate::types::EraRewardsUtils;
type Ticket = crate::types::RewardsPeriodUtils;
type Message = crate::types::RewardsPeriodUtils;
fn build(utils: &crate::types::EraRewardsUtils) -> Option<Self::Ticket> {
fn build(utils: &crate::types::RewardsPeriodUtils) -> Option<Self::Ticket> {
Some(utils.clone())
}
@ -138,6 +138,9 @@ impl crate::types::SendMessage for MockOkOutboundQueue {
}
fn deliver(_: Self::Ticket) -> Result<H256, SendError> {
if OutboundDeliverShouldFail::get() {
return Err(SendError::Halted);
}
Ok(H256::zero())
}
}
@ -160,6 +163,9 @@ impl ExternalIndexProvider for TimestampProvider {
parameter_types! {
pub RewardsEthereumSovereignAccount: H160 = REWARDS_ACCOUNT;
pub TreasuryAccount: H160 = TREASURY_ACCOUNT;
pub static OutboundDeliverShouldFail: bool = false;
pub static RewardsWindowGenesisTimestamp: u32 = 0;
pub static RewardsWindowDuration: u32 = 10;
pub const InflationTreasuryProportion: sp_runtime::Perbill = sp_runtime::Perbill::from_percent(20);
pub EraInflationProvider: u128 = Mock::mock().era_inflation.unwrap_or(42);
// Inflation scaling parameters for tests
@ -226,6 +232,9 @@ impl pallet_external_validators_rewards::Config for Test {
type HandleInflation = InflationMinter;
type Currency = Balances;
type RewardsEthereumSovereignAccount = RewardsEthereumSovereignAccount;
type RewardsWindowGenesisTimestamp = RewardsWindowGenesisTimestamp;
type RewardsWindowDuration = RewardsWindowDuration;
type UnixTime = Timestamp;
type GovernanceOrigin = frame_system::EnsureRoot<H160>;
type WeightInfo = ();
#[cfg(feature = "runtime-benchmarks")]
@ -283,6 +292,7 @@ impl HandleInflation<H160> for InflationMinter {
// Pallet to provide some mock data, used to test
#[frame_support::pallet]
pub mod mock_data {
use alloc::vec::Vec;
use {
frame_support::pallet_prelude::*,
pallet_external_validators::traits::{ActiveEraInfo, EraIndex, EraIndexProvider},
@ -293,9 +303,9 @@ pub mod mock_data {
pub active_era: Option<ActiveEraInfo>,
pub era_inflation: Option<u128>,
/// Set of validators that are considered offline (for liveness testing)
pub offline_validators: sp_std::vec::Vec<sp_core::H160>,
pub offline_validators: Vec<sp_core::H160>,
/// Set of (era_index, validator_id) pairs that are slashed
pub slashed_validators: sp_std::vec::Vec<(u32, sp_core::H160)>,
pub slashed_validators: Vec<(u32, sp_core::H160)>,
/// When true, MockOkOutboundQueue::validate will return Err(SendError::MessageTooLarge)
pub send_message_fails: bool,
}
@ -357,12 +367,17 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
ExistentialDeposit::get(),
), // Rewards account needs existential deposit
];
pallet_balances::GenesisConfig::<Test> { balances }
.assimilate_storage(&mut t)
.unwrap();
let ext: sp_io::TestExternalities = t.into();
pallet_balances::GenesisConfig::<Test> {
balances,
dev_accounts: Default::default(),
}
.assimilate_storage(&mut t)
.unwrap();
let mut ext: sp_io::TestExternalities = t.into();
ext.execute_with(|| {
Timestamp::set_timestamp(INIT_TIMESTAMP);
});
ext
}

File diff suppressed because it is too large Load diff

View file

@ -14,15 +14,16 @@
// You should have received a copy of the GNU General Public License
// along with Tanssi. If not, see <http://www.gnu.org/licenses/>
use alloc::vec::Vec;
use snowbridge_outbound_queue_primitives::SendError;
use sp_core::{H160, H256};
use sp_std::vec::Vec;
/// Data needed for EigenLayer rewards submission via Snowbridge.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct EraRewardsUtils {
pub era_index: u32,
pub era_start_timestamp: u32,
pub struct RewardsPeriodUtils {
pub period_index: u32,
pub period_start: u32,
pub duration: u32,
pub total_points: u128,
pub individual_points: Vec<(H160, u32)>,
pub inflation_amount: u128,
@ -32,7 +33,7 @@ pub trait SendMessage {
type Message;
type Ticket;
fn build(utils: &EraRewardsUtils) -> Option<Self::Message>;
fn build(utils: &RewardsPeriodUtils) -> Option<Self::Message>;
fn validate(message: Self::Message) -> Result<Self::Ticket, SendError>;

View file

@ -49,11 +49,13 @@
#![allow(unused_imports)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use sp_std::marker::PhantomData;
use core::marker::PhantomData;
/// Weight functions needed for pallet_external_validators_rewards.
pub trait WeightInfo {
fn on_era_end() -> Weight;
fn process_closed_windows_idle() -> Weight;
fn process_closed_windows_processed() -> Weight;
fn process_unsent_reward_eras_empty() -> Weight;
fn process_unsent_reward_eras_expired() -> Weight;
fn process_unsent_reward_eras_success() -> Weight;
@ -90,6 +92,17 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
.saturating_add(T::DbWeight::get().writes(5_u64))
}
fn process_closed_windows_idle() -> Weight {
Weight::from_parts(10_000_000, 0)
.saturating_add(T::DbWeight::get().reads(3_u64))
}
fn process_closed_windows_processed() -> Weight {
// Use the existing resend success path as an upper bound for a single
// closed-window submission/skip attempt.
Self::process_unsent_reward_eras_success()
}
fn process_unsent_reward_eras_empty() -> Weight {
// 1 read for UnsentRewardEras
Weight::from_parts(5_000_000, 0)
@ -149,6 +162,15 @@ impl WeightInfo for () {
.saturating_add(RocksDbWeight::get().writes(5_u64))
}
fn process_closed_windows_idle() -> Weight {
Weight::from_parts(10_000_000, 0)
.saturating_add(RocksDbWeight::get().reads(3_u64))
}
fn process_closed_windows_processed() -> Weight {
Self::process_unsent_reward_eras_success()
}
fn process_unsent_reward_eras_empty() -> Weight {
Weight::from_parts(5_000_000, 0)
.saturating_add(RocksDbWeight::get().reads(1_u64))

View file

@ -24,7 +24,6 @@ frame-system = { workspace = true }
impl-trait-for-tuples = { workspace = true }
sp-runtime = { workspace = true }
sp-staking = { workspace = true }
sp-std = { workspace = true }
sp-core = { workspace = true }
frame-benchmarking = { workspace = true }
@ -56,7 +55,6 @@ std = [
"sp-io/std",
"sp-runtime/std",
"sp-staking/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",

View file

@ -27,7 +27,6 @@ use {
pallet_session::{self as session, SessionManager},
rand::{RngCore, SeedableRng},
sp_runtime::{codec, traits::Convert},
sp_std::prelude::*,
};
const SEED: u32 = 0;

View file

@ -31,16 +31,17 @@
//! The structure of this pallet and the concept of eras is inspired by `pallet_staking` from Polkadot.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
pub use pallet::*;
use {
alloc::{collections::btree_set::BTreeSet, vec::Vec},
frame_support::pallet_prelude::Weight,
log::log,
parity_scale_codec::{Decode, Encode, MaxEncodedLen},
parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen},
scale_info::TypeInfo,
sp_runtime::{traits::Get, RuntimeDebug},
sp_staking::SessionIndex,
sp_std::{collections::btree_set::BTreeSet, vec::Vec},
traits::{
ActiveEraInfo, EraIndex, EraIndexProvider, ExternalIndexProvider, InvulnerablesProvider,
OnEraEnd, OnEraStart, ValidatorProvider,
@ -92,6 +93,7 @@ pub mod pallet {
use frame_support::traits::Currency;
use {
super::*,
alloc::vec::Vec,
frame_support::{
dispatch::DispatchResultWithPostInfo,
pallet_prelude::*,
@ -101,7 +103,6 @@ pub mod pallet {
frame_system::pallet_prelude::*,
sp_core::H160,
sp_runtime::{traits::Convert, SaturatedConversion},
sp_std::vec::Vec,
};
/// Configure the pallet by specifying the parameters and types on which it depends.
@ -255,7 +256,7 @@ pub mod pallet {
// T::ValidatorId does not impl Ord or Hash so we cannot collect into set directly,
// but we can check for duplicates if we encode them first.
.map(|x| x.encode())
.collect::<sp_std::collections::btree_set::BTreeSet<_>>();
.collect::<alloc::collections::btree_set::BTreeSet<_>>();
assert!(
duplicate_validators.len() == self.whitelisted_validators.len(),
"duplicate validators in genesis."
@ -763,7 +764,17 @@ impl<T: Config> InvulnerablesProvider<T::ValidatorId> for Pallet<T> {
/// Mode of era-forcing.
#[derive(
Copy, Clone, PartialEq, Eq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen,
Copy,
Clone,
PartialEq,
Eq,
Default,
Encode,
Decode,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
DecodeWithMemTracking,
)]
pub enum Forcing {
/// Not forcing anything - just let whatever happen.

View file

@ -199,6 +199,7 @@ impl pallet_session::Config for Test {
type SessionHandler = TestSessionHandler;
type Keys = MockSessionKeys;
type WeightInfo = ();
type DisablingStrategy = ();
}
// Pallet to provide some mock data, used to test
@ -298,9 +299,12 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
keys,
..Default::default()
};
pallet_balances::GenesisConfig::<Test> { balances }
.assimilate_storage(&mut t)
.unwrap();
pallet_balances::GenesisConfig::<Test> {
balances,
dev_accounts: Default::default(),
}
.assimilate_storage(&mut t)
.unwrap();
pallet_external_validators::GenesisConfig::<Test> {
skip_external_validators: false,
whitelisted_validators,

View file

@ -14,12 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with Tanssi. If not, see <http://www.gnu.org/licenses/>
use alloc::vec::Vec;
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use snowbridge_outbound_queue_primitives::SendError;
use sp_core::H256;
use sp_runtime::RuntimeDebug;
use sp_std::vec::Vec;
/// Information regarding the active era (era in used in session).
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]

View file

@ -49,7 +49,7 @@
#![allow(unused_imports)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use sp_std::marker::PhantomData;
use core::marker::PhantomData;
/// Weight functions needed for pallet_external_validators.
pub trait WeightInfo {

View file

@ -25,7 +25,6 @@ sp-application-crypto = { workspace = true }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
sp-session = { workspace = true }
sp-std = { workspace = true }
[features]
default = ["std"]
@ -42,7 +41,6 @@ std = [
"sp-core/std",
"sp-runtime/std",
"sp-session/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",

View file

@ -30,7 +30,6 @@ pallet-balances = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
xcm = { workspace = true }
xcm-builder = { workspace = true }
@ -87,7 +86,6 @@ std = [
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
"tracing/std",
"xcm-builder/std",
"xcm-executor/std",

View file

@ -20,7 +20,6 @@ snowbridge-beacon-primitives = { workspace = true }
snowbridge-core = { workspace = true }
snowbridge-inbound-queue-primitives = { workspace = true }
sp-core = { workspace = true }
sp-std = { workspace = true }
[features]
default = ["std"]
@ -33,5 +32,4 @@ std = [
"snowbridge-core/std",
"snowbridge-inbound-queue-primitives/std",
"sp-core/std",
"sp-std/std",
]

View file

@ -1,5 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
pub mod register_token;

View file

@ -3,13 +3,13 @@
// Generated, do not edit!
// See ethereum client README.md for instructions to generate
use alloc::vec;
use hex_literal::hex;
use snowbridge_beacon_primitives::{
types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader,
};
use snowbridge_inbound_queue_primitives::{EventProof, InboundQueueFixture, Log, Proof};
use sp_core::U256;
use sp_std::vec;
pub fn make_register_token_message() -> InboundQueueFixture {
InboundQueueFixture {

View file

@ -37,6 +37,7 @@ mod mock;
mod test;
pub use crate::weights::WeightInfo;
use alloc::boxed::Box;
use frame_system::ensure_signed;
use snowbridge_core::{
sparse_bitmap::{SparseBitmap, SparseBitmapImpl},
@ -48,7 +49,6 @@ use snowbridge_inbound_queue_primitives::{
};
use sp_core::H160;
use sp_runtime::traits::Zero;
use sp_std::prelude::*;
use xcm::prelude::*;
#[cfg(feature = "runtime-benchmarks")]

View file

@ -2,10 +2,10 @@
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
use super::*;
use codec::Encode;
use core::marker::PhantomData;
use frame_support::traits::Get;
use sp_runtime::traits::TryConvert;
use sp_runtime::DispatchError;
use sp_std::marker::PhantomData;
use xcm::prelude::{ExecuteXcm, Location, Parachain, SendError, SendXcm, XcmHash};
/// A message processor that simply returns the Blake2_256 hash of the SCALE encoded message

View file

@ -4,6 +4,7 @@ use super::*;
use crate::{self as inbound_queue_v2, message_processors::XcmMessageProcessor};
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use core::{convert::From, default::Default, marker::PhantomData};
use frame_support::{derive_impl, parameter_types, traits::ConstU32};
use hex_literal::hex;
use scale_info::TypeInfo;
@ -17,7 +18,6 @@ use sp_runtime::{
traits::{IdentityLookup, MaybeEquivalence, TryConvert},
BuildStorage, DispatchError,
};
use sp_std::{convert::From, default::Default, marker::PhantomData};
use xcm::{opaque::latest::WESTEND_GENESIS_HASH, prelude::*};
type Block = frame_system::mocking::MockBlock<Test>;
pub use snowbridge_test_utils::mock_xcm::{MockXcmExecutor, MockXcmSender};

View file

@ -13,7 +13,7 @@
#![allow(unused_imports)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use sp_std::marker::PhantomData;
use core::marker::PhantomData;
/// Weight functions needed for ethereum_beacon_client.
pub trait WeightInfo {

View file

@ -29,7 +29,6 @@ sp-arithmetic = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
bp-relayers = { workspace = true }
bridge-hub-common = { workspace = true }
@ -82,7 +81,6 @@ std = [
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
"xcm-builder/std",
"xcm-executor/std",
"xcm/std",

View file

@ -22,7 +22,6 @@ snowbridge-core = { workspace = true }
snowbridge-merkle-tree = { workspace = true }
snowbridge-outbound-queue-primitives = { workspace = true }
sp-api = { workspace = true }
sp-std = { workspace = true }
xcm = { workspace = true }
[features]
@ -35,6 +34,5 @@ std = [
"snowbridge-merkle-tree/std",
"snowbridge-outbound-queue-primitives/std",
"sp-api/std",
"sp-std/std",
"xcm/std",
]

View file

@ -21,6 +21,9 @@ use crate::Pallet as OutboundQueue;
)]
mod benchmarks {
use super::*;
use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
/// Build `Upgrade` message with `MaxMessagePayloadSize`, in the worst-case.
fn build_message<T: Config>() -> (Message, OutboundMessage) {

View file

@ -3,13 +3,13 @@
// Generated, do not edit!
// See ethereum client README.md for instructions to generate
use alloc::vec;
use hex_literal::hex;
use snowbridge_beacon_primitives::{
types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader,
};
use snowbridge_verification_primitives::{EventFixture, EventProof, Log, Proof};
use sp_core::U256;
use sp_std::vec;
pub fn make_submit_delivery_receipt_message() -> EventFixture {
EventFixture {

View file

@ -49,6 +49,8 @@
//!
//! * `prove_message`: Generate a merkle proof for a committed message
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
pub mod api;
pub mod process_message_impl;
pub mod send_message_impl;
@ -93,7 +95,6 @@ use sp_runtime::{
traits::{BlockNumberProvider, Hash, MaybeEquivalence},
DigestItem,
};
use sp_std::prelude::*;
pub use types::{OnNewCommitment, PendingOrder, ProcessMessageOriginOf};
pub use weights::WeightInfo;
use xcm::latest::{Location, NetworkId};
@ -107,6 +108,8 @@ pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use alloc::boxed::Box;
use alloc::vec::Vec;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;

View file

@ -10,6 +10,7 @@ use frame_support::{
};
use codec::{DecodeWithMemTracking, Encode, MaxEncodedLen};
use core::marker::PhantomData;
use hex_literal::hex;
use scale_info::TypeInfo;
use snowbridge_core::{
@ -23,7 +24,6 @@ use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup, Keccak256},
AccountId32, BuildStorage, FixedU128,
};
use sp_std::marker::PhantomData;
use xcm::prelude::Here;
use xcm_executor::traits::ConvertLocation;

View file

@ -7,7 +7,6 @@ use scale_info::TypeInfo;
pub use snowbridge_merkle_tree::MerkleProof;
use sp_core::H256;
use sp_runtime::RuntimeDebug;
use sp_std::prelude::*;
pub type ProcessMessageOriginOf<T> = <Pallet<T> as ProcessMessage>::Origin;

View file

@ -13,7 +13,6 @@ frame-system = { workspace = true }
pallet-proxy = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
[dev-dependencies]
pallet-balances = { workspace = true, features = ["insecure_zero_ed", "std"] }
@ -30,7 +29,6 @@ std = [
"pallet-proxy/std",
"scale-info/std",
"sp-runtime/std",
"sp-std/std",
]
try-runtime = [
"frame-support/try-runtime",

View file

@ -21,6 +21,7 @@
//! obstacles to including it here.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(test)]
mod mock;
@ -32,9 +33,9 @@ pub use pallet::*;
#[pallet]
pub mod pallet {
use alloc::vec::Vec;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::BlockNumberFor;
use sp_std::vec::Vec;
/// Pallet for configuring proxy at genesis
#[pallet::pallet]
@ -44,7 +45,11 @@ pub mod pallet {
/// This pallet requires pallet-proxy to be installed.
#[pallet::config]
pub trait Config:
frame_system::Config + pallet_proxy::Config<ProxyType = <Self as Config>::ProxyType>
frame_system::Config
+ pallet_proxy::Config<
ProxyType = <Self as Config>::ProxyType,
BlockNumberProvider = frame_system::Pallet<Self>,
>
{
/// This MUST be the same as in pallet_proxy or it won't compile
type ProxyType: MaybeSerializeDeserialize + Clone;

View file

@ -17,7 +17,7 @@
//! A minimal runtime including the proxy-genesis-companion pallet
use super::*;
use crate as proxy_companion;
use codec::{Decode, Encode, MaxEncodedLen};
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use frame_support::{
construct_runtime, derive_impl, parameter_types,
traits::{ConstU32, InstanceFilter},
@ -79,6 +79,7 @@ parameter_types! {
Debug,
MaxEncodedLen,
scale_info::TypeInfo,
DecodeWithMemTracking,
serde::Serialize,
serde::Deserialize,
Default,
@ -108,6 +109,7 @@ impl pallet_proxy::Config for Test {
type CallHasher = BlakeTwo256;
type AnnouncementDepositBase = AnnouncementDepositBase;
type AnnouncementDepositFactor = AnnouncementDepositFactor;
type BlockNumberProvider = System;
}
impl Config for Test {
@ -147,7 +149,7 @@ impl ExtBuilder {
pallet_balances::GenesisConfig::<Test> {
balances: self.balances,
..Default::default()
dev_accounts: Default::default(),
}
.assimilate_storage(&mut t)
.expect("Pallet balances storage can be assimilated");

View file

@ -19,7 +19,6 @@ frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-session = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
[features]
default = ["std"]
@ -30,7 +29,6 @@ std = [
"frame-system/std",
"pallet-session/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",

View file

@ -28,7 +28,6 @@ snowbridge-pallet-system = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
xcm = { workspace = true }
xcm-executor = { workspace = true }
@ -70,7 +69,6 @@ std = [
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
"xcm-executor/std",
"xcm/std",
]

View file

@ -21,7 +21,6 @@ exclude-from-umbrella = true
codec = { features = ["derive"], workspace = true }
snowbridge-core.workspace = true
sp-api.workspace = true
sp-std.workspace = true
xcm.workspace = true
[features]
@ -30,6 +29,5 @@ std = [
"codec/std",
"snowbridge-core/std",
"sp-api/std",
"sp-std/std",
"xcm/std",
]

View file

@ -15,6 +15,7 @@ use xcm::prelude::*;
#[benchmarks]
mod benchmarks {
use super::*;
use alloc::vec::Vec;
#[benchmark]
fn register_token() -> Result<(), BenchmarkError> {

View file

@ -16,6 +16,8 @@
//!
//! * [`Call::register_token`]: Register a token location as a wrapped ERC20 contract on Ethereum.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(test)]
mod mock;
@ -29,6 +31,8 @@ pub mod api;
pub mod weights;
pub use weights::*;
use alloc::boxed::Box;
use alloc::vec;
use frame_support::{pallet_prelude::*, traits::EnsureOrigin};
use frame_system::pallet_prelude::*;
use snowbridge_core::{AgentIdOf as LocationHashOf, AssetMetadata, TokenId, TokenIdOf};
@ -40,7 +44,6 @@ use snowbridge_pallet_system::{ForeignToNativeId, NativeToForeignId};
use sp_core::{H160, H256};
use sp_io::hashing::blake2_256;
use sp_runtime::traits::MaybeEquivalence;
use sp_std::prelude::*;
use xcm::prelude::*;
use xcm_executor::traits::ConvertLocation;

View file

@ -25,7 +25,6 @@ snowbridge-outbound-queue-primitives.workspace = true
sp-core.workspace = true
sp-io.workspace = true
sp-runtime.workspace = true
sp-std.workspace = true
xcm.workspace = true
xcm-executor.workspace = true
@ -63,7 +62,6 @@ std = [
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
"xcm-executor/std",
"xcm/std",
]

View file

@ -21,7 +21,6 @@ exclude-from-umbrella = true
codec = { features = ["derive"], workspace = true }
snowbridge-core.workspace = true
sp-api.workspace = true
sp-std.workspace = true
xcm.workspace = true
[features]
@ -30,6 +29,5 @@ std = [
"codec/std",
"snowbridge-core/std",
"sp-api/std",
"sp-std/std",
"xcm/std",
]

View file

@ -15,6 +15,8 @@ use xcm::prelude::*;
#[benchmarks]
mod benchmarks {
use super::*;
use alloc::boxed::Box;
use alloc::vec::Vec;
#[benchmark]
fn upgrade() -> Result<(), BenchmarkError> {

View file

@ -18,6 +18,8 @@
//!
//! * [`Call::register_token`]: Register a token location as a wrapped ERC20 contract on Ethereum.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(test)]
mod mock;
@ -32,6 +34,7 @@ pub mod api;
pub mod weights;
pub use weights::*;
use alloc::boxed::Box;
use frame_support::{
pallet_prelude::*,
traits::{
@ -53,7 +56,6 @@ use snowbridge_outbound_queue_primitives::{
use sp_core::{RuntimeDebug, H160, H256};
use sp_io::hashing::blake2_256;
use sp_runtime::{traits::MaybeEquivalence, DispatchError, SaturatedConversion};
use sp_std::prelude::*;
use xcm::prelude::*;
use xcm_executor::traits::ConvertLocation;

Some files were not shown because too many files have changed in this diff Show more