diff --git a/abi/ClaimV6Helper.json b/abi/ClaimV6Helper.json new file mode 100644 index 00000000..f11b5931 --- /dev/null +++ b/abi/ClaimV6Helper.json @@ -0,0 +1,288 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "hubAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "msg", + "type": "string" + } + ], + "name": "UnauthorizedAccess", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressHub", + "type": "error" + }, + { + "inputs": [], + "name": "SCALE18", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "chronos", + "outputs": [ + { + "internalType": "contract Chronos", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "delegatorsInfo", + "outputs": [ + { + "internalType": "contract DelegatorsInfo", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "lastClaimed", + "type": "uint256" + } + ], + "name": "enforceOnlyOneEpochAdvance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "hub", + "outputs": [ + { + "internalType": "contract Hub", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "bytes32", + "name": "delegatorKey", + "type": "bytes32" + } + ], + "name": "prepareForStakeChangeV6", + "outputs": [ + { + "internalType": "uint256", + "name": "delegatorEpochScore", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "profileStorage", + "outputs": [ + { + "internalType": "contract ProfileStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newTs", + "type": "uint256" + } + ], + "name": "setV6NodeCutoffTs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stakingStorage", + "outputs": [ + { + "internalType": "contract StakingStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "v6NodeCutoffTs", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "v6_delegatorsInfo", + "outputs": [ + { + "internalType": "contract V6_DelegatorsInfo", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "v6_randomSamplingStorage", + "outputs": [ + { + "internalType": "contract V6_RandomSamplingStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "validateDelegatorEpochClaimsV6", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/abi/Profile.json b/abi/Profile.json index c1cfc8b2..fa662464 100644 --- a/abi/Profile.json +++ b/abi/Profile.json @@ -379,6 +379,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "v6_delegatorsInfo", + "outputs": [ + { + "internalType": "contract V6_DelegatorsInfo", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", diff --git a/abi/Staking.json b/abi/Staking.json index 158c83ab..7c595146 100644 --- a/abi/Staking.json +++ b/abi/Staking.json @@ -178,16 +178,32 @@ "type": "function" }, { - "inputs": [], - "name": "askContract", + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "bytes32", + "name": "delegatorKey", + "type": "bytes32" + } + ], + "name": "_prepareForStakeChange", "outputs": [ { - "internalType": "contract Ask", - "name": "", - "type": "address" + "internalType": "uint256", + "name": "delegatorEpochScore", + "type": "uint256" } ], - "stateMutability": "view", + "stateMutability": "nonpayable", "type": "function" }, { @@ -198,21 +214,29 @@ "type": "uint72" }, { - "internalType": "uint256[]", - "name": "epochs", - "type": "uint256[]" - }, - { - "internalType": "address[]", - "name": "delegators", - "type": "address[]" + "internalType": "address", + "name": "delegator", + "type": "address" } ], - "name": "batchClaimDelegatorRewards", + "name": "_validateDelegatorEpochClaims", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "askContract", + "outputs": [ + { + "internalType": "contract Ask", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -275,6 +299,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "claimV6Helper", + "outputs": [ + { + "internalType": "contract ClaimV6Helper", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "delegatorsInfo", @@ -585,6 +622,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "v6_delegatorsInfo", + "outputs": [ + { + "internalType": "contract V6_DelegatorsInfo", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "v6_randomSamplingStorage", + "outputs": [ + { + "internalType": "contract V6_RandomSamplingStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", diff --git a/abi/StakingManager.json b/abi/StakingManager.json new file mode 100644 index 00000000..f4e0cbea --- /dev/null +++ b/abi/StakingManager.json @@ -0,0 +1,321 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "hubAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + } + ], + "name": "ProfileDoesntExist", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "msg", + "type": "string" + } + ], + "name": "UnauthorizedAccess", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressHub", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256[]", + "name": "epochs", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "delegators", + "type": "address[]" + } + ], + "name": "batchClaimDelegatorRewardsCombined", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256[]", + "name": "epochs", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "delegators", + "type": "address[]" + } + ], + "name": "batchClaimDelegatorRewardsV6", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256[]", + "name": "epochs", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "delegators", + "type": "address[]" + } + ], + "name": "batchClaimDelegatorRewardsV81", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "chronos", + "outputs": [ + { + "internalType": "contract Chronos", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "claimDelegatorRewardsCombined", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "claimV6Helper", + "outputs": [ + { + "internalType": "contract ClaimV6Helper", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "delegatorsInfo", + "outputs": [ + { + "internalType": "contract DelegatorsInfo", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hub", + "outputs": [ + { + "internalType": "contract Hub", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "profileStorage", + "outputs": [ + { + "internalType": "contract ProfileStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stakingMain", + "outputs": [ + { + "internalType": "contract Staking", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "v6Claim", + "outputs": [ + { + "internalType": "contract V6_Claim", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "v6_delegatorsInfo", + "outputs": [ + { + "internalType": "contract V6_DelegatorsInfo", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "v8_1_1_rewards_period", + "outputs": [ + { + "internalType": "contract V8_1_1_Rewards_Period", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "v8_1_1_rewards_storage", + "outputs": [ + { + "internalType": "contract V8_1_1_Rewards_Period_Storage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/abi/V6_Claim.json b/abi/V6_Claim.json new file mode 100644 index 00000000..4d8467fe --- /dev/null +++ b/abi/V6_Claim.json @@ -0,0 +1,369 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "hubAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + } + ], + "name": "ProfileDoesntExist", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "msg", + "type": "string" + } + ], + "name": "UnauthorizedAccess", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressHub", + "type": "error" + }, + { + "inputs": [], + "name": "SCALE18", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "askContract", + "outputs": [ + { + "internalType": "contract Ask", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "chronos", + "outputs": [ + { + "internalType": "contract Chronos", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "claimDelegatorRewardsV6", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "claimV6Helper", + "outputs": [ + { + "internalType": "contract ClaimV6Helper", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "delegatorsInfo", + "outputs": [ + { + "internalType": "contract DelegatorsInfo", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "epochStorageV6", + "outputs": [ + { + "internalType": "contract EpochStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hub", + "outputs": [ + { + "internalType": "contract Hub", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "identityStorage", + "outputs": [ + { + "internalType": "contract IdentityStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "parametersStorage", + "outputs": [ + { + "internalType": "contract ParametersStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "profileStorage", + "outputs": [ + { + "internalType": "contract ProfileStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "shardingTableContract", + "outputs": [ + { + "internalType": "contract ShardingTable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "shardingTableStorage", + "outputs": [ + { + "internalType": "contract ShardingTableStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stakingMain", + "outputs": [ + { + "internalType": "contract Staking", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stakingStorage", + "outputs": [ + { + "internalType": "contract StakingStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenContract", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "v6_delegatorsInfo", + "outputs": [ + { + "internalType": "contract V6_DelegatorsInfo", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "v6_randomSamplingStorage", + "outputs": [ + { + "internalType": "contract V6_RandomSamplingStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "v8_1_1_rewards_period", + "outputs": [ + { + "internalType": "contract V8_1_1_Rewards_Period", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "v8_1_1_rewards_storage", + "outputs": [ + { + "internalType": "contract V8_1_1_Rewards_Period_Storage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/abi/V6_DelegatorsInfo.json b/abi/V6_DelegatorsInfo.json new file mode 100644 index 00000000..15f28a6e --- /dev/null +++ b/abi/V6_DelegatorsInfo.json @@ -0,0 +1,890 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "hubAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "msg", + "type": "string" + } + ], + "name": "UnauthorizedAccess", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressHub", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": true, + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "DelegatorAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": true, + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newLastClaimedEpoch", + "type": "uint256" + } + ], + "name": "DelegatorLastClaimedEpochUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": true, + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "DelegatorRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": true, + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTotalRollingRewards", + "type": "uint256" + } + ], + "name": "DelegatorRollingRewardsUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "delegatorKey", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bool", + "name": "claimed", + "type": "bool" + } + ], + "name": "HasDelegatorClaimedEpochRewardsUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": true, + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "hasEverDelegatedToNode", + "type": "bool" + } + ], + "name": "HasEverDelegatedToNodeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isClaimed", + "type": "bool" + } + ], + "name": "IsOperatorFeeClaimedForEpochUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": true, + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "name": "LastStakeHeldEpochUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "NetNodeEpochRewardsSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "name": "V812ReleaseEpochUpdated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "addDelegatorRollingRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "", + "type": "uint72" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "delegatorRollingRewards", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "getDelegatorRollingRewards", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "getLastClaimedEpoch", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "getLastStakeHeldEpoch", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "name": "getNetNodeEpochRewards", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "", + "type": "uint72" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "hasDelegatorClaimedEpochRewards", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "", + "type": "uint72" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "hasEverDelegatedToNode", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hub", + "outputs": [ + { + "internalType": "contract Hub", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "", + "type": "uint72" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isDelegatorMap", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "isOperatorFeeClaimedForEpoch", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "", + "type": "uint72" + } + ], + "name": "lastClaimedDelegatorsRewardsEpoch", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "", + "type": "uint72" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "lastClaimedEpoch", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "", + "type": "uint72" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "lastStakeHeldEpoch", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "netNodeEpochRewards", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "", + "type": "uint72" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nodeDelegatorIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "setDelegatorRollingRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "bytes32", + "name": "delegatorKey", + "type": "bytes32" + }, + { + "internalType": "bool", + "name": "claimed", + "type": "bool" + } + ], + "name": "setHasDelegatorClaimedEpochRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "internalType": "bool", + "name": "_hasEverDelegatedToNode", + "type": "bool" + } + ], + "name": "setHasEverDelegatedToNode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isClaimed", + "type": "bool" + } + ], + "name": "setIsOperatorFeeClaimedForEpoch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "name": "setLastClaimedEpoch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "name": "setLastStakeHeldEpoch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "setNetNodeEpochRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + } + ], + "name": "setV812ReleaseEpoch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "v812ReleaseEpoch", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/abi/V6_RandomSampling.json b/abi/V6_RandomSampling.json new file mode 100644 index 00000000..6ffd513a --- /dev/null +++ b/abi/V6_RandomSampling.json @@ -0,0 +1,442 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "hubAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "computedMerkleRoot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "expectedMerkleRoot", + "type": "bytes32" + } + ], + "name": "MerkleRootMismatchError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + } + ], + "name": "ProfileDoesntExist", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "msg", + "type": "string" + } + ], + "name": "UnauthorizedAccess", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressHub", + "type": "error" + }, + { + "inputs": [], + "name": "SCALE18", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "askStorage", + "outputs": [ + { + "internalType": "contract AskStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + } + ], + "name": "calculateNodeScore", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "chronos", + "outputs": [ + { + "internalType": "contract Chronos", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimV6Helper", + "outputs": [ + { + "internalType": "contract ClaimV6Helper", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "createChallenge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "delegatorsInfo", + "outputs": [ + { + "internalType": "contract DelegatorsInfo", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "epochStorage", + "outputs": [ + { + "internalType": "contract EpochStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getActiveProofPeriodStatus", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "activeProofPeriodStartBlock", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isValid", + "type": "bool" + } + ], + "internalType": "struct RandomSamplingLib.ProofPeriodStatus", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getActiveProofingPeriodDurationInBlocks", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proofPeriodStartBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + } + ], + "name": "getHistoricalProofPeriodStartBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hub", + "outputs": [ + { + "internalType": "contract Hub", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "identityStorage", + "outputs": [ + { + "internalType": "contract IdentityStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isPendingProofingPeriodDuration", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "knowledgeCollectionStorage", + "outputs": [ + { + "internalType": "contract KnowledgeCollectionStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "parametersStorage", + "outputs": [ + { + "internalType": "contract ParametersStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "profileStorage", + "outputs": [ + { + "internalType": "contract ProfileStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "durationInBlocks", + "type": "uint16" + } + ], + "name": "setProofingPeriodDurationInBlocks", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "shardingTableStorage", + "outputs": [ + { + "internalType": "contract ShardingTableStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stakingStorage", + "outputs": [ + { + "internalType": "contract StakingStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "chunk", + "type": "string" + }, + { + "internalType": "bytes32[]", + "name": "merkleProof", + "type": "bytes32[]" + } + ], + "name": "submitProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "updateAndGetActiveProofPeriodStartBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "v6_randomSamplingStorage", + "outputs": [ + { + "internalType": "contract V6_RandomSamplingStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/abi/V6_RandomSamplingStorage.json b/abi/V6_RandomSamplingStorage.json new file mode 100644 index 00000000..cd125f73 --- /dev/null +++ b/abi/V6_RandomSamplingStorage.json @@ -0,0 +1,1744 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "hubAddress", + "type": "address" + }, + { + "internalType": "uint16", + "name": "_proofingPeriodDurationInBlocks", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "_w1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_w2", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "msg", + "type": "string" + } + ], + "name": "UnauthorizedAccess", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressHub", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "activeProofPeriodStartBlock", + "type": "uint256" + } + ], + "name": "ActiveProofPeriodStartBlockSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "scoreAdded", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalScore", + "type": "uint256" + } + ], + "name": "AllNodesEpochScoreAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newScore", + "type": "uint256" + } + ], + "name": "AllNodesEpochScoreSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "delegatorKey", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newDelegatorLastSettledNodeEpochScorePerStake", + "type": "uint256" + } + ], + "name": "DelegatorLastSettledNodeEpochScorePerStakeSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "delegatorKey", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "scoreAdded", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalScore", + "type": "uint256" + } + ], + "name": "EpochNodeDelegatorScoreAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "delegatorKey", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newScore", + "type": "uint256" + } + ], + "name": "EpochNodeDelegatorScoreSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newCount", + "type": "uint256" + } + ], + "name": "EpochNodeValidProofsCountIncremented", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newCount", + "type": "uint256" + } + ], + "name": "EpochNodeValidProofsCountSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "knowledgeCollectionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chunkId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "knowledgeCollectionStorageContract", + "type": "address" + }, + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "activeProofPeriodStartBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proofingPeriodDurationInBlocks", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "solved", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct RandomSamplingLib.Challenge", + "name": "challenge", + "type": "tuple" + } + ], + "name": "NodeChallengeSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "proofPeriodStartBlock", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "scoreAdded", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalScore", + "type": "uint256" + } + ], + "name": "NodeEpochProofPeriodScoreAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "proofPeriodStartBlock", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newScore", + "type": "uint256" + } + ], + "name": "NodeEpochProofPeriodScoreSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "scoreAdded", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalScore", + "type": "uint256" + } + ], + "name": "NodeEpochScoreAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "scorePerStakeToAdd", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalNodeEpochScorePerStake", + "type": "uint256" + } + ], + "name": "NodeEpochScorePerStakeAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newScorePerStake", + "type": "uint256" + } + ], + "name": "NodeEpochScorePerStakeSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newScore", + "type": "uint256" + } + ], + "name": "NodeEpochScoreSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "oldDurationInBlocks", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "newDurationInBlocks", + "type": "uint16" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "effectiveEpoch", + "type": "uint256" + } + ], + "name": "PendingProofingPeriodDurationReplaced", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "durationInBlocks", + "type": "uint16" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "effectiveEpoch", + "type": "uint256" + } + ], + "name": "ProofingPeriodDurationAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldW1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newW1", + "type": "uint256" + } + ], + "name": "W1Set", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldW2", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newW2", + "type": "uint256" + } + ], + "name": "W2Set", + "type": "event" + }, + { + "inputs": [], + "name": "CHUNK_BYTE_SIZE", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "durationInBlocks", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "effectiveEpoch", + "type": "uint256" + } + ], + "name": "addProofingPeriodDuration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "score", + "type": "uint256" + } + ], + "name": "addToAllNodesEpochScore", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "bytes32", + "name": "delegatorKey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "score", + "type": "uint256" + } + ], + "name": "addToEpochNodeDelegatorScore", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proofPeriodStartBlock", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "score", + "type": "uint256" + } + ], + "name": "addToNodeEpochProofPeriodScore", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "score", + "type": "uint256" + } + ], + "name": "addToNodeEpochScore", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "scorePerStakeToAdd", + "type": "uint256" + } + ], + "name": "addToNodeEpochScorePerStake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allNodesEpochScore", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "chronos", + "outputs": [ + { + "internalType": "contract Chronos", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "", + "type": "uint72" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "delegatorLastSettledNodeEpochScorePerStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "", + "type": "uint72" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "epochNodeDelegatorScore", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "", + "type": "uint72" + } + ], + "name": "epochNodeValidProofsCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getActiveProofPeriodStartBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "name": "getAllNodesEpochScore", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "bytes32", + "name": "delegatorKey", + "type": "bytes32" + } + ], + "name": "getDelegatorLastSettledNodeEpochScorePerStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "bytes32", + "name": "delegatorKey", + "type": "bytes32" + } + ], + "name": "getEpochNodeDelegatorScore", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + } + ], + "name": "getEpochNodeValidProofsCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "name": "getEpochProofingPeriodDurationInBlocks", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLatestProofingPeriodDurationEffectiveEpoch", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLatestProofingPeriodDurationInBlocks", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + } + ], + "name": "getNodeChallenge", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "knowledgeCollectionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chunkId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "knowledgeCollectionStorageContract", + "type": "address" + }, + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "activeProofPeriodStartBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proofingPeriodDurationInBlocks", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "solved", + "type": "bool" + } + ], + "internalType": "struct RandomSamplingLib.Challenge", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proofPeriodStartBlock", + "type": "uint256" + } + ], + "name": "getNodeEpochProofPeriodScore", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + } + ], + "name": "getNodeEpochScore", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + } + ], + "name": "getNodeEpochScorePerStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getProofingPeriodDurationFromIndex", + "outputs": [ + { + "components": [ + { + "internalType": "uint16", + "name": "durationInBlocks", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "effectiveEpoch", + "type": "uint256" + } + ], + "internalType": "struct RandomSamplingLib.ProofingPeriodDuration", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProofingPeriodDurationsLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getW1", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getW2", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hub", + "outputs": [ + { + "internalType": "contract Hub", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + } + ], + "name": "incrementEpochNodeValidProofsCount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "nodeEpochProofPeriodScore", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "nodeEpochScore", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "", + "type": "uint72" + } + ], + "name": "nodeEpochScorePerStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "", + "type": "uint72" + } + ], + "name": "nodesChallenges", + "outputs": [ + { + "internalType": "uint256", + "name": "knowledgeCollectionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chunkId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "knowledgeCollectionStorageContract", + "type": "address" + }, + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "activeProofPeriodStartBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proofingPeriodDurationInBlocks", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "solved", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "proofingPeriodDurations", + "outputs": [ + { + "internalType": "uint16", + "name": "durationInBlocks", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "effectiveEpoch", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "durationInBlocks", + "type": "uint16" + }, + { + "internalType": "uint256", + "name": "effectiveEpoch", + "type": "uint256" + } + ], + "name": "replacePendingProofingPeriodDuration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newActiveProofPeriodStartBlock", + "type": "uint256" + } + ], + "name": "setActiveProofPeriodStartBlock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "score", + "type": "uint256" + } + ], + "name": "setAllNodesEpochScore", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "bytes32", + "name": "delegatorKey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "newNodeEpochScorePerStake", + "type": "uint256" + } + ], + "name": "setDelegatorLastSettledNodeEpochScorePerStake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "bytes32", + "name": "delegatorKey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "score", + "type": "uint256" + } + ], + "name": "setEpochNodeDelegatorScore", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "count", + "type": "uint256" + } + ], + "name": "setEpochNodeValidProofsCount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "knowledgeCollectionId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chunkId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "knowledgeCollectionStorageContract", + "type": "address" + }, + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "activeProofPeriodStartBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proofingPeriodDurationInBlocks", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "solved", + "type": "bool" + } + ], + "internalType": "struct RandomSamplingLib.Challenge", + "name": "challenge", + "type": "tuple" + } + ], + "name": "setNodeChallenge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proofPeriodStartBlock", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "score", + "type": "uint256" + } + ], + "name": "setNodeEpochProofPeriodScore", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "score", + "type": "uint256" + } + ], + "name": "setNodeEpochScore", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "uint256", + "name": "scorePerStake", + "type": "uint256" + } + ], + "name": "setNodeEpochScorePerStake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_w1", + "type": "uint256" + } + ], + "name": "setW1", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_w2", + "type": "uint256" + } + ], + "name": "setW2", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "w1", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "w2", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/abi/V8_1_1_Rewards_Period.json b/abi/V8_1_1_Rewards_Period.json new file mode 100644 index 00000000..85693609 --- /dev/null +++ b/abi/V8_1_1_Rewards_Period.json @@ -0,0 +1,262 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "hubAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "msg", + "type": "string" + } + ], + "name": "UnauthorizedAccess", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressHub", + "type": "error" + }, + { + "inputs": [], + "name": "askContract", + "outputs": [ + { + "internalType": "contract Ask", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "chronos", + "outputs": [ + { + "internalType": "contract Chronos", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimV6Helper", + "outputs": [ + { + "internalType": "contract ClaimV6Helper", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "claimV8TuningPeriodRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "delegatorsInfo", + "outputs": [ + { + "internalType": "contract DelegatorsInfo", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hub", + "outputs": [ + { + "internalType": "contract Hub", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "parametersStorage", + "outputs": [ + { + "internalType": "contract ParametersStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "randomSamplingStorage", + "outputs": [ + { + "internalType": "contract RandomSamplingStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardsStorage", + "outputs": [ + { + "internalType": "contract V8_1_1_Rewards_Period_Storage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "shardingTable", + "outputs": [ + { + "internalType": "contract ShardingTable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "shardingTableStorage", + "outputs": [ + { + "internalType": "contract ShardingTableStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stakingMain", + "outputs": [ + { + "internalType": "contract Staking", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stakingStorage", + "outputs": [ + { + "internalType": "contract StakingStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/abi/V8_1_1_Rewards_Period_Storage.json b/abi/V8_1_1_Rewards_Period_Storage.json new file mode 100644 index 00000000..7ecb7132 --- /dev/null +++ b/abi/V8_1_1_Rewards_Period_Storage.json @@ -0,0 +1,218 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "hubAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "msg", + "type": "string" + } + ], + "name": "UnauthorizedAccess", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressHub", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "getReward", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + }, + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "hasReward", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hub", + "outputs": [ + { + "internalType": "contract Hub", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + } + ], + "name": "markClaimed", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "internalType": "uint96", + "name": "amount", + "type": "uint96" + } + ], + "name": "setDelegatorReward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "address[]", + "name": "delegators", + "type": "address[]" + }, + { + "internalType": "uint96[]", + "name": "amounts", + "type": "uint96[]" + } + ], + "name": "setDelegatorsRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/contracts/ClaimV6Helper.sol b/contracts/ClaimV6Helper.sol new file mode 100644 index 00000000..e52c77ef --- /dev/null +++ b/contracts/ClaimV6Helper.sol @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.20; + +import {ContractStatus} from "./abstract/ContractStatus.sol"; +import {INamed} from "./interfaces/INamed.sol"; +import {IVersioned} from "./interfaces/IVersioned.sol"; + +import {V6_RandomSamplingStorage} from "./storage/V6_RandomSamplingStorage.sol"; +import {StakingStorage} from "./storage/StakingStorage.sol"; +import {DelegatorsInfo} from "./storage/DelegatorsInfo.sol"; +import {V6_DelegatorsInfo} from "./storage/V6_DelegatorsInfo.sol"; +import {Chronos} from "./storage/Chronos.sol"; +import {ProfileStorage} from "./storage/ProfileStorage.sol"; + +contract ClaimV6Helper is INamed, IVersioned, ContractStatus { + string private constant _NAME = "ClaimV6Helper"; + string private constant _VERSION = "1.0.0"; + + uint256 public constant SCALE18 = 1e18; + + V6_RandomSamplingStorage public v6_randomSamplingStorage; + StakingStorage public stakingStorage; + DelegatorsInfo public delegatorsInfo; + V6_DelegatorsInfo public v6_delegatorsInfo; + Chronos public chronos; + ProfileStorage public profileStorage; + + // V6_NODE_CUTOFF timestamp; default 03-Sep-2024 UTC; Hub can update + uint256 public v6NodeCutoffTs; + + constructor(address hubAddress) ContractStatus(hubAddress) {} + + function initialize() external onlyHub { + v6_randomSamplingStorage = V6_RandomSamplingStorage(hub.getContractAddress("V6_RandomSamplingStorage")); + stakingStorage = StakingStorage(hub.getContractAddress("StakingStorage")); + delegatorsInfo = DelegatorsInfo(hub.getContractAddress("DelegatorsInfo")); + v6_delegatorsInfo = V6_DelegatorsInfo(hub.getContractAddress("V6_DelegatorsInfo")); + chronos = Chronos(hub.getContractAddress("Chronos")); + profileStorage = ProfileStorage(hub.getContractAddress("ProfileStorage")); + + // Default cutoff 02-Aug-2025 00:00:00 UTC + // TODO: Cutoff time is wrong - it should be the timestamp of v8.0 Hub release + v6NodeCutoffTs = 1754092800; + } + + // Hub owner can update cutoff + // TODO: change this to owner or multisig owner + function setV6NodeCutoffTs(uint256 newTs) external onlyHub { + v6NodeCutoffTs = newTs; + } + + function prepareForStakeChangeV6( + uint256 epoch, + uint72 identityId, + bytes32 delegatorKey + ) external onlyContracts returns (uint256 delegatorEpochScore) { + if (profileStorage.getOperatorFeeEffectiveDateByIndex(identityId, 0) >= v6NodeCutoffTs) { + return 0; + } + uint256 nodeScorePerStake36 = v6_randomSamplingStorage.getNodeEpochScorePerStake(epoch, identityId); + + uint256 currentDelegatorScore18 = v6_randomSamplingStorage.getEpochNodeDelegatorScore( + epoch, + identityId, + delegatorKey + ); + + uint256 delegatorLastSettledNodeEpochScorePerStake36 = v6_randomSamplingStorage + .getDelegatorLastSettledNodeEpochScorePerStake(epoch, identityId, delegatorKey); + + if (nodeScorePerStake36 == delegatorLastSettledNodeEpochScorePerStake36) { + return currentDelegatorScore18; + } + + uint96 stakeBase = stakingStorage.getDelegatorStakeBase(identityId, delegatorKey); + + if (stakeBase == 0) { + v6_randomSamplingStorage.setDelegatorLastSettledNodeEpochScorePerStake( + epoch, + identityId, + delegatorKey, + nodeScorePerStake36 + ); + return currentDelegatorScore18; + } + + uint256 scorePerStakeDiff36 = nodeScorePerStake36 - delegatorLastSettledNodeEpochScorePerStake36; + uint256 scoreEarned18 = (uint256(stakeBase) * scorePerStakeDiff36) / SCALE18; + + if (scoreEarned18 > 0) { + v6_randomSamplingStorage.addToEpochNodeDelegatorScore(epoch, identityId, delegatorKey, scoreEarned18); + } + + v6_randomSamplingStorage.setDelegatorLastSettledNodeEpochScorePerStake( + epoch, + identityId, + delegatorKey, + nodeScorePerStake36 + ); + + return currentDelegatorScore18 + scoreEarned18; + } + + // Replicates Staking's pre-stake-change validation but for V6 stores. + // Can be called by other Hub-registered contracts (e.g., Staking). + function validateDelegatorEpochClaimsV6(uint72 identityId, address delegator) external onlyContracts { + if (profileStorage.getOperatorFeeEffectiveDateByIndex(identityId, 0) >= v6NodeCutoffTs) { + return; + } + + bytes32 delegatorKey = keccak256(abi.encodePacked(delegator)); + uint256 currentEpoch = chronos.getCurrentEpoch(); + uint256 previousEpoch = currentEpoch - 1; + + // Check whether delegator has ever staked on this node using the *main* DelegatorsInfo store + if (delegatorsInfo.hasEverDelegatedToNode(identityId, delegator)) { + if (stakingStorage.getDelegatorStakeBase(identityId, delegatorKey) == 0) { + uint256 lastStakeHeldEpoch = delegatorsInfo.getLastStakeHeldEpoch(identityId, delegator); + if (lastStakeHeldEpoch > 0 && lastStakeHeldEpoch < currentEpoch) { + revert("Must claim rewards up to the lastStakeHeldEpoch before changing stake"); + } + // Rewards either not yet claimable or already claimed – sync last claimed epoch + v6_delegatorsInfo.setLastClaimedEpoch(identityId, delegator, previousEpoch); + } + } else { + // First time delegating on this node – mark it in main store and sync V6 claims pointer + delegatorsInfo.setHasEverDelegatedToNode(identityId, delegator, true); + v6_delegatorsInfo.setLastClaimedEpoch(identityId, delegator, previousEpoch); + } + + uint256 lastClaimedEpoch = v6_delegatorsInfo.getLastClaimedEpoch(identityId, delegator); + if (lastClaimedEpoch == previousEpoch) { + return; // up-to-date + } + if (lastClaimedEpoch < previousEpoch - 1) { + revert("Must claim all previous epoch rewards before changing stake"); + } + + uint256 delegatorScore18 = v6_randomSamplingStorage.getEpochNodeDelegatorScore( + previousEpoch, + identityId, + delegatorKey + ); + uint256 nodeScorePerStake36 = v6_randomSamplingStorage.getNodeEpochScorePerStake(previousEpoch, identityId); + uint256 delegatorLastSettledScorePerStake36 = v6_randomSamplingStorage + .getDelegatorLastSettledNodeEpochScorePerStake(previousEpoch, identityId, delegatorKey); + + if (delegatorScore18 == 0 && nodeScorePerStake36 == delegatorLastSettledScorePerStake36) { + v6_delegatorsInfo.setLastClaimedEpoch(identityId, delegator, previousEpoch); + return; + } + + revert("Must claim the previous epoch rewards before changing stake"); + } + + /** + * @notice Ensures that the main DelegatorsInfo store is not more than one epoch ahead of the legacy V6 store + * for the given delegator. Intended to be called by Staking before claiming rewards. + * @param identityId The node identity ID + * @param delegator Delegator address + * @param lastClaimed The lastClaimed epoch value from the main DelegatorsInfo store + */ + function enforceOnlyOneEpochAdvance( + uint72 identityId, + address delegator, + uint256 lastClaimed + ) external onlyContracts { + // Only relevant for legacy nodes (operator fee entry predates cutoff) + if (profileStorage.getOperatorFeeEffectiveDateByIndex(identityId, 0) < v6NodeCutoffTs) { + uint256 lastClaimedV6 = v6_delegatorsInfo.getLastClaimedEpoch(identityId, delegator); + + if (lastClaimedV6 == 0) { + uint256 v812Epoch = v6_delegatorsInfo.v812ReleaseEpoch(); + v6_delegatorsInfo.setLastClaimedEpoch(identityId, delegator, v812Epoch - 1); + lastClaimedV6 = v812Epoch - 1; + } + + require( + lastClaimed == lastClaimedV6, + "You have unclaimed V6 rewards for previous epoch(s). Claim them before claiming v8 rewards" + ); + } + } + + // INamed & IVersioned + function name() external pure override returns (string memory) { + return _NAME; + } + + function version() external pure override returns (string memory) { + return _VERSION; + } +} diff --git a/contracts/Profile.sol b/contracts/Profile.sol index 4a945574..476b4515 100644 --- a/contracts/Profile.sol +++ b/contracts/Profile.sol @@ -10,6 +10,7 @@ import {ProfileStorage} from "./storage/ProfileStorage.sol"; import {WhitelistStorage} from "./storage/WhitelistStorage.sol"; import {Chronos} from "./storage/Chronos.sol"; import {DelegatorsInfo} from "./storage/DelegatorsInfo.sol"; +import {V6_DelegatorsInfo} from "./storage/V6_DelegatorsInfo.sol"; import {ContractStatus} from "./abstract/ContractStatus.sol"; import {IInitializable} from "./interfaces/IInitializable.sol"; import {INamed} from "./interfaces/INamed.sol"; @@ -30,6 +31,7 @@ contract Profile is INamed, IVersioned, ContractStatus, IInitializable { WhitelistStorage public whitelistStorage; Chronos public chronos; DelegatorsInfo public delegatorsInfo; + V6_DelegatorsInfo public v6_delegatorsInfo; // solhint-disable-next-line no-empty-blocks constructor(address hubAddress) ContractStatus(hubAddress) {} @@ -63,6 +65,7 @@ contract Profile is INamed, IVersioned, ContractStatus, IInitializable { whitelistStorage = WhitelistStorage(hub.getContractAddress("WhitelistStorage")); chronos = Chronos(hub.getContractAddress("Chronos")); delegatorsInfo = DelegatorsInfo(hub.getContractAddress("DelegatorsInfo")); + v6_delegatorsInfo = V6_DelegatorsInfo(hub.getContractAddress("V6_DelegatorsInfo")); } function name() external pure virtual override returns (string memory) { @@ -142,6 +145,15 @@ contract Profile is INamed, IVersioned, ContractStatus, IInitializable { } } + if (currentEpoch > 1 && currentEpoch > v6_delegatorsInfo.v812ReleaseEpoch()) { + // All operator fees for previous epochs must be calculated and claimed before updating the operator fee + if (!v6_delegatorsInfo.isOperatorFeeClaimedForEpoch(identityId, currentEpoch - 1)) { + revert( + "Cannot update operatorFee if operatorFee has not been calculated and claimed for v6 previous epochs" + ); + } + } + if (newOperatorFee > parametersStorage.maxOperatorFee()) { revert ProfileLib.InvalidOperatorFee(); } diff --git a/contracts/Staking.sol b/contracts/Staking.sol index 78e745c5..dc01de13 100644 --- a/contracts/Staking.sol +++ b/contracts/Staking.sol @@ -10,6 +10,7 @@ import {ProfileStorage} from "./storage/ProfileStorage.sol"; import {ShardingTableStorage} from "./storage/ShardingTableStorage.sol"; import {StakingStorage} from "./storage/StakingStorage.sol"; import {DelegatorsInfo} from "./storage/DelegatorsInfo.sol"; +import {V6_DelegatorsInfo} from "./storage/V6_DelegatorsInfo.sol"; import {ContractStatus} from "./abstract/ContractStatus.sol"; import {IInitializable} from "./interfaces/IInitializable.sol"; import {INamed} from "./interfaces/INamed.sol"; @@ -24,6 +25,8 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {RandomSamplingStorage} from "./storage/RandomSamplingStorage.sol"; import {Chronos} from "./storage/Chronos.sol"; import {EpochStorage} from "./storage/EpochStorage.sol"; +import {V6_RandomSamplingStorage} from "./storage/V6_RandomSamplingStorage.sol"; +import {ClaimV6Helper} from "./ClaimV6Helper.sol"; contract Staking is INamed, IVersioned, ContractStatus, IInitializable { string private constant _NAME = "Staking"; @@ -39,8 +42,11 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable { ProfileStorage public profileStorage; StakingStorage public stakingStorage; DelegatorsInfo public delegatorsInfo; + V6_DelegatorsInfo public v6_delegatorsInfo; IERC20 public tokenContract; RandomSamplingStorage public randomSamplingStorage; + V6_RandomSamplingStorage public v6_randomSamplingStorage; + ClaimV6Helper public claimV6Helper; Chronos public chronos; EpochStorage public epochStorage; @@ -71,10 +77,13 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable { profileStorage = ProfileStorage(hub.getContractAddress("ProfileStorage")); stakingStorage = StakingStorage(hub.getContractAddress("StakingStorage")); delegatorsInfo = DelegatorsInfo(hub.getContractAddress("DelegatorsInfo")); + v6_delegatorsInfo = V6_DelegatorsInfo(hub.getContractAddress("V6_DelegatorsInfo")); tokenContract = IERC20(hub.getContractAddress("Token")); randomSamplingStorage = RandomSamplingStorage(hub.getContractAddress("RandomSamplingStorage")); + v6_randomSamplingStorage = V6_RandomSamplingStorage(hub.getContractAddress("V6_RandomSamplingStorage")); chronos = Chronos(hub.getContractAddress("Chronos")); epochStorage = EpochStorage(hub.getContractAddress("EpochStorageV8")); + claimV6Helper = ClaimV6Helper(hub.getContractAddress("ClaimV6Helper")); } /** @@ -121,6 +130,8 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable { bytes32 delegatorKey = _getDelegatorKey(msg.sender); // settle all pending score changes for the node's delegator _prepareForStakeChange(chronos.getCurrentEpoch(), identityId, delegatorKey); + // V6-specific validation only for legacy nodes (operator fee entry < cutoff) + claimV6Helper.prepareForStakeChangeV6(chronos.getCurrentEpoch(), identityId, delegatorKey); uint96 delegatorStakeBase = stakingStorage.getDelegatorStakeBase(identityId, delegatorKey); @@ -178,8 +189,12 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable { // Prepare for stake change on the source and destination nodes uint256 fromDelegatorEpochScore18 = _prepareForStakeChange(currentEpoch, fromIdentityId, delegatorKey); + // V6-specific validation only for legacy nodes (operator fee entry < cutoff) + claimV6Helper.prepareForStakeChangeV6(currentEpoch, fromIdentityId, delegatorKey); // settle all pending score changes for the node's delegator _prepareForStakeChange(currentEpoch, toIdentityId, delegatorKey); + // V6-specific validation only for legacy nodes (operator fee entry < cutoff) + claimV6Helper.prepareForStakeChangeV6(currentEpoch, toIdentityId, delegatorKey); uint96 fromDelegatorStakeBase = ss.getDelegatorStakeBase(fromIdentityId, delegatorKey); @@ -250,6 +265,8 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable { // settle all pending score changes for the node's delegator uint256 delegatorEpochScore18 = _prepareForStakeChange(currentEpoch, identityId, delegatorKey); + // V6-specific validation only for legacy nodes (operator fee entry < cutoff) + claimV6Helper.prepareForStakeChangeV6(currentEpoch, identityId, delegatorKey); uint96 delegatorStakeBase = ss.getDelegatorStakeBase(identityId, delegatorKey); if (removedStake > delegatorStakeBase) { @@ -333,6 +350,8 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable { // settle all pending score changes for the node's delegator _prepareForStakeChange(chronos.getCurrentEpoch(), identityId, delegatorKey); + // V6-specific validation only for legacy nodes (operator fee entry < cutoff) + claimV6Helper.prepareForStakeChangeV6(chronos.getCurrentEpoch(), identityId, delegatorKey); uint96 nodeStakeBefore = ss.getNodeStake(identityId); uint96 maxStake = parametersStorage.maximumStake(); @@ -400,6 +419,8 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable { bytes32 delegatorKey = _getDelegatorKey(msg.sender); // settle all pending score changes for the node's delegator _prepareForStakeChange(chronos.getCurrentEpoch(), identityId, delegatorKey); + // V6-specific validation only for legacy nodes (operator fee entry < cutoff) + claimV6Helper.prepareForStakeChangeV6(chronos.getCurrentEpoch(), identityId, delegatorKey); ss.setOperatorFeeBalance(identityId, oldOperatorFeeBalance - addedStake); @@ -536,6 +557,9 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable { lastClaimed = v81ReleaseEpoch - 1; } + // Ensure main DelegatorsInfo does not exceed V6 counterpart by more than 1 epoch (legacy nodes only) + claimV6Helper.enforceOnlyOneEpochAdvance(identityId, delegator, lastClaimed); + if (lastClaimed == currentEpoch - 1) { revert("Already claimed all finalised epochs"); } @@ -556,6 +580,9 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable { // settle all pending score changes for the node's delegator uint256 delegatorScore18 = _prepareForStakeChange(epoch, identityId, delegatorKey); + // V6-specific validation only for legacy nodes (operator fee entry < cutoff) + claimV6Helper.prepareForStakeChangeV6(epoch, identityId, delegatorKey); + uint256 nodeScore18 = randomSamplingStorage.getNodeEpochScore(epoch, identityId); uint256 reward; @@ -629,7 +656,6 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable { //Should it increase on roling rewards or on stakeBaseIncrease only? stakingStorage.addDelegatorCumulativeEarnedRewards(identityId, delegatorKey, uint96(reward)); } - /** * @dev Claims rewards for multiple delegators across multiple epochs in batch * Calls claimDelegatorRewards internally for each epoch-delegator combination @@ -639,17 +665,6 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable { * @param epochs Array of epochs to claim for (each must be valid for claiming) * @param delegators Array of delegator addresses (each must be a node delegator) */ - function batchClaimDelegatorRewards( - uint72 identityId, - uint256[] memory epochs, - address[] memory delegators - ) external profileExists(identityId) { - for (uint256 i = 0; i < epochs.length; i++) { - for (uint256 j = 0; j < delegators.length; j++) { - claimDelegatorRewards(identityId, epochs[i], delegators[j]); - } - } - } /** * @dev Internal function to validate that delegator has claimed all required epoch rewards @@ -659,7 +674,13 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable { * @param identityId Node to validate claims for * @param delegator Address of delegator to validate */ - function _validateDelegatorEpochClaims(uint72 identityId, address delegator) internal { + function _validateDelegatorEpochClaims(uint72 identityId, address delegator) public onlyContracts { + _validateDelegatorEpochClaimsv81(identityId, delegator); + // V6-specific validation only for legacy nodes (operator fee entry < cutoff) + claimV6Helper.validateDelegatorEpochClaimsV6(identityId, delegator); + } + + function _validateDelegatorEpochClaimsv81(uint72 identityId, address delegator) internal { bytes32 delegatorKey = _getDelegatorKey(delegator); uint256 currentEpoch = chronos.getCurrentEpoch(); uint256 previousEpoch = currentEpoch - 1; @@ -731,7 +752,7 @@ contract Staking is INamed, IVersioned, ContractStatus, IInitializable { uint256 epoch, uint72 identityId, bytes32 delegatorKey - ) internal returns (uint256 delegatorEpochScore) { + ) public onlyContracts returns (uint256 delegatorEpochScore) { // 1. Current "score-per-stake" uint256 nodeScorePerStake36 = randomSamplingStorage.getNodeEpochScorePerStake(epoch, identityId); diff --git a/contracts/StakingManager.sol b/contracts/StakingManager.sol new file mode 100644 index 00000000..fd1ba578 --- /dev/null +++ b/contracts/StakingManager.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +// ───────────────────────────────────────────────────────────── +// External contracts & interfaces +// ───────────────────────────────────────────────────────────── +import {Staking} from "./Staking.sol"; +import {V6_Claim} from "./V6_Claim.sol"; +import {ClaimV6Helper} from "./ClaimV6Helper.sol"; +import {ProfileStorage} from "./storage/ProfileStorage.sol"; +import {ProfileLib} from "./libraries/ProfileLib.sol"; +import {Chronos} from "./storage/Chronos.sol"; +import {DelegatorsInfo} from "./storage/DelegatorsInfo.sol"; +import {V6_DelegatorsInfo} from "./storage/V6_DelegatorsInfo.sol"; +import {V8_1_1_Rewards_Period_Storage} from "./storage/V8_1_1_Rewards_Period_Storage.sol"; +import {V8_1_1_Rewards_Period} from "./V8_1_1_Rewards_Period.sol"; +import {ContractStatus} from "./abstract/ContractStatus.sol"; +import {INamed} from "./interfaces/INamed.sol"; +import {IVersioned} from "./interfaces/IVersioned.sol"; + +/** + * @title StakingManager + * @notice Thin wrapper that combines reward-claiming functionality spanning + * multiple contract generations (v8 & legacy v6). This contract + * off-loads heavy logic to the underlying `Staking` and `V6_Claim` + * contracts but offers a unified interface for callers. + */ +contract StakingManager is INamed, IVersioned, ContractStatus { + string private constant _NAME = "StakingManager"; + string private constant _VERSION = "1.0.0"; + + Staking public stakingMain; + V6_Claim public v6Claim; + ClaimV6Helper public claimV6Helper; + ProfileStorage public profileStorage; + Chronos public chronos; + DelegatorsInfo public delegatorsInfo; + V6_DelegatorsInfo public v6_delegatorsInfo; + V8_1_1_Rewards_Period_Storage public v8_1_1_rewards_storage; + V8_1_1_Rewards_Period public v8_1_1_rewards_period; + + modifier profileExists(uint72 identityId) { + _checkProfileExists(identityId); + _; + } + + constructor(address hubAddress) ContractStatus(hubAddress) {} + + /** + * @dev Resolves contract addresses from the Hub. Must be called once by Hub + * immediately after deployment. + */ + function initialize() external onlyHub { + stakingMain = Staking(hub.getContractAddress("Staking")); + v6Claim = V6_Claim(hub.getContractAddress("V6_Claim")); + claimV6Helper = ClaimV6Helper(hub.getContractAddress("ClaimV6Helper")); + profileStorage = ProfileStorage(hub.getContractAddress("ProfileStorage")); + chronos = Chronos(hub.getContractAddress("Chronos")); + delegatorsInfo = DelegatorsInfo(hub.getContractAddress("DelegatorsInfo")); + v6_delegatorsInfo = V6_DelegatorsInfo(hub.getContractAddress("V6_DelegatorsInfo")); + v8_1_1_rewards_storage = V8_1_1_Rewards_Period_Storage(hub.getContractAddress("V8_1_1_Rewards_Period_Storage")); + v8_1_1_rewards_period = V8_1_1_Rewards_Period(hub.getContractAddress("V8_1_1_Rewards_Period")); + } + + function _checkProfileExists(uint72 identityId) internal view virtual { + if (!profileStorage.profileExists(identityId)) { + revert ProfileLib.ProfileDoesntExist(identityId); + } + } + + function name() external pure override returns (string memory) { + return _NAME; + } + + function version() external pure override returns (string memory) { + return _VERSION; + } + + function claimDelegatorRewardsCombined( + uint72 identityId, + uint256 epoch, + address delegator + ) public profileExists(identityId) { + stakingMain.claimDelegatorRewards(identityId, epoch, delegator); + // Execute V6-specific claim logic only for nodes created before the cutoff timestamp + if (profileStorage.getOperatorFeeEffectiveDateByIndex(identityId, 0) < claimV6Helper.v6NodeCutoffTs()) { + v6Claim.claimDelegatorRewardsV6(identityId, epoch, delegator); + } + + // TODO: Better to do a migration?? + // V8.1.1 migration rewards – auto-restake when delegator is up-to-date + uint256 currentEpoch = chronos.getCurrentEpoch(); + uint256 previousEpoch = currentEpoch - 1; + + if ( + delegatorsInfo.getLastClaimedEpoch(identityId, delegator) == previousEpoch && + v6_delegatorsInfo.getLastClaimedEpoch(identityId, delegator) == previousEpoch + ) { + (uint96 reward811, bool claimed811) = v8_1_1_rewards_storage.getReward(identityId, delegator); + if (reward811 > 0 && !claimed811) { + v8_1_1_rewards_period.claimV8TuningPeriodRewards(identityId, delegator); + } + } + } + + function batchClaimDelegatorRewardsV81( + uint72 identityId, + uint256[] memory epochs, + address[] memory delegators + ) external profileExists(identityId) { + for (uint256 i = 0; i < epochs.length; i++) { + for (uint256 j = 0; j < delegators.length; j++) { + stakingMain.claimDelegatorRewards(identityId, epochs[i], delegators[j]); + } + } + } + + function batchClaimDelegatorRewardsV6( + uint72 identityId, + uint256[] memory epochs, + address[] memory delegators + ) external profileExists(identityId) { + for (uint256 i = 0; i < epochs.length; i++) { + for (uint256 j = 0; j < delegators.length; j++) { + v6Claim.claimDelegatorRewardsV6(identityId, epochs[i], delegators[j]); + } + } + } + + function batchClaimDelegatorRewardsCombined( + uint72 identityId, + uint256[] memory epochs, + address[] memory delegators + ) external profileExists(identityId) { + for (uint256 i = 0; i < epochs.length; i++) { + for (uint256 j = 0; j < delegators.length; j++) { + claimDelegatorRewardsCombined(identityId, epochs[i], delegators[j]); + } + } + } + + // (lazy helper removed – rewards contracts now resolved during initialize) +} diff --git a/contracts/V6_Claim.sol b/contracts/V6_Claim.sol new file mode 100644 index 00000000..b5ea7f18 --- /dev/null +++ b/contracts/V6_Claim.sol @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.20; + +import {ShardingTable} from "./ShardingTable.sol"; +import {Ask} from "./Ask.sol"; +import {IdentityStorage} from "./storage/IdentityStorage.sol"; +import {ParametersStorage} from "./storage/ParametersStorage.sol"; +import {ProfileStorage} from "./storage/ProfileStorage.sol"; +import {ShardingTableStorage} from "./storage/ShardingTableStorage.sol"; +import {StakingStorage} from "./storage/StakingStorage.sol"; +import {DelegatorsInfo} from "./storage/DelegatorsInfo.sol"; +import {V6_DelegatorsInfo} from "./storage/V6_DelegatorsInfo.sol"; +import {ContractStatus} from "./abstract/ContractStatus.sol"; +import {IInitializable} from "./interfaces/IInitializable.sol"; +import {INamed} from "./interfaces/INamed.sol"; +import {IVersioned} from "./interfaces/IVersioned.sol"; +import {ProfileLib} from "./libraries/ProfileLib.sol"; +import {ShardingTableLib} from "./libraries/ShardingTableLib.sol"; +import {StakingLib} from "./libraries/StakingLib.sol"; +import {TokenLib} from "./libraries/TokenLib.sol"; +import {IdentityLib} from "./libraries/IdentityLib.sol"; +import {Permissions} from "./libraries/Permissions.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {V6_RandomSamplingStorage} from "./storage/V6_RandomSamplingStorage.sol"; +import {Chronos} from "./storage/Chronos.sol"; +import {EpochStorage as EpochStorageV6} from "./storage/EpochStorage.sol"; +import {Staking} from "./Staking.sol"; +import {V8_1_1_Rewards_Period} from "./V8_1_1_Rewards_Period.sol"; +import {V8_1_1_Rewards_Period_Storage} from "./storage/V8_1_1_Rewards_Period_Storage.sol"; +import {ClaimV6Helper} from "./ClaimV6Helper.sol"; + +contract V6_Claim is INamed, IVersioned, ContractStatus, IInitializable { + string private constant _NAME = "V6_Claim"; + string private constant _VERSION = "1.0.0"; + uint256 public constant SCALE18 = 1e18; + uint256 private constant EPOCH_POOL_INDEX = 1; + + Ask public askContract; + ShardingTableStorage public shardingTableStorage; + ShardingTable public shardingTableContract; + IdentityStorage public identityStorage; + ParametersStorage public parametersStorage; + ProfileStorage public profileStorage; + StakingStorage public stakingStorage; + DelegatorsInfo public delegatorsInfo; + V6_DelegatorsInfo public v6_delegatorsInfo; + IERC20 public tokenContract; + V6_RandomSamplingStorage public v6_randomSamplingStorage; + Staking public stakingMain; + ClaimV6Helper public claimV6Helper; + Chronos public chronos; + EpochStorageV6 public epochStorageV6; + V8_1_1_Rewards_Period public v8_1_1_rewards_period; + V8_1_1_Rewards_Period_Storage public v8_1_1_rewards_storage; + + // solhint-disable-next-line no-empty-blocks + constructor(address hubAddress) ContractStatus(hubAddress) {} + + modifier onlyAdmin(uint72 identityId) { + _checkAdmin(identityId); + _; + } + + modifier profileExists(uint72 identityId) { + _checkProfileExists(identityId); + _; + } + + /** + * @dev Initializes the contract by connecting to all required Hub dependencies + * Called once during deployment to set up contract references + * Only the Hub can call this function + */ + function initialize() external onlyHub { + askContract = Ask(hub.getContractAddress("Ask")); + shardingTableStorage = ShardingTableStorage(hub.getContractAddress("ShardingTableStorage")); + shardingTableContract = ShardingTable(hub.getContractAddress("ShardingTable")); + identityStorage = IdentityStorage(hub.getContractAddress("IdentityStorage")); + parametersStorage = ParametersStorage(hub.getContractAddress("ParametersStorage")); + profileStorage = ProfileStorage(hub.getContractAddress("ProfileStorage")); + stakingStorage = StakingStorage(hub.getContractAddress("StakingStorage")); + delegatorsInfo = DelegatorsInfo(hub.getContractAddress("DelegatorsInfo")); + v6_delegatorsInfo = V6_DelegatorsInfo(hub.getContractAddress("V6_DelegatorsInfo")); + tokenContract = IERC20(hub.getContractAddress("Token")); + v6_randomSamplingStorage = V6_RandomSamplingStorage(hub.getContractAddress("V6_RandomSamplingStorage")); + chronos = Chronos(hub.getContractAddress("Chronos")); + epochStorageV6 = EpochStorageV6(hub.getContractAddress("EpochStorageV6")); + stakingMain = Staking(hub.getContractAddress("Staking")); + + claimV6Helper = ClaimV6Helper(hub.getContractAddress("ClaimV6Helper")); + + address rewardsAddr; + try hub.getContractAddress("V8_1_1_Rewards_Period") returns (address addr2) { + rewardsAddr = addr2; + } catch { + rewardsAddr = address(0); + } + if (rewardsAddr != address(0)) { + v8_1_1_rewards_period = V8_1_1_Rewards_Period(rewardsAddr); + } + + address storageAddr; + try hub.getContractAddress("V8_1_1_Rewards_Period_Storage") returns (address addr3) { + storageAddr = addr3; + } catch { + storageAddr = address(0); + } + if (storageAddr != address(0)) { + v8_1_1_rewards_storage = V8_1_1_Rewards_Period_Storage(storageAddr); + } + } + + /** + * @dev Returns the name of this contract + * Used for contract identification and versioning + */ + function name() external pure virtual override returns (string memory) { + return _NAME; + } + + /** + * @dev Returns the version of this contract + * Used for contract identification and versioning + */ + function version() external pure virtual override returns (string memory) { + return _VERSION; + } + + function claimDelegatorRewardsV6( + uint72 identityId, + uint256 epoch, + address delegator + ) public profileExists(identityId) { + // Allow only nodes created before cutoff timestamp + require( + profileStorage.getOperatorFeeEffectiveDateByIndex(identityId, 0) < claimV6Helper.v6NodeCutoffTs(), + "This node was not active during V6" + ); + uint256 currentEpoch = chronos.getCurrentEpoch(); + require(epoch < currentEpoch, "Epoch not finalised"); + + // Cannot claim rewards for a delegator that is not a node delegator + require(delegatorsInfo.isNodeDelegator(identityId, delegator), "Delegator not found"); + + uint256 lastClaimedMain = delegatorsInfo.getLastClaimedEpoch(identityId, delegator); + uint256 lastClaimedV6 = v6_delegatorsInfo.getLastClaimedEpoch(identityId, delegator); + + // Ensure V6 store is exactly one epoch behind the main DelegatorsInfo store + if (lastClaimedV6 == 0) { + uint256 v812ReleaseEpoch = v6_delegatorsInfo.v812ReleaseEpoch(); + v6_delegatorsInfo.setLastClaimedEpoch(identityId, delegator, v812ReleaseEpoch - 1); + lastClaimedV6 = v812ReleaseEpoch - 1; + } + require( + lastClaimedMain == lastClaimedV6 + 1, + "Must claim V8 rewards for this epoch first, before claiming V6 rewards" + ); + + if (lastClaimedV6 == currentEpoch - 1) { + revert("Already claimed all finalised epochs"); + } + + if (epoch <= lastClaimedV6) { + revert("Epoch already claimed"); + } + + if (epoch > lastClaimedV6 + 1) { + revert("Must claim older epochs first"); + } + + bytes32 delegatorKey = _getDelegatorKey(delegator); + require( + !v6_delegatorsInfo.hasDelegatorClaimedEpochRewards(epoch, identityId, delegatorKey), + "Already claimed rewards for this epoch" + ); + + // settle all pending score changes for the node's delegator (V6 logic) + uint256 delegatorScore18 = claimV6Helper.prepareForStakeChangeV6(epoch, identityId, delegatorKey); + stakingMain._prepareForStakeChange(epoch, identityId, delegatorKey); + uint256 nodeScore18 = v6_randomSamplingStorage.getNodeEpochScore(epoch, identityId); + + uint256 reward; + + // If nodeScore18 = 0, rewards are 0 too + if (nodeScore18 > 0) { + // netNodeRewards (rewards for node's delegators) = grossNodeRewards - operator fee + uint256 netNodeRewards; + if (!v6_delegatorsInfo.isOperatorFeeClaimedForEpoch(identityId, epoch)) { + // Operator fee has not been claimed for this epoch, calculate it (V6 sources) + uint256 allNodesScore18 = v6_randomSamplingStorage.getAllNodesEpochScore(epoch); + if (allNodesScore18 > 0) { + uint256 grossNodeRewards = (epochStorageV6.getEpochPool(EPOCH_POOL_INDEX, epoch) * nodeScore18) / + allNodesScore18; + uint96 operatorFeeAmount = uint96( + (grossNodeRewards * profileStorage.getLatestOperatorFeePercentage(identityId)) / + parametersStorage.maxOperatorFee() + ); + netNodeRewards = grossNodeRewards - operatorFeeAmount; + // Mark the operator fee as claimed for this epoch + v6_delegatorsInfo.setIsOperatorFeeClaimedForEpoch(identityId, epoch, true); + // Set node's delegators net rewards for this epoch so we don't have to calculate it again + v6_delegatorsInfo.setNetNodeEpochRewards(identityId, epoch, netNodeRewards); + stakingStorage.increaseOperatorFeeBalance(identityId, operatorFeeAmount); + } + } else { + // Operator fee has been claimed for this epoch already, use the previously calculated node's delegators net rewards for this epoch + netNodeRewards = v6_delegatorsInfo.getNetNodeEpochRewards(identityId, epoch); + } + + reward = (delegatorScore18 * netNodeRewards) / nodeScore18; + } + + // If the operator fee flag has not been set for the epoch (because it had no score), set it now. + // This ensures that Profile.updateOperatorFee is not blocked by rewardless epochs. + if (!v6_delegatorsInfo.isOperatorFeeClaimedForEpoch(identityId, epoch)) { + v6_delegatorsInfo.setIsOperatorFeeClaimedForEpoch(identityId, epoch, true); + } + + // update state even when reward is zero + // Mark the delegator's rewards as claimed for this epoch + v6_delegatorsInfo.setHasDelegatorClaimedEpochRewards(epoch, identityId, delegatorKey, true); + uint256 lastClaimedEpoch = v6_delegatorsInfo.getLastClaimedEpoch(identityId, delegator); + v6_delegatorsInfo.setLastClaimedEpoch(identityId, delegator, epoch); + + // Check if this completes all required claims and reset lastStakeHeldEpoch + uint256 lastStakeHeldEpoch = delegatorsInfo.getLastStakeHeldEpoch(identityId, delegator); + if (lastStakeHeldEpoch > 0 && epoch >= lastStakeHeldEpoch) { + // They've now claimed all rewards they're entitled to, reset the tracker + delegatorsInfo.setLastStakeHeldEpoch(identityId, delegator, 0); + + // Check if they should be removed from delegators list + if (reward == 0 && stakingStorage.getDelegatorStakeBase(identityId, delegatorKey) == 0) { + delegatorsInfo.removeDelegator(identityId, delegator); + } + } + + uint256 rolling = v6_delegatorsInfo.getDelegatorRollingRewards(identityId, delegator); + + if (reward == 0 && rolling == 0) return; + + // if there are still older epochs pending, accumulate; otherwise restake immediately + if ((currentEpoch - 1) - lastClaimedEpoch > 1) { + v6_delegatorsInfo.setDelegatorRollingRewards(identityId, delegator, rolling + reward); + } else { + uint96 total = uint96(reward + rolling); + v6_delegatorsInfo.setDelegatorRollingRewards(identityId, delegator, 0); + stakingStorage.increaseDelegatorStakeBase(identityId, delegatorKey, total); + stakingStorage.increaseNodeStake(identityId, total); + stakingStorage.increaseTotalStake(total); + } + //Should it increase on roling rewards or on stakeBaseIncrease only? + stakingStorage.addDelegatorCumulativeEarnedRewards(identityId, delegatorKey, uint96(reward)); + } + + // prepareForStakeChange logic moved to ClaimV6Helper + + /** + * @dev Internal function to manage delegator registration and status tracking + * Adds delegator to node's delegator list if not already registered + * Marks delegator as having ever delegated to the node (for claim validation) + * Resets lastStakeHeldEpoch when delegator becomes active again + * @param identityId Node to manage delegator status for + * @param delegator Address of the delegator + */ + function _manageDelegatorStatus(uint72 identityId, address delegator) internal { + if (!delegatorsInfo.isNodeDelegator(identityId, delegator)) { + delegatorsInfo.addDelegator(identityId, delegator); + } + // If operator was inactive and is now restaking fees, reset their lastStakeHeldEpoch + uint256 lastStakeHeldEpoch = delegatorsInfo.getLastStakeHeldEpoch(identityId, delegator); + if (lastStakeHeldEpoch > 0) { + delegatorsInfo.setLastStakeHeldEpoch(identityId, delegator, 0); + } + } + + /** + * @dev Internal function to add node to sharding table when stake requirements are met + * Only adds node if it doesn't exist and has minimum required stake + * Validates that sharding table isn't full before adding + * @param identityId Node to potentially add to sharding table + * @param newStake Current stake amount for the node + */ + function _addNodeToShardingTable(uint72 identityId, uint96 newStake) internal { + ShardingTableStorage sts = shardingTableStorage; + ParametersStorage params = parametersStorage; + + if (!sts.nodeExists(identityId) && newStake >= params.minimumStake()) { + if (sts.nodesCount() >= params.shardingTableSizeLimit()) { + revert ShardingTableLib.ShardingTableIsFull(); + } + shardingTableContract.insertNode(identityId); + } + } + + /** + * @dev Internal function to remove node from sharding table when stake falls below minimum + * Only removes node if it exists and stake is below minimum threshold + * @param identityId Node to potentially remove from sharding table + * @param newStake Current stake amount for the node + */ + function _removeNodeFromShardingTable(uint72 identityId, uint96 newStake) internal { + if (shardingTableStorage.nodeExists(identityId) && newStake < parametersStorage.minimumStake()) { + shardingTableContract.removeNode(identityId); + } + } + + /** + * @dev Internal function to validate that caller is an admin of the specified node + * Checks if caller's address has admin key purpose for the identity + * Used by functions that require node admin permissions + * @param identityId Node identity to check admin rights for + */ + function _checkAdmin(uint72 identityId) internal view virtual { + if (!identityStorage.keyHasPurpose(identityId, _getDelegatorKey(msg.sender), IdentityLib.ADMIN_KEY)) { + revert Permissions.OnlyProfileAdminFunction(msg.sender); + } + } + + /** + * @dev Internal function to validate that a node profile exists + * Used by modifiers and functions to ensure operations target valid nodes + * @param identityId Node identity to check existence for + */ + function _checkProfileExists(uint72 identityId) internal view virtual { + if (!profileStorage.profileExists(identityId)) { + revert ProfileLib.ProfileDoesntExist(identityId); + } + } + + /** + * @dev Internal function to handle delegator cleanup when stake reaches zero + * If delegator earned score in current epoch: keeps them for future reward claims + * If no score earned: removes delegator from node immediately + * Prevents loss of rewards while optimizing storage usage + * @param identityId Node to handle delegator removal for + * @param delegator Address of delegator with zero stake + * @param delegatorEpochScore18 Score earned by delegator in current epoch + * @param currentEpoch Current epoch number + */ + function _handleDelegatorRemovalOnZeroStake( + uint72 identityId, + address delegator, + uint256 delegatorEpochScore18, + uint256 currentEpoch + ) internal { + // Don't remove delegator immediately - they might still be eligible for rewards in current epoch + if (delegatorEpochScore18 > 0) { + // Delegator earned score in current epoch (can claim), keep them for future reward claims + delegatorsInfo.setLastStakeHeldEpoch(identityId, delegator, currentEpoch); + } else { + // No score earned in current epoch, safe to remove immediately + delegatorsInfo.removeDelegator(identityId, delegator); + } + } + + /** + * @dev Helper function to get delegator key from address + * @param delegator Address to convert to key + * @return bytes32 hash of the delegator address + */ + function _getDelegatorKey(address delegator) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(delegator)); + } +} diff --git a/contracts/V6_RandomSampling.sol b/contracts/V6_RandomSampling.sol new file mode 100644 index 00000000..369ba60e --- /dev/null +++ b/contracts/V6_RandomSampling.sol @@ -0,0 +1,584 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.20; + +import {INamed} from "./interfaces/INamed.sol"; +import {IVersioned} from "./interfaces/IVersioned.sol"; +import {ContractStatus} from "./abstract/ContractStatus.sol"; +import {IInitializable} from "./interfaces/IInitializable.sol"; +import {RandomSamplingLib} from "./libraries/RandomSamplingLib.sol"; +import {ProfileLib} from "./libraries/ProfileLib.sol"; +import {IdentityStorage} from "./storage/IdentityStorage.sol"; +import {V6_RandomSamplingStorage} from "./storage/V6_RandomSamplingStorage.sol"; +import {KnowledgeCollectionStorage} from "./storage/KnowledgeCollectionStorage.sol"; +import {StakingStorage} from "./storage/StakingStorage.sol"; +import {ProfileStorage} from "./storage/ProfileStorage.sol"; +import {EpochStorage} from "./storage/EpochStorage.sol"; +import {Chronos} from "./storage/Chronos.sol"; +import {AskStorage} from "./storage/AskStorage.sol"; +import {DelegatorsInfo} from "./storage/DelegatorsInfo.sol"; +import {ParametersStorage} from "./storage/ParametersStorage.sol"; +import {ShardingTableStorage} from "./storage/ShardingTableStorage.sol"; +import {ICustodian} from "./interfaces/ICustodian.sol"; +import {HubLib} from "./libraries/HubLib.sol"; +import {ClaimV6Helper} from "./ClaimV6Helper.sol"; + +// cutoff timestamp moved to ClaimV6Helper + +contract V6_RandomSampling is INamed, IVersioned, ContractStatus, IInitializable { + string private constant _NAME = "V6_RandomSampling"; + string private constant _VERSION = "1.0.0"; + uint256 public constant SCALE18 = 1e18; + + IdentityStorage public identityStorage; + V6_RandomSamplingStorage public v6_randomSamplingStorage; + KnowledgeCollectionStorage public knowledgeCollectionStorage; + StakingStorage public stakingStorage; + ProfileStorage public profileStorage; + EpochStorage public epochStorage; + Chronos public chronos; + AskStorage public askStorage; + DelegatorsInfo public delegatorsInfo; + ParametersStorage public parametersStorage; + ShardingTableStorage public shardingTableStorage; + ClaimV6Helper public claimV6Helper; + + error MerkleRootMismatchError(bytes32 computedMerkleRoot, bytes32 expectedMerkleRoot); + + /** + * @dev Constructor initializes the contract with essential parameters for random sampling + * Only called once during deployment + * @param hubAddress Address of the Hub contract for access control + */ + constructor(address hubAddress) ContractStatus(hubAddress) {} + + modifier profileExists(uint72 identityId) { + _checkProfileExists(identityId); + _; + } + + /** + * @dev Modifier to check if a node exists in the sharding table + * Used by functions to ensure operations target valid nodes + * Reverts with NodeDoesntExist error if node is not found + * @param identityId Node identity to check existence for + */ + modifier nodeExistsInShardingTable(uint72 identityId) { + _checkNodeExistsInShardingTable(identityId); + _; + } + + modifier nodeCreatedBeforeV6Cutoff(uint72 identityId) { + require( + profileStorage.getOperatorFeeEffectiveDateByIndex(identityId, 0) < claimV6Helper.v6NodeCutoffTs(), + "Node created after V6 cutoff timestamp" + ); + _; + } + + // @dev Only transactions by HubController owner or one of the owners of the MultiSig Wallet + modifier onlyOwnerOrMultiSigOwner() { + _checkOwnerOrMultiSigOwner(); + _; + } + + /** + * @dev Initializes the contract by connecting to all required Hub dependencies + * Called once during deployment to set up contract references for storage and computation + * Only the Hub can call this function + */ + function initialize() public onlyHub { + identityStorage = IdentityStorage(hub.getContractAddress("IdentityStorage")); + v6_randomSamplingStorage = V6_RandomSamplingStorage(hub.getContractAddress("V6_RandomSamplingStorage")); + knowledgeCollectionStorage = KnowledgeCollectionStorage( + hub.getAssetStorageAddress("KnowledgeCollectionStorage") + ); + stakingStorage = StakingStorage(hub.getContractAddress("StakingStorage")); + profileStorage = ProfileStorage(hub.getContractAddress("ProfileStorage")); + epochStorage = EpochStorage(hub.getContractAddress("EpochStorageV8")); + chronos = Chronos(hub.getContractAddress("Chronos")); + askStorage = AskStorage(hub.getContractAddress("AskStorage")); + delegatorsInfo = DelegatorsInfo(hub.getContractAddress("DelegatorsInfo")); + parametersStorage = ParametersStorage(hub.getContractAddress("ParametersStorage")); + shardingTableStorage = ShardingTableStorage(hub.getContractAddress("ShardingTableStorage")); + claimV6Helper = ClaimV6Helper(hub.getContractAddress("ClaimV6Helper")); + } + + /** + * @dev Returns the name of this contract + * Used for contract identification and versioning + */ + function name() external pure virtual override returns (string memory) { + return _NAME; + } + + /** + * @dev Returns the version of this contract + * Used for contract identification and versioning + */ + function version() external pure virtual override returns (string memory) { + return _VERSION; + } + + /** + * @dev Checks if there is a pending proofing period duration that hasn't taken effect yet + * @return True if there is a pending duration change, false otherwise + */ + function isPendingProofingPeriodDuration() public view returns (bool) { + return chronos.getCurrentEpoch() < v6_randomSamplingStorage.getLatestProofingPeriodDurationEffectiveEpoch(); + } + + /** + * @dev Sets the duration of proofing periods in blocks with a one-epoch delay + * Only contracts registered in the Hub can call this function + * If a pending change exists, replaces it; otherwise adds a new duration + * Changes take effect in the next epoch to ensure smooth transitions + * @param durationInBlocks New proofing period duration in blocks (must be > 0) + */ + function setProofingPeriodDurationInBlocks(uint16 durationInBlocks) external onlyOwnerOrMultiSigOwner { + require(durationInBlocks > 0, "Duration in blocks must be greater than 0"); + + // Calculate the effective epoch (current epoch + delay) + uint256 effectiveEpoch = chronos.getCurrentEpoch() + 1; + + // Check if there's a pending change + if (isPendingProofingPeriodDuration()) { + v6_randomSamplingStorage.replacePendingProofingPeriodDuration(durationInBlocks, effectiveEpoch); + } else { + v6_randomSamplingStorage.addProofingPeriodDuration(durationInBlocks, effectiveEpoch); + } + } + + /** + * @dev Creates a new challenge for the calling node in the current proofing period + * Caller must have a registered profile and cannot have an active unsolved challenge + * Generates a random knowledge collection and chunk to be proven + * Can only create one challenge per proofing period + */ + function createChallenge() + external + profileExists(identityStorage.getIdentityId(msg.sender)) + nodeExistsInShardingTable(identityStorage.getIdentityId(msg.sender)) + nodeCreatedBeforeV6Cutoff(identityStorage.getIdentityId(msg.sender)) + { + uint72 identityId = identityStorage.getIdentityId(msg.sender); + RandomSamplingLib.Challenge memory nodeChallenge = v6_randomSamplingStorage.getNodeChallenge(identityId); + + if (nodeChallenge.activeProofPeriodStartBlock == updateAndGetActiveProofPeriodStartBlock()) { + // Revert if node has already solved the challenge for this period + if (nodeChallenge.solved) { + revert("The challenge for this proof period has already been solved"); + } + + // Revert if a challenge for this node exists but has not been solved yet + if (nodeChallenge.knowledgeCollectionId != 0) { + revert("An unsolved challenge already exists for this node in the current proof period"); + } + } + + // Generate a new challenge + RandomSamplingLib.Challenge memory challenge = _generateChallenge(msg.sender); + + // Store the new challenge in the storage contract + v6_randomSamplingStorage.setNodeChallenge(identityId, challenge); + } + + /** + * @dev Submits proof for an active challenge to earn score used for later reward calculation + * Validates the submitted chunk and merkle proof against the expected Merkle root + * On successful proof: marks challenge as solved, increments valid proofs count, + * calculates and adds node score, and updates epoch scoring data + * @param chunk The data chunk being proven (must match challenge requirements) + * @param merkleProof Array of hashes for Merkle proof verification + */ + function submitProof( + string memory chunk, + bytes32[] calldata merkleProof + ) + external + profileExists(identityStorage.getIdentityId(msg.sender)) + nodeExistsInShardingTable(identityStorage.getIdentityId(msg.sender)) + nodeCreatedBeforeV6Cutoff(identityStorage.getIdentityId(msg.sender)) + { + // Get node identityId + uint72 identityId = identityStorage.getIdentityId(msg.sender); + // Get node challenge + RandomSamplingLib.Challenge memory challenge = v6_randomSamplingStorage.getNodeChallenge(identityId); + + if (challenge.solved) { + revert("This challenge has already been solved"); + } + + uint256 activeProofPeriodStartBlock = updateAndGetActiveProofPeriodStartBlock(); + + // verify that the challengeId matches the current challenge + if (challenge.activeProofPeriodStartBlock != activeProofPeriodStartBlock) { + revert("This challenge is no longer active"); + } + + // Construct the merkle root from chunk and merkleProof + bytes32 computedMerkleRoot = _computeMerkleRootFromProof(chunk, challenge.chunkId, merkleProof); + + // Get the expected merkle root for this challenge + bytes32 expectedMerkleRoot = knowledgeCollectionStorage.getLatestMerkleRoot(challenge.knowledgeCollectionId); + + // Verify the submitted root matches + if (computedMerkleRoot == expectedMerkleRoot) { + // Mark as correct submission and add points to the node + challenge.solved = true; + v6_randomSamplingStorage.setNodeChallenge(identityId, challenge); + + uint256 epoch = chronos.getCurrentEpoch(); + v6_randomSamplingStorage.incrementEpochNodeValidProofsCount(epoch, identityId); + uint256 score18 = calculateNodeScore(identityId); + v6_randomSamplingStorage.addToNodeEpochProofPeriodScore( + epoch, + activeProofPeriodStartBlock, + identityId, + score18 + ); + v6_randomSamplingStorage.addToNodeEpochScore(epoch, identityId, score18); + v6_randomSamplingStorage.addToAllNodesEpochScore(epoch, score18); + + // Calculate and add to nodeEpochScorePerStake + uint96 totalNodeStake = stakingStorage.getNodeStake(identityId); + if (totalNodeStake > 0) { + uint256 nodeScorePerStake36 = (score18 * SCALE18) / totalNodeStake; + v6_randomSamplingStorage.addToNodeEpochScorePerStake(epoch, identityId, nodeScorePerStake36); + } + } else { + revert MerkleRootMismatchError(computedMerkleRoot, expectedMerkleRoot); + } + } + + /** + * @dev Internal function to compute Merkle root from a chunk and its proof + * Reconstructs the Merkle tree root by hashing the chunk with its ID and + * traversing up the tree using the provided proof hashes + * Uses standard Merkle tree construction where smaller hash goes left + * @param chunk The data chunk to verify + * @param chunkId Unique identifier for the chunk position + * @param merkleProof Array of sibling hashes for tree traversal + * @return computedRoot The computed Merkle root hash + */ + function _computeMerkleRootFromProof( + string memory chunk, + uint256 chunkId, + bytes32[] calldata merkleProof + ) internal pure returns (bytes32) { + bytes32 computedHash = keccak256(abi.encodePacked(chunk, chunkId)); + + for (uint256 i = 0; i < merkleProof.length; ) { + if (computedHash < merkleProof[i]) { + computedHash = keccak256(abi.encodePacked(computedHash, merkleProof[i])); + } else { + computedHash = keccak256(abi.encodePacked(merkleProof[i], computedHash)); + } + + unchecked { + i++; + } + } + + return computedHash; + } + + /** + * @dev Internal function to generate a new random challenge for a node + * Uses blockchain properties (block hash, difficulty, timestamp, gas price) for randomness + * Selects a random active knowledge collection and chunk within it + * Creates challenge with current epoch and active proof period information + * @param originalSender The original caller address for randomness seed + * @return challenge The generated challenge struct + */ + function _generateChallenge(address originalSender) internal returns (RandomSamplingLib.Challenge memory) { + uint256 knowledgeCollectionsCount = knowledgeCollectionStorage.getLatestKnowledgeCollectionId(); + if (knowledgeCollectionsCount == 0) { + revert("No knowledge collections exist"); + } + + bytes32 pseudoRandomVariable = keccak256( + abi.encodePacked( + block.difficulty, + blockhash(block.number - ((block.difficulty % 256) + 1)), // +1 to avoid blockhash(block.number) situation + originalSender, + block.timestamp, + tx.gasprice, + uint8(1) // sector = 1 by default + ) + ); + uint256 currentEpoch = chronos.getCurrentEpoch(); + + // Optimized binary search approach for finding active knowledge collection + uint256 knowledgeCollectionId = _findActiveKnowledgeCollection( + pseudoRandomVariable, + 1, + knowledgeCollectionsCount, + currentEpoch + ); + + if (knowledgeCollectionId == 0) { + revert("Failed to find a knowledge collection that is active in the current epoch"); + } + + uint88 kcByteSize = knowledgeCollectionStorage.getByteSize(knowledgeCollectionId); + if (kcByteSize == 0) { + revert("Knowledge collection byte size is 0"); + } + + uint256 chunkId; + uint256 chunkByteSize = v6_randomSamplingStorage.CHUNK_BYTE_SIZE(); + // KC with byteSize < chunkByteSize will always have chunkId = 0 + if (kcByteSize > chunkByteSize) { + chunkId = uint256(pseudoRandomVariable) % (kcByteSize / chunkByteSize); + } + + return + RandomSamplingLib.Challenge( + knowledgeCollectionId, + chunkId, + address(knowledgeCollectionStorage), + currentEpoch, + updateAndGetActiveProofPeriodStartBlock(), + getActiveProofingPeriodDurationInBlocks(), + false + ); + } + + /** + * @dev Internal function to find an active knowledge collection using breadth-first search + * Uses BFS with a queue-based approach to efficiently search for collections that are + * still active (current epoch <= collection's end epoch) + * Splits ranges recursively and uses randomness to select from each range + * Limits iterations to prevent infinite loops and ensures gas efficiency + * @param randomSeed Random seed for picking a collection from current range + * @param start Start of the range (inclusive) - collection ID range to search + * @param end End of the range (inclusive) - collection ID range to search + * @param currentEpoch Current epoch to check collection activity against + * @return knowledgeCollectionId ID of an active knowledge collection, or 0 if none found + */ + function _findActiveKnowledgeCollection( + bytes32 randomSeed, + uint256 start, + uint256 end, + uint256 currentEpoch + ) internal view returns (uint256) { + // Queue using fixed array - [start1, end1, start2, end2, ...] + uint256[100] memory queue; // Can hold 50 ranges max + uint8 queueStart = 0; // Front of queue + uint8 queueEnd = 0; // Back of queue + + // Push initial range + queue[queueEnd++] = start; + queue[queueEnd++] = end; + + bytes32 currentRandom = randomSeed; + uint8 iterations = 0; + + while (queueStart < queueEnd && iterations < 50) { + // Pop range from front of queue (BFS behavior) + uint256 currentStart = queue[queueStart++]; + uint256 currentEnd = queue[queueStart++]; + + // Pick random collection from current range + uint256 randomKcId = currentStart + (uint256(currentRandom) % (currentEnd - currentStart + 1)); + + // Check if this collection is active + if (currentEpoch <= knowledgeCollectionStorage.getEndEpoch(randomKcId)) { + return randomKcId; + } + + // If single element and not active, continue to next range + if (currentStart == currentEnd) { + currentRandom = keccak256(abi.encodePacked(currentRandom)); + unchecked { + iterations++; + } + continue; + } + + // Split range and push both halves to back of queue (BFS order) + uint256 mid = currentStart + (currentEnd - currentStart) / 2; + + if (queueEnd < 96) { + // Leave room for both ranges + // Always push left half first, then right half (consistent BFS) + if (currentStart <= mid) { + queue[queueEnd++] = currentStart; + queue[queueEnd++] = mid; + } + if (mid + 1 <= currentEnd) { + queue[queueEnd++] = mid + 1; + queue[queueEnd++] = currentEnd; + } + } + + currentRandom = keccak256(abi.encodePacked(currentRandom)); + unchecked { + iterations++; + } + } + + return 0; // No active collection found + } + + /** + * @dev Calculates the node score based on stake, ask price, and publishing activity + * Score = nodeStakeFactor + nodeAskFactor + nodePublishingFactor + * + * nodeStakeFactor: 2 * (nodeStake / maxStake)^2 - rewards higher stake + * nodeAskFactor: (nodeStake/maxStake) * ((upperBound - nodeAsk) / (upperBound - lowerBound))^2 - rewards lower ask prices + * nodePublishingFactor: nodeStakeFactor * (nodePublishing / maxNodePublishing) - rewards active publishers + * + * All calculations use 18-decimal precision for accuracy + * @param identityId The node identity to calculate score for + * @return score18 The calculated node score scaled by 18-decimal for precision + */ + function calculateNodeScore(uint72 identityId) public view returns (uint256) { + // 1. Node stake factor calculation + // Formula: nodeStakeFactor = 2 * (nodeStake / maximumStake)^2 + uint256 maximumStake = uint256(parametersStorage.maximumStake()); + uint256 nodeStake = uint256(stakingStorage.getNodeStake(identityId)); + nodeStake = nodeStake > maximumStake ? maximumStake : nodeStake; + uint256 stakeRatio18 = (nodeStake * SCALE18) / maximumStake; + uint256 nodeStakeFactor18 = (2 * stakeRatio18 * stakeRatio18) / SCALE18; + + // 2. Node ask factor calculation + // Formula: nodeStake * ((upperAskBound - nodeAsk) / (upperAskBound - lowerAskBound))^2 / maximumStake + uint256 nodeAsk18 = uint256(profileStorage.getAsk(identityId)) * SCALE18; + (uint256 askLowerBound18, uint256 askUpperBound18) = askStorage.getAskBounds(); + uint256 nodeAskFactor18; + if (askUpperBound18 > askLowerBound18 && nodeAsk18 >= askLowerBound18 && nodeAsk18 <= askUpperBound18) { + uint256 askDiffRatio18 = ((askUpperBound18 - nodeAsk18) * SCALE18) / (askUpperBound18 - askLowerBound18); + nodeAskFactor18 = (stakeRatio18 * (askDiffRatio18 ** 2)) / (SCALE18 ** 2); + } + + // 3. Node publishing factor calculation + // Original: nodeStakeFactor * (nodePublishingFactor / MAX(allNodesPublishingFactors)) + uint256 maxNodePub = uint256(epochStorage.getCurrentEpochNodeMaxProducedKnowledgeValue()); + if (maxNodePub == 0) { + return nodeStakeFactor18 + nodeAskFactor18; + } + uint256 nodePub = uint256(epochStorage.getNodeCurrentEpochProducedKnowledgeValue(identityId)); + uint256 pubRatio18 = (nodePub * SCALE18) / maxNodePub; + uint256 nodePublishingFactor18 = (nodeStakeFactor18 * pubRatio18) / SCALE18; + + return nodeStakeFactor18 + nodeAskFactor18 + nodePublishingFactor18; + } + + /** + * @dev Updates and returns the current active proof period start block + * Automatically advances to the next period if the current one has ended + * @return Current active proof period start block number + */ + function updateAndGetActiveProofPeriodStartBlock() public returns (uint256) { + uint256 activeProofingPeriodDurationInBlocks = getActiveProofingPeriodDurationInBlocks(); + + if (activeProofingPeriodDurationInBlocks == 0) { + revert("Active proofing period duration in blocks should not be 0"); + } + + uint256 activeProofPeriodStartBlock = v6_randomSamplingStorage.getActiveProofPeriodStartBlock(); + + if (block.number > activeProofPeriodStartBlock + activeProofingPeriodDurationInBlocks - 1) { + // Calculate how many complete periods have passed since the last active period started + uint256 blocksSinceLastStart = block.number - activeProofPeriodStartBlock; + uint256 completePeriodsPassed = blocksSinceLastStart / activeProofingPeriodDurationInBlocks; + + uint256 newActiveProofPeriodStartBlock = activeProofPeriodStartBlock + + completePeriodsPassed * + activeProofingPeriodDurationInBlocks; + + v6_randomSamplingStorage.setActiveProofPeriodStartBlock(newActiveProofPeriodStartBlock); + + return newActiveProofPeriodStartBlock; + } + + return activeProofPeriodStartBlock; + } + + /** + * @dev Returns the status of the current active proof period including start block and whether it's still active + * @return ProofPeriodStatus struct containing start block and active status + */ + function getActiveProofPeriodStatus() external view returns (RandomSamplingLib.ProofPeriodStatus memory) { + uint256 activeProofPeriodStartBlock = v6_randomSamplingStorage.getActiveProofPeriodStartBlock(); + return + RandomSamplingLib.ProofPeriodStatus( + activeProofPeriodStartBlock, + block.number < activeProofPeriodStartBlock + getActiveProofingPeriodDurationInBlocks() + ); + } + + /** + * @dev Calculates the start block of a historical proof period based on current period and offset + * Used to determine proof periods from the past for validation purposes + * @param proofPeriodStartBlock Start block of a valid proof period (must be > 0 and aligned to period boundaries) + * @param offset Number of periods to go back (must be > 0) + * @return Start block of the historical proof period + */ + function getHistoricalProofPeriodStartBlock( + uint256 proofPeriodStartBlock, + uint256 offset + ) external view returns (uint256) { + require(proofPeriodStartBlock > 0, "Proof period start block must be greater than 0"); + require( + proofPeriodStartBlock % getActiveProofingPeriodDurationInBlocks() == 0, + "Proof period start block is not valid" + ); + require(offset > 0, "Offset must be greater than 0"); + return proofPeriodStartBlock - offset * getActiveProofingPeriodDurationInBlocks(); + } + + /** + * @dev Returns the currently active proofing period duration in blocks + * Automatically selects the appropriate duration based on current epoch + * @return Duration in blocks of the currently active proofing period + */ + function getActiveProofingPeriodDurationInBlocks() public view returns (uint16) { + return v6_randomSamplingStorage.getEpochProofingPeriodDurationInBlocks(chronos.getCurrentEpoch()); + } + + /** + * @dev Internal function to validate that a node profile exists + * Used by modifiers and functions to ensure operations target valid nodes + * Reverts with ProfileDoesntExist error if profile is not found + * @param identityId Node identity to check existence for + */ + function _checkProfileExists(uint72 identityId) internal view virtual { + if (!profileStorage.profileExists(identityId)) { + revert ProfileLib.ProfileDoesntExist(identityId); + } + } + + /** + * @dev Internal function to validate that a node exists in the sharding table + * Used by modifiers and functions to ensure operations target valid nodes + * Reverts with NodeDoesntExist error if node is not found + * @param identityId Node identity to check existence for + */ + function _checkNodeExistsInShardingTable(uint72 identityId) internal view virtual { + if (!shardingTableStorage.nodeExists(identityId)) { + revert("Node does not exist in sharding table"); + } + } + + function _isMultiSigOwner(address multiSigAddress) internal view returns (bool) { + try ICustodian(multiSigAddress).getOwners() returns (address[] memory multiSigOwners) { + for (uint256 i = 0; i < multiSigOwners.length; i++) { + if (msg.sender == multiSigOwners[i]) { + return true; + } + } // solhint-disable-next-line no-empty-blocks + } catch {} + + return false; + } + + function _checkOwnerOrMultiSigOwner() internal view virtual { + address hubOwner = hub.owner(); + if (msg.sender != hubOwner && !_isMultiSigOwner(hubOwner)) { + revert HubLib.UnauthorizedAccess("Only Hub Owner or Multisig Owner"); + } + } +} diff --git a/contracts/V8_1_1_Rewards_Period.sol b/contracts/V8_1_1_Rewards_Period.sol new file mode 100644 index 00000000..e17cc133 --- /dev/null +++ b/contracts/V8_1_1_Rewards_Period.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {StakingStorage} from "./storage/StakingStorage.sol"; +import {V8_1_1_Rewards_Period_Storage} from "./storage/V8_1_1_Rewards_Period_Storage.sol"; +import {ShardingTableStorage} from "./storage/ShardingTableStorage.sol"; +import {ParametersStorage} from "./storage/ParametersStorage.sol"; +import {Ask} from "./Ask.sol"; +import {ShardingTable} from "./ShardingTable.sol"; +import {DelegatorsInfo} from "./storage/DelegatorsInfo.sol"; +import {ContractStatus} from "./abstract/ContractStatus.sol"; +import {INamed} from "./interfaces/INamed.sol"; +import {IVersioned} from "./interfaces/IVersioned.sol"; +import {RandomSamplingStorage} from "./storage/RandomSamplingStorage.sol"; +import {Chronos} from "./storage/Chronos.sol"; +import {Staking} from "./Staking.sol"; +import {ClaimV6Helper} from "./ClaimV6Helper.sol"; + +/** + * @title DelegatorRewardsMigrator + * @notice Logic contract that allows delegators to restake the migration + * rewards that have been pre-filled in `DelegatorRewardsStorage`. + * The implementation purposefully mimics the important state changes + * performed by `Staking.stake` but **without** requiring token + * allowances and transfers from the delegator because the tokens have + * already been moved to `StakingStorage` during the migration. + * + * Workflow: + * 1. Governance (Hub owner) populates `DelegatorRewardsStorage` with + * `(identityId, delegator, rewardAmount)` entries. + * 2. Delegators call `increaseDelegatorStakeBase` providing the node + * they delegate to. The function will: + * • settle score changes for the current epoch (best-effort) + * • increase delegator stake base + * • increase node stake and total stake + * • insert node into the sharding table if required + * • recalculate active set so that Ask prices stay correct + * • mark the reward as claimed in storage. + * 3. Governance can call `batchClaimAll` to process any unclaimed + * rewards in bulk (e.g., if some delegators never claim). + */ +contract V8_1_1_Rewards_Period is INamed, IVersioned, ContractStatus { + string private constant _NAME = "V8_1_1_Rewards_Period"; + string private constant _VERSION = "1.0.0"; + + V8_1_1_Rewards_Period_Storage public rewardsStorage; + StakingStorage public stakingStorage; + ShardingTableStorage public shardingTableStorage; + ShardingTable public shardingTable; + ParametersStorage public parametersStorage; + Ask public askContract; + DelegatorsInfo public delegatorsInfo; + RandomSamplingStorage public randomSamplingStorage; + Chronos public chronos; + Staking public stakingMain; + ClaimV6Helper public claimV6Helper; + + // solhint-disable-next-line no-empty-blocks + constructor(address hubAddress) ContractStatus(hubAddress) {} + + function initialize() external onlyHub { + rewardsStorage = V8_1_1_Rewards_Period_Storage(hub.getContractAddress("V8_1_1_Rewards_Period_Storage")); + stakingStorage = StakingStorage(hub.getContractAddress("StakingStorage")); + shardingTableStorage = ShardingTableStorage(hub.getContractAddress("ShardingTableStorage")); + shardingTable = ShardingTable(hub.getContractAddress("ShardingTable")); + parametersStorage = ParametersStorage(hub.getContractAddress("ParametersStorage")); + askContract = Ask(hub.getContractAddress("Ask")); + delegatorsInfo = DelegatorsInfo(hub.getContractAddress("DelegatorsInfo")); + randomSamplingStorage = RandomSamplingStorage(hub.getContractAddress("RandomSamplingStorage")); + chronos = Chronos(hub.getContractAddress("Chronos")); + stakingMain = Staking(hub.getContractAddress("Staking")); + claimV6Helper = ClaimV6Helper(hub.getContractAddress("ClaimV6Helper")); + } + + /** + * @notice Claims the pre-calculated reward for the caller and immediately + * restakes it for the given node. + * @param identityId The node identifier the caller is delegating to. + */ + function claimV8TuningPeriodRewards(uint72 identityId, address delegator) external { + (uint96 addedStake, bool claimed) = rewardsStorage.getReward(identityId, delegator); + require(addedStake > 0, "No reward"); + require(!claimed, "Already claimed"); + + // Validate epoch claims for V8 and V6 rewards + stakingMain._validateDelegatorEpochClaims(identityId, delegator); + + bytes32 delegatorKey = keccak256(abi.encodePacked(delegator)); + uint256 currentEpoch = chronos.getCurrentEpoch(); + + // Settle pending score changes in both v8 and v6 systems + stakingMain._prepareForStakeChange(currentEpoch, identityId, delegatorKey); + claimV6Helper.prepareForStakeChangeV6(currentEpoch, identityId, delegatorKey); + + uint96 currentDelegatorStakeBase = stakingStorage.getDelegatorStakeBase(identityId, delegatorKey); + uint96 newDelegatorStakeBase = currentDelegatorStakeBase + addedStake; + + uint96 totalNodeStakeBefore = stakingStorage.getNodeStake(identityId); + uint96 totalNodeStakeAfter = totalNodeStakeBefore + addedStake; + + // Mark reward as processed - avoid reentrancy + rewardsStorage.markClaimed(identityId, delegator); + + // Update staking balances + stakingStorage.setDelegatorStakeBase(identityId, delegatorKey, newDelegatorStakeBase); + stakingStorage.setNodeStake(identityId, totalNodeStakeAfter); + stakingStorage.increaseTotalStake(addedStake); + + _addNodeToShardingTable(identityId, totalNodeStakeAfter); + askContract.recalculateActiveSet(); + + _manageDelegatorStatus(identityId, delegator); + } + + function _manageDelegatorStatus(uint72 identityId, address delegator) internal { + if (!delegatorsInfo.isNodeDelegator(identityId, delegator)) { + delegatorsInfo.addDelegator(identityId, delegator); + } + uint256 lastStakeHeldEpoch = delegatorsInfo.getLastStakeHeldEpoch(identityId, delegator); + if (lastStakeHeldEpoch > 0) { + delegatorsInfo.setLastStakeHeldEpoch(identityId, delegator, 0); + } + } + + function _addNodeToShardingTable(uint72 identityId, uint96 totalNodeStakeAfter) internal { + if (!shardingTableStorage.nodeExists(identityId)) { + if (totalNodeStakeAfter >= parametersStorage.minimumStake()) { + shardingTable.insertNode(identityId); + } + } + } + + // --------------------------------------------------------------------------------------------- + // INamed & IVersioned + // --------------------------------------------------------------------------------------------- + + function name() external pure override returns (string memory) { + return _NAME; + } + + function version() external pure override returns (string memory) { + return _VERSION; + } +} diff --git a/contracts/storage/V6_DelegatorsInfo.sol b/contracts/storage/V6_DelegatorsInfo.sol new file mode 100644 index 00000000..238debfd --- /dev/null +++ b/contracts/storage/V6_DelegatorsInfo.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.20; + +import {StakingStorage} from "./StakingStorage.sol"; +import {IInitializable} from "../interfaces/IInitializable.sol"; +import {INamed} from "../interfaces/INamed.sol"; +import {IVersioned} from "../interfaces/IVersioned.sol"; +import {ContractStatus} from "../abstract/ContractStatus.sol"; + +contract V6_DelegatorsInfo is INamed, IVersioned, ContractStatus, IInitializable { + string private constant _NAME = "V6_DelegatorsInfo"; + string private constant _VERSION = "1.0.0"; + + // IdentityId => Delegator => Index + mapping(uint72 => mapping(address => uint256)) public nodeDelegatorIndex; + // IdentityId => Delegator => IsDelegator + mapping(uint72 => mapping(address => bool)) public isDelegatorMap; + // IdentityId => Delegator => LastClaimedEpoch + mapping(uint72 => mapping(address => uint256)) public lastClaimedEpoch; + // IdentityId => Delegator => RollingRewards + mapping(uint72 => mapping(address => uint256)) public delegatorRollingRewards; + // IdentityId => Epoch => OperatorFeeClaimed + mapping(uint72 => mapping(uint256 => bool)) public isOperatorFeeClaimedForEpoch; + // IdentityId => Epoch => Amount + mapping(uint72 => mapping(uint256 => uint256)) public netNodeEpochRewards; + // IdentityId => Epoch + mapping(uint72 => uint256) public lastClaimedDelegatorsRewardsEpoch; + // epoch => identityId => delegatorKey => rewards claimed status + mapping(uint256 => mapping(uint72 => mapping(bytes32 => bool))) public hasDelegatorClaimedEpochRewards; + // IdentityId => Delegator => HasEverDelegatedToNode + mapping(uint72 => mapping(address => bool)) public hasEverDelegatedToNode; + // IdentityId => Delegator => LastStakeHeldEpoch (the last epoch when delegator held stake, 0 if fully claimed) + mapping(uint72 => mapping(address => uint256)) public lastStakeHeldEpoch; + + // Epoch when v8.1.2 release occurred + uint256 public v812ReleaseEpoch; + + event DelegatorAdded(uint72 indexed identityId, address indexed delegator); + event DelegatorRemoved(uint72 indexed identityId, address indexed delegator); + event DelegatorLastClaimedEpochUpdated( + uint72 indexed identityId, + address indexed delegator, + uint256 newLastClaimedEpoch + ); + event DelegatorRollingRewardsUpdated( + uint72 indexed identityId, + address indexed delegator, + uint256 amount, + uint256 newTotalRollingRewards + ); + event IsOperatorFeeClaimedForEpochUpdated(uint72 indexed identityId, uint256 indexed epoch, bool isClaimed); + event NetNodeEpochRewardsSet(uint72 indexed identityId, uint256 indexed epoch, uint256 amount); + event HasEverDelegatedToNodeUpdated( + uint72 indexed identityId, + address indexed delegator, + bool hasEverDelegatedToNode + ); + event LastStakeHeldEpochUpdated(uint72 indexed identityId, address indexed delegator, uint256 epoch); + event HasDelegatorClaimedEpochRewardsUpdated( + uint256 indexed epoch, + uint72 indexed identityId, + bytes32 indexed delegatorKey, + bool claimed + ); + event V812ReleaseEpochUpdated(uint256 indexed epoch); + + constructor(address hubAddress) ContractStatus(hubAddress) { + // TODO: change this to the actual epoch + v812ReleaseEpoch = 2; + } + + function initialize() external onlyHub {} + + function name() external pure virtual override returns (string memory) { + return _NAME; + } + + function version() external pure virtual override returns (string memory) { + return _VERSION; + } + + // TODO: change for only hub owner or multisig owner + function setV812ReleaseEpoch(uint256 _epoch) external onlyHub { + v812ReleaseEpoch = _epoch; + emit V812ReleaseEpochUpdated(_epoch); + } + + function setLastClaimedEpoch(uint72 identityId, address delegator, uint256 epoch) external onlyContracts { + lastClaimedEpoch[identityId][delegator] = epoch; + emit DelegatorLastClaimedEpochUpdated(identityId, delegator, epoch); + } + + function getLastClaimedEpoch(uint72 identityId, address delegator) external view returns (uint256) { + return lastClaimedEpoch[identityId][delegator]; + } + + function setDelegatorRollingRewards(uint72 identityId, address delegator, uint256 amount) external onlyContracts { + delegatorRollingRewards[identityId][delegator] = amount; + emit DelegatorRollingRewardsUpdated(identityId, delegator, amount, amount); + } + + function addDelegatorRollingRewards(uint72 identityId, address delegator, uint256 amount) external onlyContracts { + delegatorRollingRewards[identityId][delegator] += amount; + emit DelegatorRollingRewardsUpdated( + identityId, + delegator, + amount, + delegatorRollingRewards[identityId][delegator] + ); + } + + function getDelegatorRollingRewards(uint72 identityId, address delegator) external view returns (uint256) { + return delegatorRollingRewards[identityId][delegator]; + } + + function setIsOperatorFeeClaimedForEpoch(uint72 identityId, uint256 epoch, bool isClaimed) external onlyContracts { + isOperatorFeeClaimedForEpoch[identityId][epoch] = isClaimed; + emit IsOperatorFeeClaimedForEpochUpdated(identityId, epoch, isClaimed); + } + + function setNetNodeEpochRewards(uint72 identityId, uint256 epoch, uint256 amount) external onlyContracts { + netNodeEpochRewards[identityId][epoch] = amount; + emit NetNodeEpochRewardsSet(identityId, epoch, amount); + } + + function getNetNodeEpochRewards(uint72 identityId, uint256 epoch) external view returns (uint256) { + return netNodeEpochRewards[identityId][epoch]; + } + + function setHasDelegatorClaimedEpochRewards( + uint256 epoch, + uint72 identityId, + bytes32 delegatorKey, + bool claimed + ) external onlyContracts { + hasDelegatorClaimedEpochRewards[epoch][identityId][delegatorKey] = claimed; + emit HasDelegatorClaimedEpochRewardsUpdated(epoch, identityId, delegatorKey, claimed); + } + + function setHasEverDelegatedToNode( + uint72 identityId, + address delegator, + bool _hasEverDelegatedToNode + ) external onlyContracts { + hasEverDelegatedToNode[identityId][delegator] = _hasEverDelegatedToNode; + emit HasEverDelegatedToNodeUpdated(identityId, delegator, _hasEverDelegatedToNode); + } + + function setLastStakeHeldEpoch(uint72 identityId, address delegator, uint256 epoch) external onlyContracts { + lastStakeHeldEpoch[identityId][delegator] = epoch; + emit LastStakeHeldEpochUpdated(identityId, delegator, epoch); + } + + function getLastStakeHeldEpoch(uint72 identityId, address delegator) external view returns (uint256) { + return lastStakeHeldEpoch[identityId][delegator]; + } +} diff --git a/contracts/storage/V6_RandomSamplingStorage.sol b/contracts/storage/V6_RandomSamplingStorage.sol new file mode 100644 index 00000000..cf8f20f7 --- /dev/null +++ b/contracts/storage/V6_RandomSamplingStorage.sol @@ -0,0 +1,638 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.20; + +import {INamed} from "../interfaces/INamed.sol"; +import {IVersioned} from "../interfaces/IVersioned.sol"; +import {IInitializable} from "../interfaces/IInitializable.sol"; +import {RandomSamplingLib} from "../libraries/RandomSamplingLib.sol"; +import {Chronos} from "../storage/Chronos.sol"; +import {ContractStatus} from "../abstract/ContractStatus.sol"; +import {ICustodian} from "../interfaces/ICustodian.sol"; +import {HubLib} from "../libraries/HubLib.sol"; + +contract V6_RandomSamplingStorage is INamed, IVersioned, IInitializable, ContractStatus { + string private constant _NAME = "V6_RandomSamplingStorage"; + string private constant _VERSION = "1.0.0"; + uint8 public constant CHUNK_BYTE_SIZE = 32; + Chronos public chronos; + + uint256 public w1; + uint256 public w2; + + RandomSamplingLib.ProofingPeriodDuration[] public proofingPeriodDurations; + + uint256 private activeProofPeriodStartBlock; + // identityId => Challenge - used in proof to verify the challenge is within proofing period + mapping(uint72 => RandomSamplingLib.Challenge) public nodesChallenges; + // epoch => identityId => successful proofs count + mapping(uint256 => mapping(uint72 => uint256)) public epochNodeValidProofsCount; + // identityId => epoch => proofPeriodStartBlock => score + mapping(uint72 => mapping(uint256 => mapping(uint256 => uint256))) public nodeEpochProofPeriodScore; + // identityId => epoch => score + mapping(uint72 => mapping(uint256 => uint256)) public nodeEpochScore; + // epoch => score + mapping(uint256 => uint256) public allNodesEpochScore; + // epoch => identityId => delegatorKey => score + mapping(uint256 => mapping(uint72 => mapping(bytes32 => uint256))) public epochNodeDelegatorScore; + // epoch => identityId => scorePerStake + mapping(uint256 => mapping(uint72 => uint256)) public nodeEpochScorePerStake; + // epoch => identityId => delegatorKey => last settled nodeEpochScorePerStake + mapping(uint256 => mapping(uint72 => mapping(bytes32 => uint256))) + public delegatorLastSettledNodeEpochScorePerStake; + + event W1Set(uint256 oldW1, uint256 newW1); + event W2Set(uint256 oldW2, uint256 newW2); + event ProofingPeriodDurationAdded(uint16 durationInBlocks, uint256 indexed effectiveEpoch); + event PendingProofingPeriodDurationReplaced( + uint16 oldDurationInBlocks, + uint16 newDurationInBlocks, + uint256 indexed effectiveEpoch + ); + event NodeEpochScoreAdded(uint256 indexed epoch, uint72 indexed identityId, uint256 scoreAdded, uint256 totalScore); + event AllNodesEpochScoreAdded(uint256 indexed epoch, uint256 scoreAdded, uint256 totalScore); + event NodeEpochProofPeriodScoreAdded( + uint256 indexed epoch, + uint256 indexed proofPeriodStartBlock, + uint72 indexed identityId, + uint256 scoreAdded, + uint256 totalScore + ); + event NodeEpochProofPeriodScoreSet( + uint256 indexed epoch, + uint256 indexed proofPeriodStartBlock, + uint72 indexed identityId, + uint256 newScore + ); + event NodeEpochScorePerStakeAdded( + uint256 indexed epoch, + uint72 indexed identityId, + uint256 scorePerStakeToAdd, + uint256 totalNodeEpochScorePerStake + ); + event NodeEpochScorePerStakeSet(uint256 indexed epoch, uint72 indexed identityId, uint256 newScorePerStake); + event EpochNodeDelegatorScoreAdded( + uint256 indexed epoch, + uint72 indexed identityId, + bytes32 indexed delegatorKey, + uint256 scoreAdded, + uint256 totalScore + ); + event DelegatorLastSettledNodeEpochScorePerStakeSet( + uint256 indexed epoch, + uint72 indexed identityId, + bytes32 indexed delegatorKey, + uint256 newDelegatorLastSettledNodeEpochScorePerStake + ); + event NodeChallengeSet(uint72 indexed identityId, RandomSamplingLib.Challenge challenge); + event ActiveProofPeriodStartBlockSet(uint256 indexed activeProofPeriodStartBlock); + event EpochNodeValidProofsCountIncremented(uint256 indexed epoch, uint72 indexed identityId, uint256 newCount); + event EpochNodeValidProofsCountSet(uint256 indexed epoch, uint72 indexed identityId, uint256 newCount); + event NodeEpochScoreSet(uint256 indexed epoch, uint72 indexed identityId, uint256 newScore); + event AllNodesEpochScoreSet(uint256 indexed epoch, uint256 newScore); + event EpochNodeDelegatorScoreSet( + uint256 indexed epoch, + uint72 indexed identityId, + bytes32 indexed delegatorKey, + uint256 newScore + ); + + /** + * @dev Initializes the RandomSamplingStorage contract with initial parameters + * Sets up proofing period duration, block time, and weight parameters for random sampling + * @param hubAddress Address of the Hub contract for access control and contract dependencies + * @param _proofingPeriodDurationInBlocks Initial duration of proofing periods in blocks + * @param _w1 First weight parameter used in rewards calculations + * @param _w2 Second weight parameter used in rewards calculations + */ + constructor( + address hubAddress, + uint16 _proofingPeriodDurationInBlocks, + uint256 _w1, + uint256 _w2 + ) ContractStatus(hubAddress) { + require(_proofingPeriodDurationInBlocks > 0, "Proofing period duration in blocks must be greater than 0"); + + Chronos c = Chronos(hub.getContractAddress("Chronos")); + + proofingPeriodDurations.push( + RandomSamplingLib.ProofingPeriodDuration({ + durationInBlocks: _proofingPeriodDurationInBlocks, + effectiveEpoch: c.getCurrentEpoch() + }) + ); + w1 = _w1; + w2 = _w2; + + emit ProofingPeriodDurationAdded(_proofingPeriodDurationInBlocks, c.getCurrentEpoch()); + emit W1Set(0, _w1); + emit W2Set(0, _w2); + } + + // @dev Only transactions by HubController owner or one of the owners of the MultiSig Wallet + modifier onlyOwnerOrMultiSigOwner() { + _checkOwnerOrMultiSigOwner(); + _; + } + + /** + * @dev Initializes the contract by setting up the Chronos reference from the hub + * Called once after deployment to complete contract setup + */ + function initialize() external onlyHub { + chronos = Chronos(hub.getContractAddress("Chronos")); + } + + /** + * @dev Returns the name of this contract for identification purposes + * @return Contract name as a string + */ + function name() external pure virtual override returns (string memory) { + return _NAME; + } + + /** + * @dev Returns the version of this contract for compatibility tracking + * @return Contract version as a string + */ + function version() external pure virtual override returns (string memory) { + return _VERSION; + } + + /** + * @dev Updates the w1 parameter used in rewards calculations + * Can only be called by the hub owner or multisig owners + * @param _w1 New w1 parameter value + */ + function setW1(uint256 _w1) external onlyOwnerOrMultiSigOwner { + uint256 oldW1 = w1; + w1 = _w1; + emit W1Set(oldW1, w1); + } + + /** + * @dev Returns the current w1 parameter value + * @return Current w1 parameter used in rewards calculations + */ + function getW1() external view returns (uint256) { + return w1; + } + + /** + * @dev Updates the w2 parameter used in rewards calculations + * Can only be called by the hub owner or multisig owners + * @param _w2 New w2 parameter value + */ + function setW2(uint256 _w2) external onlyOwnerOrMultiSigOwner { + uint256 oldW2 = w2; + w2 = _w2; + emit W2Set(oldW2, w2); + } + + /** + * @dev Returns the current w2 parameter value + * @return Current w2 parameter used in rewards calculations + */ + function getW2() external view returns (uint256) { + return w2; + } + + /** + * @dev Returns the current active proof period start block + * @return Current active proof period start block number + */ + function getActiveProofPeriodStartBlock() external view returns (uint256) { + return activeProofPeriodStartBlock; + } + + /** + * @dev Sets the active proof period start block + * Can only be called by contracts registered in the Hub + * @param newActiveProofPeriodStartBlock New active proof period start block + */ + function setActiveProofPeriodStartBlock(uint256 newActiveProofPeriodStartBlock) external onlyContracts { + activeProofPeriodStartBlock = newActiveProofPeriodStartBlock; + emit ActiveProofPeriodStartBlockSet(newActiveProofPeriodStartBlock); + } + + /** + * @dev Replaces a pending proofing period duration with new values before it becomes active + * Can only be called by contracts registered in the Hub + * @param durationInBlocks New duration in blocks for the proofing period + * @param effectiveEpoch Epoch when the new duration will take effect + */ + function replacePendingProofingPeriodDuration( + uint16 durationInBlocks, + uint256 effectiveEpoch + ) external onlyContracts { + uint16 oldDurationInBlocks = proofingPeriodDurations[proofingPeriodDurations.length - 1].durationInBlocks; + proofingPeriodDurations[proofingPeriodDurations.length - 1] = RandomSamplingLib.ProofingPeriodDuration({ + durationInBlocks: durationInBlocks, + effectiveEpoch: effectiveEpoch + }); + + emit PendingProofingPeriodDurationReplaced(oldDurationInBlocks, durationInBlocks, effectiveEpoch); + } + + /** + * @dev Adds a new proofing period duration to take effect at a future epoch + * Can only be called by contracts registered in the Hub + * @param durationInBlocks Duration in blocks for the new proofing period + * @param effectiveEpoch Epoch when the new duration will take effect + */ + function addProofingPeriodDuration(uint16 durationInBlocks, uint256 effectiveEpoch) external onlyContracts { + proofingPeriodDurations.push( + RandomSamplingLib.ProofingPeriodDuration({ + durationInBlocks: durationInBlocks, + effectiveEpoch: effectiveEpoch + }) + ); + + emit ProofingPeriodDurationAdded(durationInBlocks, effectiveEpoch); + } + + /** + * @dev Returns the proofing period duration for a specific epoch + * @param epoch The epoch to get the duration for + * @return Duration in blocks for the specified epoch + */ + function getEpochProofingPeriodDurationInBlocks(uint256 epoch) external view returns (uint16) { + // Find the most recent duration that was effective before or at the specified epoch + for (uint256 i = proofingPeriodDurations.length; i > 0; ) { + if (epoch >= proofingPeriodDurations[i - 1].effectiveEpoch) { + return proofingPeriodDurations[i - 1].durationInBlocks; + } + + unchecked { + i--; + } + } + + // If no applicable duration found, revert + revert("No applicable duration found"); + } + + /** + * @dev Returns the length of the proofing period durations array + * @return Length of the proofing period durations array + */ + function getProofingPeriodDurationsLength() external view returns (uint256) { + return proofingPeriodDurations.length; + } + + /** + * @dev Returns the effective epoch of the latest proofing period duration + * @return Effective epoch of the latest duration + */ + function getLatestProofingPeriodDurationEffectiveEpoch() external view returns (uint256) { + return proofingPeriodDurations[proofingPeriodDurations.length - 1].effectiveEpoch; + } + + /** + * @dev Returns the duration in blocks of the latest proofing period duration + * @return Duration in blocks of the latest proofing period duration + */ + function getLatestProofingPeriodDurationInBlocks() external view returns (uint16) { + return proofingPeriodDurations[proofingPeriodDurations.length - 1].durationInBlocks; + } + + /** + * @dev Returns the proofing period duration struct for a specific index + * @param index The index to get the duration for + * @return Proofing period duration struct for the specified index + */ + function getProofingPeriodDurationFromIndex( + uint256 index + ) external view returns (RandomSamplingLib.ProofingPeriodDuration memory) { + return proofingPeriodDurations[index]; + } + + /** + * @dev Returns the current challenge assigned to a specific node + * Challenges are used to verify proofs during random sampling + * @param identityId The node identity ID to get the challenge for + * @return Challenge struct containing all challenge details + */ + function getNodeChallenge(uint72 identityId) external view returns (RandomSamplingLib.Challenge memory) { + return nodesChallenges[identityId]; + } + + /** + * @dev Sets a new challenge for a specific node + * Can only be called by contracts registered in the Hub + * @param identityId The node identity ID to set the challenge for + * @param challenge The challenge struct containing all challenge details + */ + function setNodeChallenge( + uint72 identityId, + RandomSamplingLib.Challenge calldata challenge + ) external onlyContracts { + nodesChallenges[identityId] = challenge; + emit NodeChallengeSet(identityId, challenge); + } + + /** + * @dev Returns the score earned by a node during a specific epoch and proof period + * @param identityId The node identity ID to get the score for + * @param epoch The epoch to get the score for + * @param proofPeriodStartBlock The start block of the proof period + * @return Score earned by the node in the specified epoch and proof period, scaled by 10^18 + */ + function getNodeEpochProofPeriodScore( + uint72 identityId, + uint256 epoch, + uint256 proofPeriodStartBlock + ) external view returns (uint256) { + return nodeEpochProofPeriodScore[identityId][epoch][proofPeriodStartBlock]; + } + + /** + * @dev Increments the count of valid proofs submitted by a node in an epoch + * Can only be called by contracts registered in the Hub + * @param epoch The epoch to increment the count for + * @param identityId The node identity ID to increment the count for + */ + function incrementEpochNodeValidProofsCount(uint256 epoch, uint72 identityId) external onlyContracts { + epochNodeValidProofsCount[epoch][identityId] += 1; + emit EpochNodeValidProofsCountIncremented(epoch, identityId, epochNodeValidProofsCount[epoch][identityId]); + } + + function setEpochNodeValidProofsCount(uint256 epoch, uint72 identityId, uint256 count) external onlyContracts { + epochNodeValidProofsCount[epoch][identityId] = count; + emit EpochNodeValidProofsCountSet(epoch, identityId, count); + } + + /** + * @dev Returns the number of valid proofs submitted by a node in a specific epoch + * @param epoch The epoch to get the count for + * @param identityId The node identity ID to get the count for + * @return Number of valid proofs submitted by the node in the specified epoch + */ + function getEpochNodeValidProofsCount(uint256 epoch, uint72 identityId) external view returns (uint256) { + return epochNodeValidProofsCount[epoch][identityId]; + } + + /** + * @dev Adds to the total score earned by a node in a specific epoch + * Can only be called by contracts registered in the Hub + * @param epoch The epoch to add the score to + * @param identityId The node identity ID to add the score for + * @param score The score amount to add, scaled by 10^18 + */ + function addToNodeEpochScore(uint256 epoch, uint72 identityId, uint256 score) external onlyContracts { + nodeEpochScore[identityId][epoch] += score; + emit NodeEpochScoreAdded(epoch, identityId, score, nodeEpochScore[identityId][epoch]); + } + + /** + * @dev Sets a node's score for a specific epoch + * Can only be called by contracts registered in the Hub + * @param epoch The epoch to set the score for + * @param identityId The node identity ID to set the score for + * @param score The score amount to set, scaled by 10^18 + */ + function setNodeEpochScore(uint256 epoch, uint72 identityId, uint256 score) external onlyContracts { + nodeEpochScore[identityId][epoch] = score; + emit NodeEpochScoreSet(epoch, identityId, score); + } + + /** + * @dev Returns the total score earned by a node in a specific epoch + * @param epoch The epoch to get the score for + * @param identityId The node identity ID to get the score for + * @return Total score earned by the node in the specified epoch, scaled by 10^18 + */ + function getNodeEpochScore(uint256 epoch, uint72 identityId) external view returns (uint256) { + return nodeEpochScore[identityId][epoch]; + } + + /** + * @dev Adds to the total score of all nodes in a specific epoch + * Can only be called by contracts registered in the Hub + * @param epoch The epoch to add the score to + * @param score The score amount to add to the total, scaled by 10^18 + */ + function addToAllNodesEpochScore(uint256 epoch, uint256 score) external onlyContracts { + allNodesEpochScore[epoch] += score; + emit AllNodesEpochScoreAdded(epoch, score, allNodesEpochScore[epoch]); + } + + /** + * @dev Sets the total score of all nodes in a specific epoch + * Can only be called by contracts registered in the Hub + * @param epoch The epoch to set the score for + * @param score The score amount to set, scaled by 10^18 + */ + function setAllNodesEpochScore(uint256 epoch, uint256 score) external onlyContracts { + allNodesEpochScore[epoch] = score; + emit AllNodesEpochScoreSet(epoch, score); + } + + /** + * @dev Returns the total score of all nodes in a specific epoch + * @param epoch The epoch to get the total score for + * @return Total score of all nodes in the specified epoch, scaled by 10^18 + */ + function getAllNodesEpochScore(uint256 epoch) external view returns (uint256) { + return allNodesEpochScore[epoch]; + } + + /** + * @dev Adds to a node's score for a specific epoch and proof period + * Can only be called by contracts registered in the Hub + * @param epoch The epoch to add the score to + * @param proofPeriodStartBlock The start block of the proof period + * @param identityId The node identity ID to add the score for + * @param score The score amount to add, scaled by 10^18 + */ + function addToNodeEpochProofPeriodScore( + uint256 epoch, + uint256 proofPeriodStartBlock, + uint72 identityId, + uint256 score + ) external onlyContracts { + nodeEpochProofPeriodScore[identityId][epoch][proofPeriodStartBlock] += score; + emit NodeEpochProofPeriodScoreAdded( + epoch, + proofPeriodStartBlock, + identityId, + score, + nodeEpochProofPeriodScore[identityId][epoch][proofPeriodStartBlock] + ); + } + + /** + * @dev Sets a node's score for a specific epoch and proof period + * Can only be called by contracts registered in the Hub + * @param epoch The epoch to set the score for + * @param proofPeriodStartBlock The start block of the proof period + * @param identityId The node identity ID to set the score for + * @param score The score amount to set, scaled by 10^18 + */ + function setNodeEpochProofPeriodScore( + uint256 epoch, + uint256 proofPeriodStartBlock, + uint72 identityId, + uint256 score + ) external onlyContracts { + nodeEpochProofPeriodScore[identityId][epoch][proofPeriodStartBlock] = score; + emit NodeEpochProofPeriodScoreSet(epoch, proofPeriodStartBlock, identityId, score); + } + + /** + * @dev Returns the score earned by a specific node's delegator in an epoch + * Used for calculating delegator rewards + * @param epoch The epoch to get the score for + * @param identityId The node identity ID the delegator is delegating to + * @param delegatorKey The unique key identifying the delegator + * @return Score earned by the delegator for the specified node in the epoch, scaled by 10^18 + */ + function getEpochNodeDelegatorScore( + uint256 epoch, + uint72 identityId, + bytes32 delegatorKey + ) external view returns (uint256) { + return epochNodeDelegatorScore[epoch][identityId][delegatorKey]; + } + + /** + * @dev Adds to the score earned by a node's delegator in an epoch + * Can only be called by contracts registered in the Hub + * @param epoch The epoch to add the score to + * @param identityId The node identity ID the delegator is delegating to + * @param delegatorKey The unique key identifying the delegator + * @param score The score amount to add, scaled by 10^18 + */ + function addToEpochNodeDelegatorScore( + uint256 epoch, + uint72 identityId, + bytes32 delegatorKey, + uint256 score + ) external onlyContracts { + epochNodeDelegatorScore[epoch][identityId][delegatorKey] += score; + emit EpochNodeDelegatorScoreAdded( + epoch, + identityId, + delegatorKey, + score, + epochNodeDelegatorScore[epoch][identityId][delegatorKey] + ); + } + + function setEpochNodeDelegatorScore( + uint256 epoch, + uint72 identityId, + bytes32 delegatorKey, + uint256 score + ) external onlyContracts { + epochNodeDelegatorScore[epoch][identityId][delegatorKey] = score; + emit EpochNodeDelegatorScoreSet(epoch, identityId, delegatorKey, score); + } + + /** + * @dev Returns the score per stake ratio for a node in a specific epoch + * Used for calculating proportional rewards based on staked amount + * @param epoch The epoch to get the score per stake for + * @param identityId The node identity ID to get the score per stake for + * @return Score per stake ratio for the node in the specified epoch, scaled by 10^36 + */ + function getNodeEpochScorePerStake(uint256 epoch, uint72 identityId) external view returns (uint256) { + return nodeEpochScorePerStake[epoch][identityId]; + } + + /** + * @dev Adds to the score per stake ratio for a node in a specific epoch + * Can only be called by contracts registered in the Hub + * @param epoch The epoch to add the score per stake to + * @param identityId The node identity ID to add the score per stake for + * @param scorePerStakeToAdd The score per stake amount to add, scaled by 10^36 + */ + function addToNodeEpochScorePerStake( + uint256 epoch, + uint72 identityId, + uint256 scorePerStakeToAdd + ) external onlyContracts { + nodeEpochScorePerStake[epoch][identityId] += scorePerStakeToAdd; + emit NodeEpochScorePerStakeAdded( + epoch, + identityId, + scorePerStakeToAdd, + nodeEpochScorePerStake[epoch][identityId] + ); + } + + /** + * @dev Sets the score per stake ratio for a node in a specific epoch + * Can only be called by contracts registered in the Hub + * @param epoch The epoch to set the score per stake for + * @param identityId The node identity ID to set the score per stake for + * @param scorePerStake The score per stake amount to set, scaled by 10^36 + */ + function setNodeEpochScorePerStake(uint256 epoch, uint72 identityId, uint256 scorePerStake) external onlyContracts { + nodeEpochScorePerStake[epoch][identityId] = scorePerStake; + emit NodeEpochScorePerStakeSet(epoch, identityId, scorePerStake); + } + + /** + * @dev Returns the last settled score per stake value for a delegator + * Used to track reward settlement state for delegators + * @param epoch The epoch to get the last settled score per stake for + * @param identityId The node identity ID the delegator is delegating to + * @param delegatorKey The unique key identifying the delegator + * @return Last settled score per stake value for the delegator, scaled by 10^36 + */ + function getDelegatorLastSettledNodeEpochScorePerStake( + uint256 epoch, + uint72 identityId, + bytes32 delegatorKey + ) external view returns (uint256) { + return delegatorLastSettledNodeEpochScorePerStake[epoch][identityId][delegatorKey]; + } + + /** + * @dev Updates the last settled score per stake value for a delegator + * Can only be called by contracts registered in the Hub + * @param epoch The epoch to update the last settled score per stake for + * @param identityId The node identity ID the delegator is delegating to + * @param delegatorKey The unique key identifying the delegator + * @param newNodeEpochScorePerStake The new score per stake value to set as last settled, scaled by 10^36 + */ + function setDelegatorLastSettledNodeEpochScorePerStake( + uint256 epoch, + uint72 identityId, + bytes32 delegatorKey, + uint256 newNodeEpochScorePerStake + ) external onlyContracts { + delegatorLastSettledNodeEpochScorePerStake[epoch][identityId][delegatorKey] = newNodeEpochScorePerStake; + emit DelegatorLastSettledNodeEpochScorePerStakeSet(epoch, identityId, delegatorKey, newNodeEpochScorePerStake); + } + + /** + * @dev Internal function to check if an address is an owner of the multisig wallet + * Used for access control in administrative functions + * @param multiSigAddress Address of the multisig wallet to check ownership of + * @return True if the caller is an owner of the multisig, false otherwise + */ + function _isMultiSigOwner(address multiSigAddress) internal view returns (bool) { + try ICustodian(multiSigAddress).getOwners() returns (address[] memory multiSigOwners) { + for (uint256 i = 0; i < multiSigOwners.length; i++) { + if (msg.sender == multiSigOwners[i]) { + return true; + } + } // solhint-disable-next-line no-empty-blocks + } catch {} + + return false; + } + + /** + * @dev Internal function to verify that the caller is either the hub owner or a multisig owner + * Used by the onlyOwnerOrMultiSigOwner modifier for access control + */ + function _checkOwnerOrMultiSigOwner() internal view virtual { + address hubOwner = hub.owner(); + if (msg.sender != hubOwner && !_isMultiSigOwner(hubOwner)) { + revert HubLib.UnauthorizedAccess("Only Hub Owner or Multisig Owner"); + } + } +} diff --git a/contracts/storage/V8_1_1_Rewards_Period_Storage.sol b/contracts/storage/V8_1_1_Rewards_Period_Storage.sol new file mode 100644 index 00000000..aa4dd111 --- /dev/null +++ b/contracts/storage/V8_1_1_Rewards_Period_Storage.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {INamed} from "../interfaces/INamed.sol"; +import {IVersioned} from "../interfaces/IVersioned.sol"; +import {IInitializable} from "../interfaces/IInitializable.sol"; +import {ContractStatus} from "../abstract/ContractStatus.sol"; + +/** + * @title DelegatorRewardsMigrationStorage + * @notice Same functionality as original DelegatorRewardsStorage; file renamed to reflect migration purpose. + */ +contract V8_1_1_Rewards_Period_Storage is INamed, IVersioned, ContractStatus, IInitializable { + string private constant _NAME = "V8_1_1_Rewards_Period_Storage"; + string private constant _VERSION = "1.0.0"; + + struct RewardInfo { + uint96 amount; + bool claimed; + } + + mapping(uint72 => mapping(address => RewardInfo)) private _rewards; + + constructor(address hubAddress) ContractStatus(hubAddress) {} + + function initialize() external onlyHub {} + + // ---------------- GETTERS ---------------- + function name() external pure override returns (string memory) { + return _NAME; + } + function version() external pure override returns (string memory) { + return _VERSION; + } + + function getReward(uint72 identityId, address delegator) external view returns (uint96, bool) { + RewardInfo memory info = _rewards[identityId][delegator]; + return (info.amount, info.claimed); + } + + function hasReward(uint72 identityId, address delegator) external view returns (bool) { + RewardInfo memory info = _rewards[identityId][delegator]; + return info.amount > 0 && !info.claimed; + } + + // -------------- SETTER ------------------- + /** + * @dev Sets or overwrites reward for a single delegator on a given node. + * Callable only by Hub / Hub owner. + */ + function setDelegatorReward(uint72 identityId, address delegator, uint96 amount) external onlyHub { + require(amount > 0, "Zero amount"); + _rewards[identityId][delegator] = RewardInfo(amount, false); + } + + function setDelegatorsRewards( + uint72 identityId, + address[] calldata delegators, + uint96[] calldata amounts + ) external onlyHub { + require(delegators.length == amounts.length, "Length mismatch"); + for (uint256 i; i < delegators.length; i++) { + address d = delegators[i]; + uint96 a = amounts[i]; + require(a > 0, "Zero amount"); + _rewards[identityId][d] = RewardInfo(a, false); + } + } + + /** + * @notice Marks reward as claimed **and** zeros the amount so that + * `hasReward` will return false afterwards. + */ + function markClaimed(uint72 identityId, address delegator) external onlyContracts { + RewardInfo storage info = _rewards[identityId][delegator]; + require(info.amount > 0, "Reward not found"); + require(!info.claimed, "Already claimed"); + + info.claimed = true; + info.amount = 0; + } +} diff --git a/data/gnosis_withpout_operator_fee.csv b/data/gnosis_withpout_operator_fee.csv new file mode 100644 index 00000000..6d00f8ad --- /dev/null +++ b/data/gnosis_withpout_operator_fee.csv @@ -0,0 +1,904 @@ +"transaction_hash","block_number","delegator_key" +"0xdd281435f9ef204a2015affc71a6226bc0090ab6bde2eb8ed6db97a1490aef3d","37747468","0xcd89b238d943911a63d3aeb5839e73b71e9ee3db7fcafcb40a5a4522326781d5" +"0xdd79cda961d1f57d8f686eeadcd11e1af9e2fa6502412025040e37575c7281ac","37748332","0x0c2ca65a8e695025110953e5dd6ff914b3b763d865f068acbdbbe4439bb5b8ce" +"0x77cb16f2b9b7d37a42edec29f16e2014081bdcbff698eb3f64d89ae30968331d","37748346","0x0c2ca65a8e695025110953e5dd6ff914b3b763d865f068acbdbbe4439bb5b8ce" +"0x3f2b200e9a323954d2312374d69ef8ec5c07cef4230406bf74a1081bd454df02","37752208","0xdc9fc83bf405e26e5be71a112b1665ed697d0b1e79daacec502b35ff723ae99d" +"0xbb7b6ad22f6d43a42c76a59026e54cf3892a346d593a2f9b4beaacd352a23700","37752302","0xaaf8e9e78611b410a9fe2715c58eb7a8f5cfa7053d8840fb71d3bafe45d01118" +"0x139649c484e009ac74a67ca792f51d0b9f094bac50fdcbb02a2839e32aae6216","37752311","0xaaf8e9e78611b410a9fe2715c58eb7a8f5cfa7053d8840fb71d3bafe45d01118" +"0x8479e055ecbf566b69d61df33cf37e623a108d76513cd538dff90e61aaf1ea65","37752314","0xdc9fc83bf405e26e5be71a112b1665ed697d0b1e79daacec502b35ff723ae99d" +"0xd80df0eeefbafff6229153c1d8a6b1140e748fb360fcf729791061e260aa0a1f","37752322","0xdc9fc83bf405e26e5be71a112b1665ed697d0b1e79daacec502b35ff723ae99d" +"0x86296c35cc6921db108cb9cb8371d0f75313c758404b97e88e7c6bf448214bac","37753954","0x787e2a9770dd0826ed59f7c20a17cfb71303cbe3280a342d4b25dde1c78eae4c" +"0xcfee335b3ed3099f6dbec37bdffb13879389ededa2338c78f0d739c7e2444e3a","37753968","0x787e2a9770dd0826ed59f7c20a17cfb71303cbe3280a342d4b25dde1c78eae4c" +"0x29fd12f6a02c8c58a80603b1b7175804604b7b8bb79ffbf8b3b4c1d272dd970a","37754233","0x4546b21588db1d59a6bf973e17635b3b5feed942a5fc26a0ec371027fd27e8a7" +"0x326865c60d2f590acae10f3a1d61724cf177bb25bc8039024677c4f0885fa8e4","37754236",NULL +"0xe596a5e712cb49072abee7623b3f0951572ae2541492fdbb9ceee9a2da0601b6","37754262",NULL +"0xfa33aa613ffd901e41f33bfcf89c4081462b07c1ef20bd25fae9430bfc93432f","37754277",NULL +"0x4d8afc0bc4827c2cc36471b40ddb637270932730299ede92f4dedb3ad968f43a","37754330",NULL +"0xd0c3aac0aef520e0e76115b19cdfc83e6e6393b143017ed57d2809d768c943ba","37754347","0x4546b21588db1d59a6bf973e17635b3b5feed942a5fc26a0ec371027fd27e8a7" +"0x7d741e17f0a7dd712fd8c8a69ef6b666e4a49e2eaf5f06a4398c3cab12bf2c32","37754369",NULL +"0xd8f7f263ea65c4912e15b8161b5edd7743626b0b9a8ecaf7730361afbbf8d49e","37755399","0x8c346f6a404234229ce6098f494806e65c3585640cdd3a898e3fb7c2980d7a8b" +"0x6964237c3dd566c421e4f9f347ea20436608838d93e642867d84dabbd9615976","37755515","0xd2b24c5513d3bc0a5a4045548453153396317f655f25d908f678936ea3953a64" +"0x0600f0d5a3a9a67b1473bf84b9217e6f3ddbfcb650aaf64582671442733e336a","37756162",NULL +"0x3bc1fd46ae7102759f6006971fe2d279858dbc70b7d3bf3be673ef7477e7d97f","37756191",NULL +"0xf3c6771a17afd3232b2b3c826a724daedca80384110ec714f717a9d089bddeb9","37756203",NULL +"0x806e6d0416d1706e27cb65ffd004fb7b50e2d5fb72afb908417c1253abcb6e86","37756210",NULL +"0x2281f0bdf1c6213058ab2c1e47e054fe1112017f1872e6e525cc42275e458540","37756233",NULL +"0x6c2bde7547dfe9b60adaeefea0d624c259d338ae96afe9d8438dcd38c1ff74de","37756246",NULL +"0x7f422d30a35fea8754e3682877f2b69c6dcae7fd4622b903d1de3a780a3ae12c","37756254",NULL +"0xe03f488f0de1851b8fb80cfccfcdaf25793a86b3ac18e25735a2f74f148da231","37756261",NULL +"0x76e1a0cace911dbe5f82dcdaa609d06618cda82292d2a0a033d79ecc13979b1b","37756270","0x2b9594b0ed3c41745504430d39616ef8da7609b6dff35038df14e9405dad0454" +"0x33b8297675bf20bb05b532a2617879e909909e8f0f2097ed509432984b8658d8","37756493","0x123710d6c151b51425b4b092d5dc904b03d73fec442a77519c7fe7a7e3e5fe1d" +"0xca479a6864680eae7b4aaadfbb986dbcf2d798be48c6085a3d8531f42ad997a4","37756504","0x344ef1d75b0e50693e683f815bdaa5b4a0370329c3f85fc83b13dbcef313ab04" +"0x07485b0ef5ea524a45b6db21c5615d60341f6bf7025794c05b7b8e9d220bc7f3","37756625","0xa0563417062f453626aa6218c18370b666f0ee67ac1bce48e1aab4fe667b82ac" +"0x2e89f4f11438ae434932f53b8e2ffd2228cebc20b2d05af48e716039ac8b5b1f","37756635","0xa0563417062f453626aa6218c18370b666f0ee67ac1bce48e1aab4fe667b82ac" +"0x3a0e2bcc85b092a51f52ce73525eec992a982f23523117f1e462e026dc745da3","37756645","0xa0563417062f453626aa6218c18370b666f0ee67ac1bce48e1aab4fe667b82ac" +"0xf99fb152352a92ae3771d93ac71375252694302527063627fe3a272eae10149c","37756663","0xa0563417062f453626aa6218c18370b666f0ee67ac1bce48e1aab4fe667b82ac" +"0x3c339c3b3da15afa5b120a3e29a9d35089bf0feda9f40dd8bd8605b7b346cae4","37759731","0x18a303bbb55d9f47a8b36eb291be335c3a03f555fa2780950085c3dae35d8d43" +"0xc85f23d3c9869ca44f49a1cb2af76da32dc7723cdd592ef6dbcd1019dc3cb139","37760350","0x9259ae4fd9e4e8e52a9f5cf91536b7406a906763681e89db854397058c9b39a4" +"0x2f6b9bc25208a648eb290ca50d3a5f13f09542e123ff7f7d4c266fd9d0b7864d","37760432","0x9259ae4fd9e4e8e52a9f5cf91536b7406a906763681e89db854397058c9b39a4" +"0x5b82253efdefd65d556b7ce523dc9aed649906b409a71e8cc70e22dba18f75f9","37760673","0x123710d6c151b51425b4b092d5dc904b03d73fec442a77519c7fe7a7e3e5fe1d" +"0x1173a59466133a3b5de90a156362587aa825b8f913ba5c7ca4bc887db7a2fe16","37760814","0x0932e8f66e7170ca5c586cf9471eca9dd7662125b1b9a69d7a1b24a35712e0ea" +"0x29ee31b891baebfb805622a9e76d283c9efe509b036615d5c8196504a8c6f370","37760826","0x0932e8f66e7170ca5c586cf9471eca9dd7662125b1b9a69d7a1b24a35712e0ea" +"0x9a1e4a34cca29ad0a8f62f56882ef3bfb8504464167d579b73f75904a192d0a2","37760844","0x0932e8f66e7170ca5c586cf9471eca9dd7662125b1b9a69d7a1b24a35712e0ea" +"0x8cea4f6fba71c59f05f075ed093f5c6c094040c30543f35a4cf64a6d3b044955","37761858","0xad35f4506a34b432aa1f0c8c280d86f8a3720369311bfd4a23c1165a0c0cf5ff" +"0x96da73757107d05f7cdc29667f83838701ab60b7fef86010f8008738fac61761","37762026","0x0cff6bd5ddebe612e11371aa04a3fb847077cf2e8bd68db803549d44d0d1151b" +"0xda34d7e64d664b23465fba60998a18e6029089bae256d4a6465d37107b4a83a5","37764699","0xc25556f0d53e7f7fa5b64a98455007d915d191d74b353b777382630639480993" +"0xd06990aacc7069511d8f45ea8ab78fd190806b0fbc2f527d44f43dad269fa82c","37764709","0xc25556f0d53e7f7fa5b64a98455007d915d191d74b353b777382630639480993" +"0x1b2f7ac9d6312dc9f6933b72e43cb9f3cfdde9f297ca67d2950058beeb849dd4","37764720","0xc25556f0d53e7f7fa5b64a98455007d915d191d74b353b777382630639480993" +"0x857f8a2917ca44a1c573b43e229254c46b6e54c6c6a4627698ecd74a56b609c5","37764726","0xc25556f0d53e7f7fa5b64a98455007d915d191d74b353b777382630639480993" +"0x13dd48291f7704647935ece8fe342c8b33e75db951b2351babb7129fb2e2e618","37764738","0xc25556f0d53e7f7fa5b64a98455007d915d191d74b353b777382630639480993" +"0xb0657fdcff2ab173705634fcab90844a794c658eade003181c684cec62c8fb87","37767029","0x4247f4d60521fec626eb3927db918c70128a42954658116845bd5ba67a7e7cfc" +"0x5a0f7b70589abc3dbba05e1058373ec607c6a462ae198a11cfdd3902c4c4476a","37767044","0x4247f4d60521fec626eb3927db918c70128a42954658116845bd5ba67a7e7cfc" +"0xf0c0b5e491941e52de050e5f80551e96e207ed08af16d4c204e482837f8b6057","37767245","0x4247f4d60521fec626eb3927db918c70128a42954658116845bd5ba67a7e7cfc" +"0x6bd40d35026df34251279766bc2e0556b879b57a11c97c607f0bb867aa25335c","37767395",NULL +"0xf12c45aba3d47790cc820b37475f5b8a22dd464fd8139f4ccb72f0a9880d884f","37767401",NULL +"0x5c5768acde793352d7bec3a1e8f0c9711d312ced98fe3dd5ce5caa873b52b852","37769879","0xad886aed7861d0419cdda43d67b51fba07a2e2aa95236a1326a9a33f1f9813fd" +"0xd8aa0c9ac18002d6d3d5783a65113cc3f91e05906510cd9662f2234c5ecdd8f8","37769893","0xad886aed7861d0419cdda43d67b51fba07a2e2aa95236a1326a9a33f1f9813fd" +"0x68b2eab1dfc52f6614eebe2eaa3b8ce7bcf227ce6c7e05cf403663878ba65554","37769909","0xad886aed7861d0419cdda43d67b51fba07a2e2aa95236a1326a9a33f1f9813fd" +"0x33dcbf2601f0373d39c263445b608e6b6faf9211dad5db087904015f12f44417","37769936","0xad886aed7861d0419cdda43d67b51fba07a2e2aa95236a1326a9a33f1f9813fd" +"0x4b82bee93b852b045ec6e1bc500631583a2ce7311babbacebb75cddf7641c2ab","37769943","0xad886aed7861d0419cdda43d67b51fba07a2e2aa95236a1326a9a33f1f9813fd" +"0xe81b52573e2dd61a79603d23e42e5c150b0d11bfcc2440645ad1bcc814be0a08","37770698","0xbd461b5b7dd325593ec7f57744e7165058cf79edcee07e0efa15df0a9db5aa31" +"0x27585824261e3de0a08c7caa3c83fdae402650b92ea9a7051f1e01310f59a9d8","37772312","0xd0b4da4df15a8b0e0b1901f05af18272ed5fd379d6b3a09fd45a135746f1c56a" +"0x45a870a783177a25de823e52482f18fb0ac8729c9e8cc0f7e348d5db01cc5a2c","37772323","0xd0b4da4df15a8b0e0b1901f05af18272ed5fd379d6b3a09fd45a135746f1c56a" +"0x1d45c2a4b25fc482488febbde459eaea7f1a22e990cd3a4c2af2dc2f5a9d0583","37773115","0xc42b975de5c811b91d840d5a90d822d8aba00510fd472a573a4ad4fe0e3bf4e2" +"0x97dc076b3baa7d9aaa02422feb53d1b4dc3a8faf3197d327f630c033ab66870a","37776114","0x50b7d144319e661072f486b8c56ef6e75cba77e9c6ec1672dc177221486ac6df" +"0x0836b9492948e2eea9fe0fd025d73ae9d073a9e087c335dc328db22cc1fbb37c","37776120","0x50b7d144319e661072f486b8c56ef6e75cba77e9c6ec1672dc177221486ac6df" +"0x344d6a3129e80b3c94b9a33fae789d39343b1d231c9470136eebcf7f3e5e9d7f","37776772","0xa97bf7843dbdba2b944bc8129b8489560c150d5b47bf33276a192451d7fbeffe" +"0x75295ee5d5563009858e6c94eb0cfceda2038e116a8b164a9d2e2b745907736f","37776779","0xa97bf7843dbdba2b944bc8129b8489560c150d5b47bf33276a192451d7fbeffe" +"0x2db29f781fd1cc5e5e03e99ab72e558e8153eb6c5ca0ae9a56d7cc344a7ab22b","37776791","0xa97bf7843dbdba2b944bc8129b8489560c150d5b47bf33276a192451d7fbeffe" +"0x0eab3e932223cd50d369d548d3ec955b925f2431206334499bcb2e8ada44189e","37776797",NULL +"0x4bc0587ecc75889106bf85d9f0ec558d976d83c93d286cfd18d3ca7700f60a8d","37776811","0xcbb4b0cdc47df4a5fe3664c488654cb3203ba96fb082a72c58a8c9e3f4f041df" +"0x67b55ca4be8cec295cd1223f62cd74003ff494c2d5b42580a251815648b5c46f","37776828",NULL +"0x1a43b50cdff30ab6b8e2b6ea3ce7c7b7bcf77965b6797b164a78faffdf43c486","37776848",NULL +"0xdfbf49a16a82bd5d2175a3e1a806631a87e80929b9524c50bb93679abfddb251","37776854",NULL +"0xa8252ec53d935dfea36114e5f42934f54da96d3851c6e21e7b321207469bac41","37776860",NULL +"0x27d698882d76994a366b8546b8c4a9f2af7109e78a96855fdf58217bcab01b53","37776862",NULL +"0xca7fb91b7af1cb960ebcf56a83b3a95aac3c4c0feb51bbcb0e72948c03066f33","37776867",NULL +"0xa687e9558e578876dc84c98c972bf047006857518b617c6ea8564c7d8fba57c0","37776868",NULL +"0xa01cf6ecbea59081b11560a716e115372105fb7c2ad5ebf84308904921075212","37776880",NULL +"0x7d28c25e47c1798be1fa8e09ef625c42692024d1c41175a62d6ba6e9e8c79376","37776887",NULL +"0x5ff52489a1ac9eec96b79a4be8df869ecfbf007a9ed0987f0c69930939e2a0e1","37776893",NULL +"0xcdc1a724c8ba2c6d449d5a6404da10b91751815eae282cfcb2f4edecdbd6473c","37776900",NULL +"0xd2f1ddbef8bf804429173fe0d438372009bbb33c206f9271b1e4712b9c7efea0","37776906",NULL +"0xe9a63e658527d3a210817175bf2568bb41d08875a4221bdae2e4802f41eec2f1","37778016","0xbdcde58947b58a1ec854d7e80144bb2c535b2d3db2456afbd882abf6a21cac6f" +"0x0a857df3a6c2e589b6e14726f2ff6974d2fc98efc8371196d8d0c01d84184c58","37779369","0x01abae51086635bb607999a85af9f9d1ab0b920ac0da53e62a2ce2d8e1916238" +"0xe27d7109736ebd9c31033d76e71c5c2c8b39dcdbcf2c41373e642f0f38b8bada","37783761","0x5006b214d9d5343aa5e97f06a8eb21d7ab64a83d2b28cd43e737371fa2c49f93" +"0x5f18ef912793d0963476999a23248e818c91512ba0573f50a4db4667c7365fc6","37783785","0x5006b214d9d5343aa5e97f06a8eb21d7ab64a83d2b28cd43e737371fa2c49f93" +"0xc05a8c04b880e8d81912f56d69183f38d394d0726df91f808bdd84d4de4c69e1","37783798","0x5006b214d9d5343aa5e97f06a8eb21d7ab64a83d2b28cd43e737371fa2c49f93" +"0x2cb68f594552068cf7738c985020a5f01f99f7620763df29a45917729b74b259","37789174","0xbf84fa7c1842648651c4ae75d5f0b2ce97e0b326033f83281db5f5e737d95bd2" +"0xd67430526769a5620ffa0005a68c6d6a156e2c553b83e0db02a9c7ead29c5567","37789752","0xd0b4da4df15a8b0e0b1901f05af18272ed5fd379d6b3a09fd45a135746f1c56a" +"0x60b28b71fb210d8c79cf7fbb7ab7f0fb24523a86d59c78bc0115b4bf52de49dc","37789772","0xd0b4da4df15a8b0e0b1901f05af18272ed5fd379d6b3a09fd45a135746f1c56a" +"0x61869cdd13e5b6b1c090ecc1cb477c01c408574bfd688d7f894f3595430c00a6","37790690","0x7ffe77b342929e6daa53730a540846ca9b85ad36551011b968785f80aed86eab" +"0x8b3e1c581f3ed2b16713b69ce5924ca65389e15114f97ff4425c136d7b58acc3","37792838","0xff22cddee05cdec8e013eb5d130f3b092dfb9198fc0dbbaf02f813d0f3d74664" +"0x30199d265d3ff75902bedec38f6439ee32fdbf6b6c86a14356fac01e45021a44","37793131","0x5ba6814ea85ed59df7b70a8febc3fb7ff96793de3f6cb518a74d9c59cec6b2a2" +"0x2d625ff90048a4742e020280d499b6522bb092470d02ce23ea69e1ab2032afed","37793174","0xdf40e5f862554f61bf947930c895a4e27c0a8a0942d983efa0454d7acff04f34" +"0x754620b34f8a234a696897ed01b4646ec31d267dedd665d89f8faa1c450dcf95","37793186","0xdf40e5f862554f61bf947930c895a4e27c0a8a0942d983efa0454d7acff04f34" +"0x489580e30708a27886680a96b2feff56ef4c112dc9fe71f0202426ead0c80f42","37793196","0xdf40e5f862554f61bf947930c895a4e27c0a8a0942d983efa0454d7acff04f34" +"0x957c87aa122d0e5e064716a6abfbc9bef4760a760432e5790a7bedc433f0b37f","37793202","0xdf40e5f862554f61bf947930c895a4e27c0a8a0942d983efa0454d7acff04f34" +"0xc9e062cd6a8835a911ef591ad22cc4325bfaaa329085d6250972b195323b1cfa","37795072","0x567da71af7a478d8204aeca7619230e8e059768cc9b75c52abf636f8259bd572" +"0x2e450fcc954a796132d360baf58f3767817aac40faf7503f19426e30c317c6d3","37795096","0x567da71af7a478d8204aeca7619230e8e059768cc9b75c52abf636f8259bd572" +"0xb1760b5cfda28c734d4a29912fb2d194e47b93950e69a1a74c9bf4a906258361","37797455","0x7e41288f90849ff9cb3c06b7760f66ff4f199aaddc1f677af476388418e6fb72" +"0x0df0614050b0871c4944d1c60b56e80a5f69fab357055991ee4bb93a53152ac4","37797494","0x7e41288f90849ff9cb3c06b7760f66ff4f199aaddc1f677af476388418e6fb72" +"0xd566fa457704a7b641a317cb51a6b33f3509dcdeec8f514b093c9fb5163b4989","37798346","0x7e41288f90849ff9cb3c06b7760f66ff4f199aaddc1f677af476388418e6fb72" +"0xb6aaa59e254ef5c14b4749413ab9adddbe46ba35ed3ff37c8841a42882842c3c","37801698",NULL +"0x2a5423721fb955bea082450564fa9bcac97a11f895e280a2b0bfcb0d68497cc0","37803037",NULL +"0xa872027377f246d78b6908a6c5b6dc1b5b8fa6e2aef200c6ddf39c77f917a769","37803503","0x7035601b19e5797ca0a893b3e12aa2495aaf8c4e727b83f0aa5d2ffeea749f31" +"0x23ff325afca15b5bdaea37bb4140b02bafc7556843d5b511e85afa9da142a9fb","37803509",NULL +"0x5d9c197fe2318ee65087d164676a9fe872c3e5a5596cf4fe35e37b07ecb87a2a","37803517","0x7035601b19e5797ca0a893b3e12aa2495aaf8c4e727b83f0aa5d2ffeea749f31" +"0x5ab149d2bf93cb7e0ed63e6b584d4c848bf6d2e9f8805df39108cbfa481ae299","37803525","0x7035601b19e5797ca0a893b3e12aa2495aaf8c4e727b83f0aa5d2ffeea749f31" +"0xdb7af0048a0434837e9c93035e4de494509e704967b32ed41883a89ba8deeab7","37803685",NULL +"0x20eb1b591b6818613e2b9ca9941c958dfc836c786e2f7ac3a069ba401abcc562","37803693",NULL +"0x6d31f20b98de9817d772c0b3ba32063973675d0d035d6528484ad79d755b4b06","37804630","0xe0a76001b92063d4969531f49a27f8ef6e90594c4d75766379fb7c53b8d31f8c" +"0xfb02b6e19e2ca3384d5c0a832613a4f0065402f128104a92434c46cf80ff495f","37804875","0x0ccaea8b79535620903b94fb798e2e834a2f3f739344c9f347dd1680231b17cc" +"0xbceff0356eded931771918cfbaad54077499fe3323482d5f7c7bd11375020772","37804884","0x0ccaea8b79535620903b94fb798e2e834a2f3f739344c9f347dd1680231b17cc" +"0x120b6294e941c95e30da701c4954a1acce4ef8778ce9a051fbe6fd37e1320ec9","37804939","0x0ccaea8b79535620903b94fb798e2e834a2f3f739344c9f347dd1680231b17cc" +"0x94c67f47aa147bc306b9b4a7311023ae26de96d93556fe7fae506b596ca78f8e","37805075","0xcd7774e4456b11a3606a6fcbbcb17203435e43f06a530dac9295d1d0dc3001da" +"0xa77e0cd9fe51540523c1041c9348d7e3bcb0fc0aa784ff6f131ecc73512b07eb","37805634",NULL +"0x409eb0f400dadbb4a701e99dc38003daab591a1020c05f386fd6d1cf91e39a12","37805647",NULL +"0x646b75bccfc21d4203759f355d62f2be0a07e73a14acd7247ba484ad1f40acf7","37805661",NULL +"0x921dc2a0bf028d9af5bf27c5f9167a9be40c4b95267b21b7adfa8362ea87b52c","37806016","0x8b8e9d54c9cacea24c6637bdc6b10d3ecb2529b396ccad00bf989e222796c03d" +"0x52fff150f7de470eb76f2b57facf99989a96ecbaaa417c49e1bf4510ed408666","37806030","0x8b8e9d54c9cacea24c6637bdc6b10d3ecb2529b396ccad00bf989e222796c03d" +"0xc6d0d845b42a8ef9adfe0afd9ae580b8d939984e22b94f532020a51adb55ce6f","37807002","0x7035601b19e5797ca0a893b3e12aa2495aaf8c4e727b83f0aa5d2ffeea749f31" +"0x1c9befefc1ff2f4d3a1faf6b896159cc035fdc7f6b023d0dc0b0f8778c24876c","37807016",NULL +"0x704343ce09603e8754b94c954d984b7d9cf6cea8fc87cf14c60b35d16ecb4566","37807027","0x7035601b19e5797ca0a893b3e12aa2495aaf8c4e727b83f0aa5d2ffeea749f31" +"0xcfdf94e966c2a71b6bf75b8f735fff7e0dce3c617f0cb66aad0cbc9352866e32","37807076","0x7ffe77b342929e6daa53730a540846ca9b85ad36551011b968785f80aed86eab" +"0x11f31ac4d9f14ca074806faccfd7901ad987063821973c5c5b37fba0c208d14b","37810502","0xf3a62c92160c996b0ea429c0002e514110bd2ecf64f31ea637d360ea9686decc" +"0x0fa9adc6e457c188e4014a395ee35130101eb83caf8dfe02c5a4b7b965e70bba","37810510","0xf3a62c92160c996b0ea429c0002e514110bd2ecf64f31ea637d360ea9686decc" +"0x97343d8416daad51367d41ff3fa2e3b61f6d1e1cda8ac1ed739b62e1b5c9dd9f","37810519","0xf3a62c92160c996b0ea429c0002e514110bd2ecf64f31ea637d360ea9686decc" +"0xcee947c4c95871f659d1a58205f84924e451cd87f187484b814982ccb2c1d04e","37810595","0x2116f9dba3f1905599f6e6e3654168067be3685c44360f9507beffee7159a370" +"0xd422bcd4576a3bdbf728c22a7068b973d28b4558200db8ffad319d05869188e5","37810608","0x2116f9dba3f1905599f6e6e3654168067be3685c44360f9507beffee7159a370" +"0x9487125a82306e31ff0b0e3224e75ddbe3e58ef9b4fecc385760515a576508f7","37810615","0x2116f9dba3f1905599f6e6e3654168067be3685c44360f9507beffee7159a370" +"0xc251de10828230cbe6c2cce91c339a25ec2bef0c48280227f356836bfabd430c","37810621","0x2116f9dba3f1905599f6e6e3654168067be3685c44360f9507beffee7159a370" +"0x4d5b89726980c4329630e80215aec588d04bfdfc794957365ac11accc2dcba48","37810628","0x2116f9dba3f1905599f6e6e3654168067be3685c44360f9507beffee7159a370" +"0xe332df81cfe714a8035f0ed2abd84e4506b8f0c5a67bfbf055a918ab84bc1b4f","37810649","0xf3a62c92160c996b0ea429c0002e514110bd2ecf64f31ea637d360ea9686decc" +"0xa0ff46090ef99eb0f6a7acc4dd674827c6d53cb5927a9340ea2bc8b4b1c1bfa1","37811615","0x8753bed5c34c31b9edb31efd9552ee6174d1f4e80626fe31cf4380edfbca9f45" +"0xd72543d37eb01ca22b50f5527e3721ba36078b80838e0cf3f68c8cd7118136ba","37821542","0x49f43a617971dde05348e842c4758be79739e15f37da0425750a388de09087db" +"0x04e85580268baa42a53626646f0791084551bc7a81970bab66d8e3864bcba7d4","37821607","0x49f43a617971dde05348e842c4758be79739e15f37da0425750a388de09087db" +"0x0f415606d18b863428432786b48901105b36db285fce08543b0a6aedf74d7663","37821667","0x49f43a617971dde05348e842c4758be79739e15f37da0425750a388de09087db" +"0xd875fe14b3d94fc2314d7cb861e6b7748dc94ed3c34a11e544858279e1931367","37821681","0x49f43a617971dde05348e842c4758be79739e15f37da0425750a388de09087db" +"0xb376f7cd73d440c0c0c784bfa6dbbd694eab23c505e85a15a9cc8c1b7d0b08f2","37825723","0x5e5300ff532315e4731def17b21eec086d56101b9dfcd155674547ae5c324ff3" +"0x766ec10a0b33ec7d04b86ebbacfec87a9ab33c1bb86bfca592d123760ac25af8","37825756","0x5e5300ff532315e4731def17b21eec086d56101b9dfcd155674547ae5c324ff3" +"0xcbb5a1aa227114fa41d984ab2db6e03e78b77e317a85e3947064779b3dbcaacc","37825767","0x5e5300ff532315e4731def17b21eec086d56101b9dfcd155674547ae5c324ff3" +"0x514cfa5eb557dcc7e1bed113950544345348f41e585afc57c5b3b814e194079c","37826192","0x6fd1c80317f4996126747416899c2477a6fb8ff8eb39b476f8e6eb87a9c3be3a" +"0x589062bd932a2a3791ae78ec1424891b3fc24b181821c60d03faf8d30e179a9d","37827704","0x917c9823877e5d19e2c8a757446a310a4e814303b090c98509899929cc80c16c" +"0x73f4dd239f2a6b5e8b6d0fbbd714b1b393f51cf03569a641fb5cc6db053ebcd7","37827725","0x917c9823877e5d19e2c8a757446a310a4e814303b090c98509899929cc80c16c" +"0x4819cf8037e9c548eb604a0de549a16e412a30ebc2f63ca32fac77fbe45885bc","37827743","0x917c9823877e5d19e2c8a757446a310a4e814303b090c98509899929cc80c16c" +"0x406e19bdaeb02d7ea46ae5a108320ccb694e89780a370ce88a62a3437a32512f","37827756","0x917c9823877e5d19e2c8a757446a310a4e814303b090c98509899929cc80c16c" +"0x0425fa1dad578e8fab6a57e3461833463d469dc5381597d71ab290f1d8105ce3","37827771","0x917c9823877e5d19e2c8a757446a310a4e814303b090c98509899929cc80c16c" +"0xa48c84ab51b42d2b59ad82b4aae8726d873a2bb1a23170dec7ee34429da26efe","37827920","0x917c9823877e5d19e2c8a757446a310a4e814303b090c98509899929cc80c16c" +"0xc2684c8aafcd9ab3a9ab5fd3a91c6fb0169e42fbde552eca6f585e16cf95471e","37828051","0x342975b4ccd3ffccbde66ee03e0b562a5bf947c389bff34ebc1038082745806b" +"0x91217a812e11673b4f454e42095b72237469c9bd1786e53611c6b159f0065017","37828253","0xd53b7d4244d15deba01e51acee299d20243c7598a4aaae2ab9520379aacf028b" +"0x168d0870d98f173dbc362886c21caf6c0ffc8ad8b299aea9148a7019c1da4917","37835118","0x0f3b6aa21acef0fce2078cb4674e45ba4e0cacd2eab0fdb9f9f0a9990c8fa233" +"0x34f92e504e613115c488c4d395f5ea61740e13bff78293933ffef0ae83fd43b3","37835131","0x0f3b6aa21acef0fce2078cb4674e45ba4e0cacd2eab0fdb9f9f0a9990c8fa233" +"0x2966e6bb2187e2c5f9cb54becd03bb25cb8ce346635f15e1fad2a693f9e60ea8","37835875","0x21c3fb45a989d1a6e1c7f2cf4a07694a87415872c8ad557853fd5a5960ec4b50" +"0x39d68d1fabfa42e51006a0e430216238738a792430920808e005e12a7c918d10","37835979","0x751b9955791df607cde4952d5d25d2d4204f0268cc665cbb70ada96c6e2936d8" +"0x21dc715f3a8a3ff3227009b4c323c7489a7f7c5d0c17e5cbf353f9e1c9af478d","37835995","0x751b9955791df607cde4952d5d25d2d4204f0268cc665cbb70ada96c6e2936d8" +"0xf16aebeeb94b81235663df4b1a5dc09dfead96f29f57b434c0db6cbf45015d92","37837812","0x68b76f0e0ee61f0f9e2a6e6b29d12926d7b67d2e0772f58ca7c1e4c9d9811839" +"0x4b3299fef3c4ed94b8e03a64f5aee18df80ba9f4140904d753320755201e0ea6","37837932","0x09ff255b61aa6bb6f7234fa249a32d34f1e434715894a370ec72880d0fbc4e9b" +"0x09528c355d0e73dc3b181d8a756aef14574c773d44d66f5086a9c29409b54f28","37837983","0x09ff255b61aa6bb6f7234fa249a32d34f1e434715894a370ec72880d0fbc4e9b" +"0x1758fe0d35d7576064d0837d192f22d8c2f78b9b5c2e90671811b5d2191777e1","37838021","0x09ff255b61aa6bb6f7234fa249a32d34f1e434715894a370ec72880d0fbc4e9b" +"0xd6c19c9f2be84ccfa21e4ac4992cdda03dd23082e2a99d8b38f014f28b054740","37838362","0xd2b24c5513d3bc0a5a4045548453153396317f655f25d908f678936ea3953a64" +"0x3ce3df7bebb8c12e729144d366ab01b070a0147739a420132e9e384d4287148f","37838429","0xd2b24c5513d3bc0a5a4045548453153396317f655f25d908f678936ea3953a64" +"0x1be62e1808d0b6c8cc03c752c88f5d9c85262ae0d6be7bb55a43795a77354d69","37838680","0x1f2dfb25affa9147ad3a8ca1910dada1a2bcd481e5bb1dde7f1f800922bbc669" +"0x34a82d9a7511e46908389a23957a4a4f6ac0e71ab539106b1ee1fb958e63375e","37838707","0x1f2dfb25affa9147ad3a8ca1910dada1a2bcd481e5bb1dde7f1f800922bbc669" +"0xc4aa8a3ae163a706b696b16d4d03a2f592b15fb64db68b55f4bb7b7015eaaec8","37838723","0x1f2dfb25affa9147ad3a8ca1910dada1a2bcd481e5bb1dde7f1f800922bbc669" +"0x2fb13e0f6b7669398c3aea213705633aa988b4aa87c2492f5264403616935c05","37839699","0x2909bdf1593c956eb511663ee0e535e969905ec6c022cd96700b9d8f640ccc98" +"0x28a38a8e929668b0f4316dd6c014c7216f9914e23ca73f9e8fc7243037ca474a","37840460","0xf885345eedfbc38f3b963c55f4120ac22705f81c45bc98f4bd0169040d52fef0" +"0xe9331b9ad4c91cfaed88c7f36ae81d572ff769ee9ec3bdb92ff00bb839ae1a1b","37841652","0xc2a74d6e87e29d271595001650da6df9980da0616ed31041898c9376238b5247" +"0xe83adc10374880aefd433e378b62d7939231b7f5669e92a4c22585846530c82a","37841675","0xc2a74d6e87e29d271595001650da6df9980da0616ed31041898c9376238b5247" +"0xc5775a45292531c0495bba03e7c2614a425b692cc004b02656a16c300839858c","37841857","0x0622d61d818dda2b4fee80a84cbdd428e5bfb8342f961cd1bbc19bc39f9ab92a" +"0x8bb8aacf0117e3d0c6690929426c6b0f1c698dc5648f5f3a18d558fe952d755c","37841879","0x0622d61d818dda2b4fee80a84cbdd428e5bfb8342f961cd1bbc19bc39f9ab92a" +"0xc606bf93d200ea874bdff46e91e02cdeee538079f7d7fb388a5563367a2e8d21","37841899",NULL +"0x0efd4bfdb4e84b9e783803cea3006bf44352e0c225aeed603f0cb369b12adee9","37843775",NULL +"0xdca6db79271b554be5cc8668c889cdeca43f052cc43896ff6b797ec436bf45c3","37843780",NULL +"0xd9786c7b17fdd250dac599a9288c35c1f53138c0f56a79a2b9d6517e4cb92dcb","37844036","0x79f1e286e914acb1608f866b2900842bcf144857f09139c561f11bd82cc40157" +"0xb7f9caa24ffbc3af28e95d23f22a42f65db6eba0f242bf05774d576a95b8a1f0","37847472","0xe2e363792ec8b324abc2e356bfbfe3162f7c2e08c8ed3e20d7ff9c9dcdb8146d" +"0x014ff72e25527525233e72edec4b13c6d963410c7daed4e82c74c2d61f1819ca","37850010","0x4546b21588db1d59a6bf973e17635b3b5feed942a5fc26a0ec371027fd27e8a7" +"0x545f9ab32730d8357017059d36b58568489fee8c5da527261519f3c8fb2c71d4","37850059",NULL +"0x1578d82137e1b406e6fc2d708d7698c004a29175dcd8c3de4ab416471fa8d70a","37850082",NULL +"0x73ee85a91b58c8683fd5c112673f031d339ae1c1b5dd9cdfaa057c1834c1ba00","37852960","0x8126cd2ec1cc984899843dbc341bd5edb7ea98b74c6d8ad57f6665fac9aadc81" +"0x1a5cb0cc5ca81dd3364eb1e360f80d286b6ef43e635a07149a702e32e1e90598","37852978","0x8126cd2ec1cc984899843dbc341bd5edb7ea98b74c6d8ad57f6665fac9aadc81" +"0xf86a2f1fbf9a98b823a2db346c8523cfe65fc40e0d6975106ecde7acc00e6b58","37854863","0x6b9f22b82a33af665f30873e84f3c3a1e1e90427f7aa22e8f8e823c1f047e1b4" +"0xb0f1e84cdb2926d05d3cd076903980d594dcba44b51e7225a944058f0d9bf8ef","37854877",NULL +"0x283f03f9dd0e576d07ed0424eb4f7fcf3a6178b12cd3b3a73d0c0ce96037fbb4","37854891","0x6b9f22b82a33af665f30873e84f3c3a1e1e90427f7aa22e8f8e823c1f047e1b4" +"0x05e4dea371ac4a2ba6bf010a961184538bbe134c1d98742c232d9f6315b5c372","37854905","0x6b9f22b82a33af665f30873e84f3c3a1e1e90427f7aa22e8f8e823c1f047e1b4" +"0x29fd583d7a4bc9280861e7284fca3c2cff699db978e048028aed5fda65573d71","37854990","0x6b9f22b82a33af665f30873e84f3c3a1e1e90427f7aa22e8f8e823c1f047e1b4" +"0x00aee64800e42a398026026a5cc1461e729bf77559205f72cc987db0204954b2","37855366","0x896771573c38ccacacde00dbebcc294c57f555e9222e19aae4e60961d79ac314" +"0x7d503a831603f6f6df766ee958a1638661187c2529b95a33a5fa70bd7ab86a83","37856094","0xbfa6c151d0b46bf90fc9101d6d73679bbba5d2dcce0a9f3f6b524b74b183c756" +"0x7f2194de7d01a4b32f06527ecf9ca10f5ce51ab6ad4c49dd70faa7294489737a","37856568","0xa81c20e2a76b58087e59e872205fecdaadb3bad2a4f4b7619e943627ab443eb7" +"0x64375063c2d63785777cc7913776637ceea91dce73be6b902eec1dbca676144d","37856612","0xa81c20e2a76b58087e59e872205fecdaadb3bad2a4f4b7619e943627ab443eb7" +"0xb5c96ec9a80c19d32787abf29cde16cdf3ca14ad324055fe263c74d1a890547c","37856788",NULL +"0x656cbd6fc5f0ad1f6c52f54197ce54d08f7ec146d57578bb106cb48c66e9d155","37856796",NULL +"0x12769f90f53d17941afa86f5ffb130bb60c5b41896f775bc20f2d53fe7e00fb9","37856810",NULL +"0xd6957454e91e2368302ac5807a532bc12ab22fed32254cf104da7292fe08acfb","37857308","0x641776b040d1464cc66bf744b96a6ed5fd8d1710a25c2c0c4ff36a41202200fd" +"0xf9177f2af9024423018d0e826158f7ee4356989803e457797bb89aeece3faa7f","37858501","0x081786edbd072c1348f04958fb5406f3194e3a9f064934f0f0760076fb3e4318" +"0xb96b22e4e643f40e83a8e41a98b3641d47e44d2844d7aaa5c661bd2b77667c10","37858889","0x081786edbd072c1348f04958fb5406f3194e3a9f064934f0f0760076fb3e4318" +"0x03657453a4d08780ffe3e70c0bb612b36906159525aa643c57fe25b6ca2ef56c","37858946","0x3ba267d9088dc92808747eab9fca7557446bb8a3f5cf435bad4ad3ac9d9c2be6" +"0x20cfbffc912b349bfa965d6e98cb0a969d056eccd540dca9d8373f786332d39b","37859926","0xfcd38926f614a7bb326804ceacbd75280c11d9730ac026e673705952061f723f" +"0xbe367794cad90863c35732c20e0fb22ddc8f7c93f73468be9a44a714a86d084a","37860981","0xfcd38926f614a7bb326804ceacbd75280c11d9730ac026e673705952061f723f" +"0x69642690fbbee868e625734d549e93be77c577f12c898556507e27869a5141e3","37861951","0xa97bf7843dbdba2b944bc8129b8489560c150d5b47bf33276a192451d7fbeffe" +"0x1538b0e72c96be1198c3fca007f0bd925f6c88faaea5d176d0b2ffdda91b7468","37862265","0xc977462a6e9d705596dd9912f020fba5a4bb65176aac0a01c1adf13f6e6a25e0" +"0x18ce72c95a5c2e75ccfb2af6a3074d24659498e6dc213b0317ba920c683c65a3","37865515","0xf095dd1449db4909cf9155133f8e649b1cc66b217570d8494657ade37ae3aac2" +"0x7b03be8713ea9b6916bbab28e11d2157be262d4a2351887cc270d2afbb5c8224","37865810",NULL +"0x3f4fbec49eed099ad9c5572c703cea214db6d01e4234644c3ab08f3da15404df","37870354","0x0fefdeef0424989b8cd2fa0d21b5e0dde7fa25f1c0839146988e4b511d4828bd" +"0x9f18547cb4ca40320eda2e4b5068b3831cb71bc9a9f1afa3a0bf7b0aec6ccf7e","37873588","0xb77b996856ddff03ab40f4a5b3ff018eb1e32364e43773a2d16fcf24dfef863c" +"0x07456a06515f4eda182cbe3ca94de08753007c8f89847e79e3574c708cfa0409","37873606","0xb77b996856ddff03ab40f4a5b3ff018eb1e32364e43773a2d16fcf24dfef863c" +"0xe70e31cea42a170053c86b887e740946e0df11da00ccfe025f045c172be5c9b9","37873621",NULL +"0xf1c941ec2914b9a5506948ffba9920eb86a7761ae6e6e4e69d99fbd137a487c3","37877528","0x10e271e59947b76031793c1e6b6aac4260934c57d88e5ec82bda6b297314240e" +"0x35d5922568365a5713d21aff1fcf4c386380f24561b20fa3813afc2ab3680c34","37880853","0x98c602639a97f7dfeb86243a2c1c5e2d5736aa5da15d2173d330807125dfd007" +"0x5300424f62b2fa068dcbfcd206085a049e6033df043de162b74e79c4310f3e72","37882423",NULL +"0xa6491d5148958c8e41cbbd6eb6661267525058b8d09c0843dc3d1bb59d7035ab","37885777","0x7f69cfaed05c265642c7e6cbf98719540798ca445f03806f39df393e7ee42167" +"0x0fced05c8b3c85448ef631a6f6fc770644f273fcd26def719b832705e34ff3e1","37886728","0xfa6acf2bcdc1127a7e81de5bf1c89d511300151d546801c7457bc8cedef4df4a" +"0x31e47792a13af33f3eca12a3fa0d917ccded3e84a3ab4adf3dadfec267bcdb11","37889538","0xc1dc18f3c18e412bc306ed2ffaacb2feb6774448bf1f81a96ebc7567c14e5465" +"0xa254552bda590233140bbfdd540664108cf66a2dbc8ea2376e4ff2b76f91fe1f","37889854","0x2f84b954d401651045c7a65158b8214a786b35e1c9d7e08f6f99e3415eb4539f" +"0x441f797ee7201db9bf88817dd393789af9005988f505de5234d172a02d22b82f","37892460","0x6f71cc14f4b3805065e69425368a3804c6d38343fb22387af7192931b93e8231" +"0x96075463b2b145c2c4e2ec8024df23327036633b771e7c1366efcd060d6196d6","37892513","0x6f71cc14f4b3805065e69425368a3804c6d38343fb22387af7192931b93e8231" +"0x8d4c0b4287d1ec4810317e4d508a935fd5fafa9373183ced52c2ba9a113457a0","37898831","0x9259ae4fd9e4e8e52a9f5cf91536b7406a906763681e89db854397058c9b39a4" +"0x69d002962724ba0af651d3cdef952394304a23541ba28d3e84d4b9b6196b9e30","37898847","0x9259ae4fd9e4e8e52a9f5cf91536b7406a906763681e89db854397058c9b39a4" +"0x518eae2aa0de8f32f393b50d966e6d6699fcc0287ce9349e9e6d46727d4747aa","37902362","0xa9b2606c5734e9d5be733d31befd675f01af31e0145669eceedab8f33cf8744b" +"0x9d6c8c60ec1760abab20316dc7d68ab3b3dd5cbe1602d8578416c396c5528d4c","37903881","0x0a49d280403aaa64c251bd788e19c8c5f632bcc8a56895c960392b15b8c0dc32" +"0x3a59a5b94e2011d147c7e4c6c094837012fcd9d9f9c81b1e1fbfc88326225f9d","37903890","0x0a49d280403aaa64c251bd788e19c8c5f632bcc8a56895c960392b15b8c0dc32" +"0x4baa67801ade141da10677f715c6af813b53d31f9eb6f90bab55e4c4f4185d3b","37903905","0x0a49d280403aaa64c251bd788e19c8c5f632bcc8a56895c960392b15b8c0dc32" +"0xb3fbe173b41a7c726a9b1db67e087b5f60150dc1a34dc06f85a495bc7ee1342e","37903911","0x0a49d280403aaa64c251bd788e19c8c5f632bcc8a56895c960392b15b8c0dc32" +"0x11d4730650752497cbb63844dc7ce1549d4b92fa2c21b5f9b62db2d9f26e954b","37905064","0x787e2a9770dd0826ed59f7c20a17cfb71303cbe3280a342d4b25dde1c78eae4c" +"0xdb835812ed67ecee0039618045d7597d7f150ea3ee6d7549badf6fb60b864dc4","37905077","0x787e2a9770dd0826ed59f7c20a17cfb71303cbe3280a342d4b25dde1c78eae4c" +"0x8906e166958a6b1697f9bd5b32f34cb760d899c8eaff15af00d0978a5665c2fe","37905111","0x787e2a9770dd0826ed59f7c20a17cfb71303cbe3280a342d4b25dde1c78eae4c" +"0xb011ee2949d409103106cadb4e86608066eb8e019ccece1c8f12a2509d73c52f","37905135","0x787e2a9770dd0826ed59f7c20a17cfb71303cbe3280a342d4b25dde1c78eae4c" +"0x87cb7a48cbc439fbcb36eb7892e7afa191e10ecc357aab86833d8c2c0dfaf9cf","37907252","0x8716a3763ca9660ede31e0c3239264ff468707b63fb75da6e5b3435e878d7a9d" +"0xcb64ecf67d5ca3007db447792d5a6da5ada291b9e5503f25945e5388ac8198f4","37907271","0x8716a3763ca9660ede31e0c3239264ff468707b63fb75da6e5b3435e878d7a9d" +"0xe127ff9a22a12f467caead9312679d58bfc0c0973ad9826a67b5b0d03d70d016","37913082","0xb51c2e1cd185439fbd87b3da8c2cadb5b6315eb4818250cbdb84c6861c70274a" +"0xb85cfedd4b3b7320bc228259f5eab7bbde736e15aed46b1b949cebeaaf6498f2","37913097","0xb51c2e1cd185439fbd87b3da8c2cadb5b6315eb4818250cbdb84c6861c70274a" +"0x410f02e1cc684e11720c669581f35f6111d6b86256fefa7084ddc1f70978fe57","37913324","0xb11cf12fee2627d1002ada38d53154f88469b68ebd1c4aeacfd07e05b388976a" +"0x59039310eb4cb12c3da9207dbee9e9eeb247aac887afd4b3d392b9d1a1e3f2ad","37918952","0x77972fcba0156fb9a73c03e18bff55af3a5ef96fe871b6a26078feb96e97f12c" +"0x0fa67cbae2252886f10034cb4410318f15ddb84b0a47e517b1848d8ae0d5f6b1","37918966","0x77972fcba0156fb9a73c03e18bff55af3a5ef96fe871b6a26078feb96e97f12c" +"0xca8996d0796072ff49d16539354676b89665772da3432f14ac7fea0bd4922bc4","37918976","0x77972fcba0156fb9a73c03e18bff55af3a5ef96fe871b6a26078feb96e97f12c" +"0x1235836885e4d4c70b7a74ed39367aabf900175eb96e8924c15373360c629da7","37918984","0x77972fcba0156fb9a73c03e18bff55af3a5ef96fe871b6a26078feb96e97f12c" +"0x7809da688e9332681b1fe5414540ab79a92b92903641340ae5baa973cf8a441e","37919118","0xa0563417062f453626aa6218c18370b666f0ee67ac1bce48e1aab4fe667b82ac" +"0xe8069d52e348b3826857750b9be44ff3789a865d3e091f08c36059e68dffb6e4","37919189","0x8716a3763ca9660ede31e0c3239264ff468707b63fb75da6e5b3435e878d7a9d" +"0x5e79eb20887f8ea9c373581fe9e23c299f8eefc873066f3f8b1cb2155f966bdf","37922599","0x23e19584dece110fcf57edd2ea8a97f9a0e601fee295bc97d54ffa4295e2c57f" +"0x3077aedf12b1e39d03a93a9ba5edebf39f403bafaad12847f11db6b5384aeb31","37922630","0x23e19584dece110fcf57edd2ea8a97f9a0e601fee295bc97d54ffa4295e2c57f" +"0x910fc321a7356884027d19c3fc003dbf1c51b8db6a0e1e78f5e1b3583f36fa3f","37922796","0x23e19584dece110fcf57edd2ea8a97f9a0e601fee295bc97d54ffa4295e2c57f" +"0xf0e2b2dae0534883a9836af8bdf8410e624284ed51e2b734d9bf5df730a1f207","37922805","0x23e19584dece110fcf57edd2ea8a97f9a0e601fee295bc97d54ffa4295e2c57f" +"0x8c2cdf148ee6b1f8d48b08b25345967560f690e4309532751e65ccbed011f3dc","37922951",NULL +"0xc8418d7a051803379bbadf3add26598db2a138a4376e871a7fa378a65fe5d509","37923324",NULL +"0xdcb85f30186595b99411a0a4274c6958e324212e290dda9741e49559da1d2884","37923337",NULL +"0x90bb822a18cfb65735644929b5f7e5bb5419b29bc7fdc39be5f387aa5b70dfde","37923342","0x1234db101a9019660dd3dc6f3256955e822b07d928fdb2c479258ce99b407572" +"0x90bb822a18cfb65735644929b5f7e5bb5419b29bc7fdc39be5f387aa5b70dfde","37923342",NULL +"0x38d2e4d58067bc8787ea4b9efd03a3b629ec61784fbd8322cb5e45c42cdcb04c","37925106","0xf59f0636a2dd1fcecbb889b3a772b1b897e8ea55e35b298b0ab257d9640b7ab3" +"0x151d6457dafd6bd4499b456429ff21c586890f5c38e835647f26fc476f709f32","37925127","0xf59f0636a2dd1fcecbb889b3a772b1b897e8ea55e35b298b0ab257d9640b7ab3" +"0x09245c3fa7dfe1a60f7000d7805c66b2ba53d046d4fb14f020eb6185b45a02eb","37925146","0xf59f0636a2dd1fcecbb889b3a772b1b897e8ea55e35b298b0ab257d9640b7ab3" +"0x1123ab3f6f187bcd0f38ea8a96f10f54466b29f77cc6e9b8ab74bd09cda985ce","37925166","0xf59f0636a2dd1fcecbb889b3a772b1b897e8ea55e35b298b0ab257d9640b7ab3" +"0x4beb3d9b172c91a2a95f20308700a19613c871cdca5c94223eeb9529ccd04220","37925202","0xa0563417062f453626aa6218c18370b666f0ee67ac1bce48e1aab4fe667b82ac" +"0xd06d534cf639a1949254d0f5a8f366972dd2fcac751cb2127cd4fbc737ef79ba","37925207","0xa0563417062f453626aa6218c18370b666f0ee67ac1bce48e1aab4fe667b82ac" +"0x1f6f3d6ac41a656cf01a7c0d424b8db639c8235b18c760692c91f5e8458e5b4f","37925353","0xf59f0636a2dd1fcecbb889b3a772b1b897e8ea55e35b298b0ab257d9640b7ab3" +"0xd6346d561e13461feb2c8fffa85673fc374e18c8bfa49d3aff41020efe1e4d6c","37934598","0xb2b6de6d4ea8dcd0e071947845bc705794aa9f6eb5b74b7e36b2d0509969f32d" +"0x538526ed3cff7f33d043a5c29194290cdf23b52ba9ae12d89662dc61d86bdc40","37940707","0xd0b4da4df15a8b0e0b1901f05af18272ed5fd379d6b3a09fd45a135746f1c56a" +"0x86613e5e7a43705d9631c2599dc00b33be0d4c173fa14f65a0d94c77aff4a857","37940878","0x6207cb5e64f1828e7b3e34d87770aba7c180c40156e18271c4fe71259aeb0f73" +"0xd732030340431843ef2f2b411b67224823c57104165aff6430fb24231de995f9","37941175",NULL +"0x52f3257debc2acabd28493a6fee8a1e4693188f38b1d27c66987b7cd16cac99e","37941189",NULL +"0xab01962e5d97e21dbf1d10f6c733cbd4bf5a1ca3fc42b0a9916ee432fffbc9c3","37943085","0xe950e3d4fa61f6d936470aafb55fa67386b8fa85891c4451e5cebdfebdac47cc" +"0xc7f6b9571583ca3a0b3163183911ad2fa5bb386a69c3f1fc24d3b95e34592048","37943100","0xe950e3d4fa61f6d936470aafb55fa67386b8fa85891c4451e5cebdfebdac47cc" +"0x8809dd5d163ee538fc8ee59906c2d1d2c2d892dbaa13d3a777ae122ad6febfdc","37943565","0xffc947a563d122c1a72c03ca9a2d7187cad7462e483bda3798a012d755f00a33" +"0x4689d44f9517c739ee976ab73b2fb60913976a15bf5a20ed3e2f9202467f69e1","37943620","0xffc947a563d122c1a72c03ca9a2d7187cad7462e483bda3798a012d755f00a33" +"0x178027b2731f9b01d4c7ab1f17f9c04e1e3048b7f5d8e1b2d5a7345b09eba5bc","37943767","0xe950e3d4fa61f6d936470aafb55fa67386b8fa85891c4451e5cebdfebdac47cc" +"0xc488fde41bce6c6797ca75eff76643b1951ab67064f11f4c05cb68733eccd2f6","37943796","0xd0b4da4df15a8b0e0b1901f05af18272ed5fd379d6b3a09fd45a135746f1c56a" +"0x0a74d30749b4e8749cb6833505c8699d44311533af387d1b7c68dc8c0fb6a1c2","37943808","0xffc947a563d122c1a72c03ca9a2d7187cad7462e483bda3798a012d755f00a33" +"0xcd7df6f7c38906d6702e18441b2700790fefab5bbb3b66d394a1418ade80a61c","37946250","0xb35c1af1140eb210e3aed906ded8cee3fa09744c7f26de3f1ad81cab3178fcf3" +"0xf0ac9f16ad994e6b3ef33398d5f64528133d971b096d0a65d563a60481940a07","37946264","0xb35c1af1140eb210e3aed906ded8cee3fa09744c7f26de3f1ad81cab3178fcf3" +"0x58ea88f170c65696a5ede6f4d3bb3e17032595c275fd75884bd8123813e99754","37949478","0xf3c394241c5765fb829181dcc1815bc73858314cdd630be2c8a7f0650788e0ba" +"0x73b14c18157a1caffe509c09d6c5ccd8976f9826544e04fab6c59fe12dadbb60","37949501","0xf3c394241c5765fb829181dcc1815bc73858314cdd630be2c8a7f0650788e0ba" +"0xc203d97ba7a45a417e1be6658d2929513575c1cce09e2ef92a1d5054b65ece0a","37951181","0x888c664d2805e164a92c7c8c4f76ff8eb2b490379f074dbb061a1beaf05bd66b" +"0x50389190208d620ae3245db87c13d7e60dfb04a3241e4b5196d029f7900cbc6b","37955117","0x4e4fbc9cd165fc0d7650b0b83f8c3fbef586312c722dd352b6208d4dc9324972" +"0x988e3af1cbcf4943bf1371776bd388376c69baac65d1097a43b8eb1db05de0f5","37955125","0x4e4fbc9cd165fc0d7650b0b83f8c3fbef586312c722dd352b6208d4dc9324972" +"0x31f5897a15aaba8f569f36047b41b1f84c2e4a5c4f3362f8f5a4e7894dc3089b","37955185","0x4e4fbc9cd165fc0d7650b0b83f8c3fbef586312c722dd352b6208d4dc9324972" +"0x05eb185f140aafbbc4b40bd7d542602e31a492c5a2aa4f9158af74a7eb7be944","37955300","0x4e4fbc9cd165fc0d7650b0b83f8c3fbef586312c722dd352b6208d4dc9324972" +"0xa12ed86527c05b8ffe798c66bd4dba7b143aed785995aecf51bc74c3b7f8f97b","37955314",NULL +"0xf3c8bb0d0a92a5b18ad7f566d5a64a61c606891e94384c899a7a79815f1b4d42","37955338","0x4e4fbc9cd165fc0d7650b0b83f8c3fbef586312c722dd352b6208d4dc9324972" +"0x85eca9344f5fdfd74e0a99f73f8e65c725126e91bfa1c2ccfcf689e6f3bbdaf2","37955873",NULL +"0x084ab8c1684a6f4cea7078dc54ca73447f35b43b96cd43d2496a32e4e85316be","37956198","0xe950e3d4fa61f6d936470aafb55fa67386b8fa85891c4451e5cebdfebdac47cc" +"0x97e88567e91b1871ea5341e3bdb3c4684e64c025021f98a0520e860c7ca428e5","37957316","0xa5c6377709bd92ea006f0cfc68e716a5bca5c1c0a5b439109b83e0dfa009fc62" +"0x2202e937f5fb6f851fdfe3a799c881fd1fecfce161e697bf194ce0bddff18ef3","37957375",NULL +"0x534f478a5b58c7b2f475a3a9bdce8bf463f36217550c2b2241bedf9b7dd03c7a","37957579","0x5e5300ff532315e4731def17b21eec086d56101b9dfcd155674547ae5c324ff3" +"0x020439aaaa6eebc40fafd842310c00b57550a7a9fae241014df7ee465f305287","37957593",NULL +"0x0febd197341b71302d618db14de17bfa0a92a0bfa00aa32402df40c65167a1de","37974321","0xffa2335d85d531b650192ce820fb45761ed3eae704ebfd467ee29c5cc5267322" +"0x970f3cfd59a2af3e01c3f8e968fc5d5571297fb46ece69a6aa8ce0158adbaf0d","37975992","0x4bab2808f19279b5a9da6aee021952e715806aba8da22a7b3ffdd68318c305cf" +"0x1eb85235039fed3b259428572f183a38bfbbbfd59e89905fded001dc93952f80","37976858","0x0622d61d818dda2b4fee80a84cbdd428e5bfb8342f961cd1bbc19bc39f9ab92a" +"0x1b92c595766f58634456546281a3108ae8f2edb9f23fdc48d1a59d4bbb8a092c","37986194","0x75a6310d6c7c57ca378f05d1b1d88251422a73bc3bda2f24aeb3791806011dca" +"0xfe62ab2c4d32021f7718f2a8b7f58f14fdbecbc8d6cf400a0d3db07fab7256ee","37988720","0xf4b1b6f02ffbed6b3f83f0d6a7d40b6cdc16193f1d101a352b3bc7a81d0a307a" +"0xde7a520e05f0d15512e0d6eb093f0bb07a8d69c76953d88cde16e23792d34220","37993788","0x917c9823877e5d19e2c8a757446a310a4e814303b090c98509899929cc80c16c" +"0x77fe77bd031ee5f922d1b3c578054108c6254cd9dba48c4d7da63fc4c77c535f","37997122","0xd48d3985152a7a4905b7c32032aa38b31f30dbe13a98875512c55eaccf7a9c73" +"0x31597ef9c26ea82a423f5bca62a799160ca29c57c1e6308f1bb88148311a6d5e","37997288",NULL +"0x5dd6070fbb6ae5ce16561dfa8dff0228ac62eac45e1f546d7a37601253bbe88f","38003827","0xdc9fc83bf405e26e5be71a112b1665ed697d0b1e79daacec502b35ff723ae99d" +"0x6df689109a61c84a91b6d3307bcd05d034734b458bbbe229a6528fa10a139553","38009013","0xffa2335d85d531b650192ce820fb45761ed3eae704ebfd467ee29c5cc5267322" +"0xb2a53409b904e4dd7862f3247756aa80b5193554d6c11a6234fb16975a9a9648","38014066","0x8098dea5197e66489ba14e909aa959c3442efabe699e648964f78ccab51e956c" +"0x2b9fcac8fe3a799c54b060c5a47878b26ff5e99f66a3075d5bc3c12271490be9","38016886",NULL +"0x93effea2cf74535526def8e6194960bced26339d042d3c1e20a24a3f028bf0b0","38037773","0x246ee419d058ee706a38804281c94abc9add138365e8a5fd6a85d690ef706a36" +"0xe286ef8aa1411673a3559d91267471bb09c569827f3c4c4bed21a5b06c9b528f","38044139","0xefe1bc11d5e8ed32949167d7de44ff88b0772784ca8e844dccd1a2e06611391e" +"0x33e882ae07962da8f89bccfa82f274017ea9b1c4a9de2fa9f891723f110c8aa2","38044166","0xefe1bc11d5e8ed32949167d7de44ff88b0772784ca8e844dccd1a2e06611391e" +"0x99651a4752e06a8f077d81085294916c2143cf9d544b51b6c8ed78450e8829a1","38058290","0xb06627b6181e21168129f514cc2570c37afd9eb5ff804a3afa18c3286697b279" +"0xc630da10d03d9a367400d3441f9e95a4ee878660fadb282fb9a84ece071f2bc2","38069584","0x7c7a44f1fb4ada548aaccc55425b08543a56968415182ba8f046ee0a89b1873f" +"0xb1a575c417ff4a1107b1191492c05dec2d919011b398ec481eae5089b35a1081","38085502","0xf3c394241c5765fb829181dcc1815bc73858314cdd630be2c8a7f0650788e0ba" +"0x6549323a2a64609286ca0754eba3b0bf864d8e58f6cf7a9e86326f6baef9ef6e","38092182","0x896029b5a1a3677ff12a87d1f132ea9f140d72b59ab37ddc388a787d0acbd91b" +"0xb205011ff7e0020ae99515f056d054df08563cc1701e5a50383e3d8fa6e6fca0","38092193","0x896029b5a1a3677ff12a87d1f132ea9f140d72b59ab37ddc388a787d0acbd91b" +"0x2467580f7fbdb84c6ac05a118a7be8dcae16702bdd6aa6ac280099384847c1b1","38092244","0x896029b5a1a3677ff12a87d1f132ea9f140d72b59ab37ddc388a787d0acbd91b" +"0xa1f2698acefd3195f6f89dd59a61e76e1b077d45190eeebb20ccc216c4b72f9d","38098714","0xf2a177c565f52a02d205919577a91438e741c893dc76571543f06bd90bffb1c3" +"0x8d3d004bbcbc839350ae82d26062fa1750fa4d518eda61423b550837e8651d06","38098738","0xf2a177c565f52a02d205919577a91438e741c893dc76571543f06bd90bffb1c3" +"0x08514c1a9630086cefc651fbda479eabd37072dc6d0eea1127a2223835adb8dc","38098750","0xf2a177c565f52a02d205919577a91438e741c893dc76571543f06bd90bffb1c3" +"0x65327871010065e8bc026271a77435e92b087a35a63fa6c8c70262e39086ec81","38098832","0xf2a177c565f52a02d205919577a91438e741c893dc76571543f06bd90bffb1c3" +"0xb6e121d3bf67786399b3d8915891548e23831386d945b86ccafbfaeccaee58ad","38109960","0x01474554a289f84c89d78fb229d32c8aae06cb6bc57fcd1e9163125d807c4afb" +"0xd65ab5c058379cafa529c1d7f75f161a8490a0cbf28fd34a2dfe9e062ebfe014","38109971","0x01474554a289f84c89d78fb229d32c8aae06cb6bc57fcd1e9163125d807c4afb" +"0x0951864e988e4f373f00f4e6bd14a52bf0efc5d836be030c6eb69ffa01a65d6b","38110726","0x214f15557c7f2778d99da80e2a8311a25510f54047280d4a053b04629af6a786" +"0xca8b232455ceea6e0be55d1c1420f25149f4a8b84ef65bf8a711cc7683110429","38114537","0x8eb33393e04d2ceeff023886f33a388ed59b21006aee6852c3609c648c2c7d5d" +"0xf04a2f8d448f0f4140c10abfa5e9109e9ff2e31f3cfa2e5b0645e3e7990eab7b","38114564","0x8eb33393e04d2ceeff023886f33a388ed59b21006aee6852c3609c648c2c7d5d" +"0x270897233f4ee19722c82190bb69e5b2c9b83092502a4d861b6d75c7c18e2000","38124745","0x23e19584dece110fcf57edd2ea8a97f9a0e601fee295bc97d54ffa4295e2c57f" +"0x6ff30f7aae87596a883f490a07ebd7b4a3c22ee94ab935e54435dd4f55e648cb","38127159","0x227a084304654df046a5f2b5e1e3f0e7427b942d0a20229d5e5acbe8a0cc6511" +"0xef2c625d10279bfa6b33d92c7e6d1e33b841be25ac82bbf4359ef46a6676084f","38127185","0x227a084304654df046a5f2b5e1e3f0e7427b942d0a20229d5e5acbe8a0cc6511" +"0x590ac318e8150ac4995431234a0f3b7ca0c14e996db040d94107c297b0b30d08","38127204","0x227a084304654df046a5f2b5e1e3f0e7427b942d0a20229d5e5acbe8a0cc6511" +"0x5fa9a134a28a4b8b25212c02c07f79ff2e6debf498261915699f746dd106ef47","38127248","0x227a084304654df046a5f2b5e1e3f0e7427b942d0a20229d5e5acbe8a0cc6511" +"0xd4288cafc359712385d8f12976ae80feae043c89f257f1e508e78fec8ad40b3a","38127265","0x227a084304654df046a5f2b5e1e3f0e7427b942d0a20229d5e5acbe8a0cc6511" +"0x243e936ff80e5ca2a3b787a74bd0b7e3e9cab8821f818d448d025895c529e274","38130972","0x730768a5f78c741265baae1c8902a34c665d8fd444ef2c4e9e26160dba855551" +"0x76f8f3a717948c01cb97a4fbf73f894683aaf81486b1c921ca2c9c21bc883aec","38130989","0x730768a5f78c741265baae1c8902a34c665d8fd444ef2c4e9e26160dba855551" +"0x1703df6e087502170464bd2b6f61c7237995f0d6335fee883b44a4819eb26dfe","38131006","0x730768a5f78c741265baae1c8902a34c665d8fd444ef2c4e9e26160dba855551" +"0x94c5b782cceed1f0f924362383e7c09a7b7be855bca8dd82e9e3c903d94158d3","38131020","0x730768a5f78c741265baae1c8902a34c665d8fd444ef2c4e9e26160dba855551" +"0x2baf11521a9949e55abd108008c3a5c5515fcc9ee727e766fef74161f26a9add","38135439","0xd53b7d4244d15deba01e51acee299d20243c7598a4aaae2ab9520379aacf028b" +"0x66bcf0545cc80570228a8c64d568ae7fe689843f672ee897d062502f05ff7159","38138429","0x8126cd2ec1cc984899843dbc341bd5edb7ea98b74c6d8ad57f6665fac9aadc81" +"0x74032cf6c9ed19b9c3a4c248caea15de999851b1bf23c0db4f67fe5f359890c4","38138466","0x8126cd2ec1cc984899843dbc341bd5edb7ea98b74c6d8ad57f6665fac9aadc81" +"0x24c23e0b4394e54f7afe433f153536ea9d2ce45a09f1bbc05e1e87c96e24372e","38155656","0xad854f7d8e8734bdc9059fd2852139cf189fa39828f49b87785d0d197f117160" +"0x7b4830f631afb930ad81467ad68472542249800bd7828dbc39eeaf93550b0b8a","38155676","0xad854f7d8e8734bdc9059fd2852139cf189fa39828f49b87785d0d197f117160" +"0x5b69119302e6ec9cd17dee6261807e46b150981fa7578c5db52261b8ce5d25ec","38155691","0xad854f7d8e8734bdc9059fd2852139cf189fa39828f49b87785d0d197f117160" +"0x919a5cac262f987314c1c83626480dd8e7efd6e70370eec59c311c675de26d21","38159671","0xdf40e5f862554f61bf947930c895a4e27c0a8a0942d983efa0454d7acff04f34" +"0x1bd1ef6dd8cd1be6cb80faf6ab0cf9e2ac94cdd148da2f11f06521ee5c3575fc","38167226","0xf3a62c92160c996b0ea429c0002e514110bd2ecf64f31ea637d360ea9686decc" +"0x3d448474bf29c606e62094c7ea7970282ea62e6940780e2f675fe043975c87b1","38167232","0xf3a62c92160c996b0ea429c0002e514110bd2ecf64f31ea637d360ea9686decc" +"0x3d6f1469154ff4e5de0640be3089e3cd15a462e378a7222cd18f8b82185c23cb","38168672","0x0c2ca65a8e695025110953e5dd6ff914b3b763d865f068acbdbbe4439bb5b8ce" +"0xe091848c2f704149aef1c0ecb9a7407a73cf0f92197832cba5f3c0109e530b29","38168686","0x0c2ca65a8e695025110953e5dd6ff914b3b763d865f068acbdbbe4439bb5b8ce" +"0x4e5c04dd9ae379fce70e9bc37848953268e887f11c367300909b87245a4be1cd","38168698","0x0c2ca65a8e695025110953e5dd6ff914b3b763d865f068acbdbbe4439bb5b8ce" +"0x6fc854e8a180d9b3282a5f4b260837322cb879d54dd7ef742c8f9a71ceea089c","38204078","0x888c664d2805e164a92c7c8c4f76ff8eb2b490379f074dbb061a1beaf05bd66b" +"0xc700ff11c15f99a9ee0336d35dbdb18de358dfbadbc7bcc9a2c1ace971af2457","38208934","0x8ba1ecfc255fcdae221c6afee5ae2f64d71ba4cf9cef5938a3693166164b773f" +"0x3ee9e0b706f087d4dfec9a5d3abfce2d61c4903724bbf0cdc1bffc40f41ea09c","38208944","0x8ba1ecfc255fcdae221c6afee5ae2f64d71ba4cf9cef5938a3693166164b773f" +"0x40e9bba0823fdbbb134be9297aaf40527745d22b751ab573b51407f7c0304a7c","38210185","0x075c92f07386d9ad72c44a87a07a7524b0d5bd59b03f32b7a841c2b36087d56d" +"0x2b6d273a142421bbad25a534765aeb5fa964470b1f5a5647ff90af9e0e5619d0","38210720","0x075c92f07386d9ad72c44a87a07a7524b0d5bd59b03f32b7a841c2b36087d56d" +"0x26d85661ee03811bead44b518939d2d82bb05513e7fcd29c5ae9159ff6d1a9a5","38217341","0x9259ae4fd9e4e8e52a9f5cf91536b7406a906763681e89db854397058c9b39a4" +"0x384f2289582ddea5d4cb5130bd9fc8e253aa9c4ef920047ffd00153084764b4c","38217393","0x9259ae4fd9e4e8e52a9f5cf91536b7406a906763681e89db854397058c9b39a4" +"0x5b147d74b7030c63187110ed429432faa9687b21ae9b87a0c0ca3e1387b2f731","38219336","0xb6084a1a5f115ef694173e9cfa2a3e088d4df7aa05724038c66b72b37eae0ac5" +"0x487f045da6df9d4bb9c4019eebd43a1b960f7ff9793e30acd64a443868d76af0","38219345","0xb6084a1a5f115ef694173e9cfa2a3e088d4df7aa05724038c66b72b37eae0ac5" +"0x5f2217a2e28e5b4a4a1c8041a4280f3ad3293a5b03f698b63991102d13260b8c","38219353","0xb6084a1a5f115ef694173e9cfa2a3e088d4df7aa05724038c66b72b37eae0ac5" +"0x59048c9edd250dc3bfb1a2819d2dfe5100b92d3b356e9b4b2bbdfad90843c3fc","38219440","0xac7fcf73d43e7ba7a44d96de7dd8b74f527cba271da37968fe32acefa20b4cbd" +"0x043d89e5cf6ff57b4fc9608307c9a94842524c4acfca1b72db9a0cf2b2db35fe","38219446","0xac7fcf73d43e7ba7a44d96de7dd8b74f527cba271da37968fe32acefa20b4cbd" +"0x290a5551d1d93dc248afa9d19992312bb097c2fd475b394966048ce615ae5b1f","38219456","0xac7fcf73d43e7ba7a44d96de7dd8b74f527cba271da37968fe32acefa20b4cbd" +"0xfe54cd0297162ff4ce5cef76b50413bf87a9bea87cba13dadcb098e04a120a41","38220836","0xb6084a1a5f115ef694173e9cfa2a3e088d4df7aa05724038c66b72b37eae0ac5" +"0x4070890f7674c684cf4cf650eb5809ad856afa99ebfa9b18929cfc60fa1f5428","38232026","0x075c92f07386d9ad72c44a87a07a7524b0d5bd59b03f32b7a841c2b36087d56d" +"0x7f76afef513309e75fd70e9573662f34bf1e8f37a76cc67aab0c78b4d2e7e3a0","38238635","0x3a9e50796a8591cbb60c1a9d370e1d9836389011ca5690f67d257da8905e86be" +"0x90cd2d61a4071fd09d32f03a98d85bc424ba0df9e8796f507c9584091beeaece","38238814","0xf0fec9e96c52edd34e089f02b3c56edbaf137c876ab638f5976c4b283d25862f" +"0x89ea756653c005e5ff31260609ddea1e53881f9f57ad1dbeae408c924e2b16b5","38239279","0x690df8bb4fdad24735c2587247a1de1e79aa74bd265b690a80003983a022d22a" +"0x8daa16f5469a40bda2658bbfd962fc8ad422ec2f19cc4ccdd279edbd8d0085d1","38239319","0x690df8bb4fdad24735c2587247a1de1e79aa74bd265b690a80003983a022d22a" +"0xdf9030734f5af3da44eac9032a21026894592a2916d030d8fcc2bf05f8e99a8b","38259705","0x4bab2808f19279b5a9da6aee021952e715806aba8da22a7b3ffdd68318c305cf" +"0x0788dc96d5a5d6458acd3070274c3264ee7f4697e7c82239d868a041b74c9cb1","38265257","0xf3c394241c5765fb829181dcc1815bc73858314cdd630be2c8a7f0650788e0ba" +"0x5ce70e781060ef9dbf8482533d81f5d9c238e589986f14ceb63663eecb695f9e","38265281","0xf3c394241c5765fb829181dcc1815bc73858314cdd630be2c8a7f0650788e0ba" +"0xb966dae77d50e3e4e583c97cf8f660c33065c4089cadcd599adeaabbee418bff","38282365","0x123710d6c151b51425b4b092d5dc904b03d73fec442a77519c7fe7a7e3e5fe1d" +"0x5bd365d73bc6f698f1e55b5bcb788ecd95417ada779ab521e0b4496b62b83f6f","38287137","0xe459cbd111f33021694aa0302c6c5ca86d2347257123174759707d3810ebddfa" +"0xaf317b86d0aa9c58ac0ee4958d93daa4ccd5bc284a932d041444fbeca7a8bf04","38289328","0xa81c20e2a76b58087e59e872205fecdaadb3bad2a4f4b7619e943627ab443eb7" +"0x1fb4a65d303b0afc75589590f90003e4b90af8cc34ce3c91a220432f42f22768","38289335",NULL +"0x332aa0e90cc8fe713292eb42edbb8f20ab3b4a419fddd06216081eac4661f5ec","38291274","0xe459cbd111f33021694aa0302c6c5ca86d2347257123174759707d3810ebddfa" +"0xa0808a1e4e3b078793de733c527d76d51b872b937bb019190ef86c6fbbe10d7f","38294284","0xb0a2f04367fa45b2491b3124744ebc460421a47b834e157864797e835478e5bd" +"0xd4d11cebe7eb724509db1d94cb60b1c4071de95f4e3cf5c047c2da10fa2c2e05","38306219",NULL +"0x01c9d97927a7476108ae174644fe4dd57c43bd7a8a1ee47f0f7c29056ef19f26","38312671","0x18a303bbb55d9f47a8b36eb291be335c3a03f555fa2780950085c3dae35d8d43" +"0x1c3fd580f88b8b72d061588a040522643fd32e69dc7c348c38e6bf0a412c2b2c","38324280","0x7035601b19e5797ca0a893b3e12aa2495aaf8c4e727b83f0aa5d2ffeea749f31" +"0x7cb1db13c50e9aaadce6de10b19f1fc6ecbb8fa332f4347a1477555c25e3a1b1","38324306","0x7035601b19e5797ca0a893b3e12aa2495aaf8c4e727b83f0aa5d2ffeea749f31" +"0x261f25a77aa528cd45a28b9324b20eb802b5c14911fc306daea828e90f8d9e4b","38324327","0x7035601b19e5797ca0a893b3e12aa2495aaf8c4e727b83f0aa5d2ffeea749f31" +"0x391aa2a2b2e6e2c8e4fe4a4727609c0abe955cb0cc52b571486f149dbd617d19","38347599","0x82d4d5166308a8ecea6c81d6e8ea7b8dcc51e957309285e42753d1322af0559a" +"0x89091c676387c13783d56bb121008fd5f34687bbcec54cea5241e449d644b7ba","38348462","0x82d4d5166308a8ecea6c81d6e8ea7b8dcc51e957309285e42753d1322af0559a" +"0x5bb4c1c306543dcc94df83a4f1b267e24fb71b205fd2a475d6ba51647eb1c62f","38348506","0x82d4d5166308a8ecea6c81d6e8ea7b8dcc51e957309285e42753d1322af0559a" +"0xa2bff87913333bd263df2bc850b06c3d6231e98456394d805f79d1b773762ee7","38348530","0x82d4d5166308a8ecea6c81d6e8ea7b8dcc51e957309285e42753d1322af0559a" +"0x499abac674f1bf5af3a69d6836757be1807867a4a4f4e8f0b3fcc761891011bc","38357136","0x6f71cc14f4b3805065e69425368a3804c6d38343fb22387af7192931b93e8231" +"0x22e2ee7471fa904cb5357625761e50a50e3732e9477356f652766eee8ed586ef","38357155","0x6f71cc14f4b3805065e69425368a3804c6d38343fb22387af7192931b93e8231" +"0x19da063e09ab896a3b4379c503c87f89e2dab8323b0761101c47fed911f3d347","38364774","0xcd93d9ea4de516a525b33bb6fca2b302ed10517843e6651a8444c5a05bfcb98b" +"0xc0c11e9aa6568ddb87f52339450dc36a0965fa7bfcf62dbf1af8ead168d12a3e","38367456","0x075c92f07386d9ad72c44a87a07a7524b0d5bd59b03f32b7a841c2b36087d56d" +"0x4d70dc774cb80e6693632c59f41bc5842a1118639f0d42331df508fcf1cf8855","38367464","0x075c92f07386d9ad72c44a87a07a7524b0d5bd59b03f32b7a841c2b36087d56d" +"0xc2a092e3fa0588f709ae0e58f32374692ad02ece3de9807a60534a3fd2db47a4","38369497","0x53e43ac35fd1926e62c2df39a5d9e356a913ee638f1ddb15f50680d24512da5e" +"0x8ad0a50eed975e2bfc6c7756c66520346ead40d6230d444969cfcee0aafc4fa1","38369505",NULL +"0x082b73b85f78e2e9d91a45433d030d66a77110de1c0462d530ea75ef6e5f5239","38369827","0x5db05e595ec5f7b898aa2684be233671ba32c5a2c7ff90fe362c7b2379f35888" +"0xc423384f9fe2b654503d2d6ab89e1929ef09cdd47d383127f149d444c2591687","38371800","0xffa2335d85d531b650192ce820fb45761ed3eae704ebfd467ee29c5cc5267322" +"0x6164f6f8b70dc4593e454dc24e1c20411bd65706594724e9cdad18b1bad87f75","38372654","0x5d8f3b9d9e85afc3ddda071226ace4edff021c32bb2458ac5fb097400bad6f4e" +"0xaa6d09068a2c18a9aa7e6633c27ff7d2ecc0f63c4fdca16e534313039f008beb","38376335","0x6796bd551827d928f57a0a4586e2168ecf1baeeaefe25ada9d22e8d2bb577e95" +"0x1ed72a3327eff78c9522ac88518d50290d44f5780195c6705a4a02e606ac25a2","38378542","0xafc1df6060aaa0012f29f7d3fa82e1ac64e7d4dc12fe14c20f42028614a1b820" +"0x7f5b3e10c7c6fb3bf01c919d36512542ece193a433e10408d3c94ba15384b112","38378550","0xafc1df6060aaa0012f29f7d3fa82e1ac64e7d4dc12fe14c20f42028614a1b820" +"0x1dfcead792bc0b716f0b3a7669b3d3da89b36a278452f9ac06c58297561fdf14","38379081",NULL +"0x70375932316a49adfdb9e44d061be571169946e88e9602d367cca6e57e5ed7a6","38379086",NULL +"0x992f305121a872b9135c7aeb9be2b962b425182acc3f442d4d17e0932758d6f9","38379090",NULL +"0x40b653de84b6bb91f7c92a46b43dabe78888d8f6bbb8179a929a76a1f4537d3c","38394344",NULL +"0x40b653de84b6bb91f7c92a46b43dabe78888d8f6bbb8179a929a76a1f4537d3c","38394344","0xffa2335d85d531b650192ce820fb45761ed3eae704ebfd467ee29c5cc5267322" +"0xee65d4c23b0303916077b9919c08de8aab0d5389cc294d0cee3c802a69bc240b","38400727","0x898367ba277db138c88fbd128dbdacefb3cb42257ffb61b0af674ba4ee6ac839" +"0x49bc29d516adae1fa12852c2d0b69cd71f7ca5bb7b3720d923b02ff90c429776","38400734",NULL +"0x8afc3d9034f6165260782ecdccc1f7d0bf12eae8c71b60a576e1c5a1ddad3317","38403587","0x4bda402740ec0e8a8381e6dbef0c28a8fb7807ba3e9a91b7fa7d933a9dfde1b8" +"0xc695cd7048cb82a1cf30fc702d847f9f38db144914dfd25d2c3b552903ee4a70","38403597","0x4bda402740ec0e8a8381e6dbef0c28a8fb7807ba3e9a91b7fa7d933a9dfde1b8" +"0xef859d149cfbbb8911c258dd96521b570062a4273610fad385a274d1041629d4","38413106","0x567da71af7a478d8204aeca7619230e8e059768cc9b75c52abf636f8259bd572" +"0xd9be7e508b19bbcfd39ef1ea8e0576fd6196a4a7e31d0bcb1997bfdc976138ec","38413114","0x567da71af7a478d8204aeca7619230e8e059768cc9b75c52abf636f8259bd572" +"0x6eff5e8826b3408bf0d8aaf6e892b66f7ff922c4b5e423af49488c39ff28f909","38413437","0x61751b94e724563ff4459328a7640499f3a5db5cdd326bf2dbf4b9954c474c29" +"0xf46836a6c04e5ed33fc6dcbbff4d870b11e6461b45351fe5717204e1378d4bc5","38413446","0x61751b94e724563ff4459328a7640499f3a5db5cdd326bf2dbf4b9954c474c29" +"0xb3667c798beee7b6153bfc878b7f1437748c85c5a8c5f2253022f7757c2c261d","38413455","0x61751b94e724563ff4459328a7640499f3a5db5cdd326bf2dbf4b9954c474c29" +"0x9dd51de4669e9184acd6767e225e3076b46905469234d87928f10c7cc1fd64dc","38413541","0x1234db101a9019660dd3dc6f3256955e822b07d928fdb2c479258ce99b407572" +"0x302a01d01caecfef0c61beaf5bf12b69c282037d383f609274a3aec8183c014b","38413551","0x1234db101a9019660dd3dc6f3256955e822b07d928fdb2c479258ce99b407572" +"0xd696f52142c135d8c728237203fc16880a2c9825093577066f9f8fc4d17d6002","38413810","0xcd89b238d943911a63d3aeb5839e73b71e9ee3db7fcafcb40a5a4522326781d5" +"0x211308d0b74ae39c4a1cb6bc8aaf775deb930a7c3b38cba9c1f2bdbc936e760f","38424129","0x59318103330861b275188b887b78b44979097ebc5f0d77691838b0f7a64d8466" +"0x7af7382cfbc50b281d5665ab9e443af67fb6bce1c15b86b9d7595faf94bc8ff1","38427638","0xf59f0636a2dd1fcecbb889b3a772b1b897e8ea55e35b298b0ab257d9640b7ab3" +"0x88cd103f453cd69814734c9b652859f02a8cbb93691a62d5c1dc4aa00aa659aa","38427707","0xf59f0636a2dd1fcecbb889b3a772b1b897e8ea55e35b298b0ab257d9640b7ab3" +"0x6c4eb9a592657819b9a826bcf77cea550531e725c58fdbdcb10f95745d43e4d5","38428527","0xb2664c9fb6d458a5d1cf63e32ab06677008eb8bc4ac16dd9a4aa04c67f206953" +"0x29203629b36eb4a6987c594bfed5bf6adfe7792ebd3caca6a2f7e2b9022573f0","38428608","0x730768a5f78c741265baae1c8902a34c665d8fd444ef2c4e9e26160dba855551" +"0x3a0f2d24e460a396a45a5ba379f65e5be45e7d7801c266d677436bbd95e6ae67","38428617","0x730768a5f78c741265baae1c8902a34c665d8fd444ef2c4e9e26160dba855551" +"0x25e0eccd484c010e2a4207ec996a471f01bd72b275a44744cacb7066d2c5fff7","38430067","0x4b343d7a531ae3e9d90f67c6050cc620024b23196fabd0ce074ef93871a41484" +"0xef21a4dec80145695448e875a4f301c2fdf3b20af9c99300dd0dea9fe8f032d9","38430076","0x4b343d7a531ae3e9d90f67c6050cc620024b23196fabd0ce074ef93871a41484" +"0x9769beeaa57802979d8607656326738d1b214a4d612a5d9f2e676c1a23909f72","38430586","0x096ebc87a3673e00c99aad1beea9defda6673ed633c353add5450f1cc75aa195" +"0x05a806f47a263063d38bf73264e8c0569daadd58793a8d62bef37dcf08293430","38430596","0x096ebc87a3673e00c99aad1beea9defda6673ed633c353add5450f1cc75aa195" +"0xc1ab3ff5ade6492bb6f0cb8924fed8c285e854dc9472e6df962dd29471af9b0d","38440757","0xa9b2606c5734e9d5be733d31befd675f01af31e0145669eceedab8f33cf8744b" +"0x47cc2a7999efc42ed7b8332fd92ea8f1e450996b743fd2ccc7c1515c0a8f3652","38440811","0xa9b2606c5734e9d5be733d31befd675f01af31e0145669eceedab8f33cf8744b" +"0x66f261a2eba0e8b14fbf05cfe7e7fef11b0138bffff65981fc0bf824ff4a0e7f","38445722","0x375c0aa1344e24920a770472b2b5efbb5c1d0efc1c4077a5e5c501e9bf441543" +"0xfc6fd50e8a0f0500c1d90798fa4e00a32d6c383040ce821859032c570ee0d648","38445743","0x375c0aa1344e24920a770472b2b5efbb5c1d0efc1c4077a5e5c501e9bf441543" +"0xa4e9509dd2f62685a375c7a834c3c6afa69b42642024bcda26f19cc64f24418b","38452307","0x0c2ca65a8e695025110953e5dd6ff914b3b763d865f068acbdbbe4439bb5b8ce" +"0x324905bc3c971f2b929269986a1ce05235d379267f81f895bdf9018ad6f1be30","38460114","0x71fbf36f196aa4cb9c07d8fa812a76d3b7eb7ef82c46d3eb8842ce369d606ee5" +"0x4b7f30084e5b7df57275eca259b54f5900450ebb24e9e5e9c544525aac29ad93","38460123","0x71fbf36f196aa4cb9c07d8fa812a76d3b7eb7ef82c46d3eb8842ce369d606ee5" +"0xd4b3cdcbc681f59565a6ec60bcd804fcc9419a6391ce1b7a511d4420d3d865ee","38460142","0x71fbf36f196aa4cb9c07d8fa812a76d3b7eb7ef82c46d3eb8842ce369d606ee5" +"0x59bf3841414c0b75001507e7e762781965d868f08880f1f771ae524a3630d250","38466092","0xb37ac0d49c1f7f726eb8b336579af81611108859cc38d3389236e78185f83911" +"0x7f06716855458381155c9f65957bb2980ae2c09d268aa284f1f06d976581166c","38468763","0x7e41288f90849ff9cb3c06b7760f66ff4f199aaddc1f677af476388418e6fb72" +"0x0058956c5e54d35a302afd48524f722a0f7a53fac45b65eb508f75467c3b7b6b","38468798","0x7e41288f90849ff9cb3c06b7760f66ff4f199aaddc1f677af476388418e6fb72" +"0x49e10091280a3efb4b1126cb669f8cc3801eaf93a0dd20950ba9f694c2283385","38471863",NULL +"0x138817b726e3e91e4179c0b03f84ed2d491cf76bcb5e5c27a70d0793d1378d0c","38471870",NULL +"0x8d54c5380d8b980cd0da23cf39a2fe3b73c891a9b2d94d979c7c7aaaea3d814a","38471930",NULL +"0x061f56ee87e81ab78ee212ae3ff91a4c32fba6bc32981755c14e80896ba56375","38471939","0x09ff255b61aa6bb6f7234fa249a32d34f1e434715894a370ec72880d0fbc4e9b" +"0x6adb1396f88b1e2eac1cbd36e33cc6c426329f0c490af56f8cb182b03fbac079","38473816","0xa1322bfb6298fbf435d2ee03fe86fd9489878f10008d6dab888c308260e7812d" +"0x9408c2f03795b1da98a97cf7e69cf6409213b4a2a92b17f96ba316179c2d694d","38480097",NULL +"0x9dfb641d6d91268453e6aa0499a0c56516b65ff853a2a864196b24a3f511b879","38480923","0xdf40e5f862554f61bf947930c895a4e27c0a8a0942d983efa0454d7acff04f34" +"0xbcc711d1d69573d7db117364bf84e50969dfebb99869c4cc712a95b85e688c31","38483028","0x081786edbd072c1348f04958fb5406f3194e3a9f064934f0f0760076fb3e4318" +"0xb4e6254d29bb3e97bc1a02622670ec27b50c245dcb9f15611e654a330ca762c1","38485576",NULL +"0x44d4b3baf25f2ee57e48b611008c4d00c8d0a31c0114768a9439af1ce60e97ae","38489599","0x4546b21588db1d59a6bf973e17635b3b5feed942a5fc26a0ec371027fd27e8a7" +"0x504226124958e8076b3b51aa59404218ee90b311e5b8749b2d342cac00223cda","38493739","0xb9dd488378ff5bf6e0ed45314df775222a4ccff5cff838b1cdc5e7a50a1030e7" +"0xf380588e48611dd2f2968f63e93b3d8a7d7edac0d5d8ef215537136f5d9e5bef","38507039","0x0932e8f66e7170ca5c586cf9471eca9dd7662125b1b9a69d7a1b24a35712e0ea" +"0xc7fdc8c43428be52098e322f487630744aa1d81d502fa52c7d4fb79b48ac8813","38509123","0x0932e8f66e7170ca5c586cf9471eca9dd7662125b1b9a69d7a1b24a35712e0ea" +"0x37c64d44e1f7e00ccffc48df5920229d7db42c23d3d86ca3e4f77725db81d234","38511658","0xdf40e5f862554f61bf947930c895a4e27c0a8a0942d983efa0454d7acff04f34" +"0xd93cc51c3384fc1bf7d803264384ad2299ad2947977cfeb3288dc19e99c52101","38515457",NULL +"0x801636527d79fbfbd82b7bf97cdf3d1bc3237a2f7f729c9bbc5f7cd59f7cfc13","38515656","0xbe274aefece1e1e1f594f34d9f225d97433a4e012c8579375c8373862592779a" +"0x8ea6d07ea97bb3179793d62790a5effb5874ccb7aef188079859bfdbbef2ae8e","38515671","0xbe274aefece1e1e1f594f34d9f225d97433a4e012c8579375c8373862592779a" +"0xd042ffa2bd5ce9435c0f9234e4b202509ddb97a389a0982b37869557b46d9efd","38515708","0xbe274aefece1e1e1f594f34d9f225d97433a4e012c8579375c8373862592779a" +"0x010c8e8211543f478faf00cf3b24f5a0f7c4440f14a389178408a4c3747d6d83","38515723","0xbe274aefece1e1e1f594f34d9f225d97433a4e012c8579375c8373862592779a" +"0x38d18f79768017be3156141f0e876d650c4a5de8c4a282c5393145ee49870326","38534866","0x289bc509ecbc9be9cbb4fca72889cca8a36633ffa3e247676a5b003f97568450" +"0xa38a59c82f14b0828ce123cfeb4baeaeac4d8bddfdd430fdf1cb6cc8bb49ee29","38546056","0x4a7e5427ead8a128a3c6ee9c12381861451d0db2d04f620e18fc6549fb42da9e" +"0x3285fbc8893f1496d38fffaf6c65d0c12e3f1e7c21fe0e9e2791f40e45e12d34","38559135","0xfd83706530cf32f62e78fa2a8d15e81172a686cd5d28f656d18d0d48d329a533" +"0xd759c63281f2c6f6b58af54c39540b6e18d04d577c5c3073b61dacdc2878c6cf","38559145","0xfd83706530cf32f62e78fa2a8d15e81172a686cd5d28f656d18d0d48d329a533" +"0xd759c63281f2c6f6b58af54c39540b6e18d04d577c5c3073b61dacdc2878c6cf","38559145",NULL +"0x76119f58c7992975bbb8c6cdb0c0cf9483dc6c07233c1370b4d2891864be9e5d","38561217",NULL +"0x71883dfd69b01d4b719ab6fc574e7619716ff318f72217def53402ab4efa6315","38561258",NULL +"0xe6e87f0d79ea2921cef31b2b2c488131ee65b885b3c997e5ac9af4ea8fe46d23","38574427","0xb6084a1a5f115ef694173e9cfa2a3e088d4df7aa05724038c66b72b37eae0ac5" +"0x1e99d2cf35ff03316b43d9b21757fe3d4a96f83e906a19d13e9e9ce280f1bea4","38574487","0xac7fcf73d43e7ba7a44d96de7dd8b74f527cba271da37968fe32acefa20b4cbd" +"0xbaa8bac560e9c657b18fa1978be391e8dc1a88cc85374542a4ea7686d3ea63a8","38580654","0x96b2f8c38f2c6b722927935f8f6adfa384075151d638a1555845f598010f1651" +"0x7f102ec053d7f0018a0ff51248fc8573395029e2c0201bcb231df511894faf9c","38580663",NULL +"0x587359abf89e855131ac9b76f3d621002a2c6300de76d879ee48a40194d22bb6","38589044","0x8c346f6a404234229ce6098f494806e65c3585640cdd3a898e3fb7c2980d7a8b" +"0xeba172d7357aed90af704a41ca32f940bbd90afd15888910b39e7df45cb92ec9","38589268","0xffa2335d85d531b650192ce820fb45761ed3eae704ebfd467ee29c5cc5267322" +"0xc1005262662ac7681100ce44e80370b9fe1549b30690d9879214c074dbdf0f96","38589389","0x8c346f6a404234229ce6098f494806e65c3585640cdd3a898e3fb7c2980d7a8b" +"0xddfc8702c7e42cdb26b9d03ebca61618a92a104ff07eebb2bc66534e50ba8969","38590066","0xa1322bfb6298fbf435d2ee03fe86fd9489878f10008d6dab888c308260e7812d" +"0x6ca36acaf338141f89db876a2efea58bd5d20322465b40eee2b05c76810ec861","38611620",NULL +"0xd42a01cfcd09c57c165538089e2bbdd9afc33311112ce7dd971b1ced1e786171","38623205","0x0932e8f66e7170ca5c586cf9471eca9dd7662125b1b9a69d7a1b24a35712e0ea" +"0xf5ce36ba6470e6039787c3a11ad4c22551d9f0ff58660b34c81c703518eae65d","38626575",NULL +"0x72045bb789ec04596c789f727125c8c6860efae8f5d5642868bffd7d0c53da07","38660376",NULL +"0x59c8466fc25b738ee752353de661a0fdd60876d1822eeba2318f4274b5d0f6c4","38667763",NULL +"0xc1ec7fa5c3bf20800cd50331bdc70f390d13c40e078b806d824170b3e81d4193","38677607",NULL +"0xe51c7ae75622578ada877f64a26406c85854993b7c790252126e19b78646e7bb","38678317","0xee5e29d7d63b57ba9859a1716aaa3889b18cf6e27548b289ec51d587c6cfca7d" +"0x6517f8003cbf2226d9115672c00793f9131238dcf5a7adb7a3f4f9b3482c6966","38678321",NULL +"0xece3b43614734d81cb9c53c2452c2621b57690c9d4b7ac451de4913639162e2c","38691998",NULL +"0x7f2e5bee788c9c8561547b85ed129566948c327911198a5d797cff7f6ea53275","38692038",NULL +"0x54578950e8674689d9bcd9704351bd1b8d8d673be47f11a4cffffbe50a7682c0","38692308","0x9259ae4fd9e4e8e52a9f5cf91536b7406a906763681e89db854397058c9b39a4" +"0x8e13fa77274b0b5d1f9625a169af3512be7f87a053ef083fbfe38d320441e074","38692318","0x9259ae4fd9e4e8e52a9f5cf91536b7406a906763681e89db854397058c9b39a4" +"0xdfc7737aa1b8550f69eb66a82ae9cd738af7b1e413cfa7070d643a02bfdde895","38697755","0xa3c1c0cd5b17a9fc8e2cbfba350213caf55df38a95b71ac1671f23300141ca8d" +"0x26ce3ab9ff4edccfd6959d4d06a4a55f26c0378ed513360b336887f8aa34209b","38697759",NULL +"0x3741cb56539d7b564d60cd9e0f27c4ebcc80d55d9312590f7a0ed67fbe1cf625","38744533","0xad27adfd0662c7331b6210d88e09b66d6510502d4b5de93ba2f59d466227d497" +"0xbd2620b638adb9552d024f860ca3302f350b7d8345e7740c5bee2e2986ae2edf","38744544","0xad27adfd0662c7331b6210d88e09b66d6510502d4b5de93ba2f59d466227d497" +"0x201ab0ed52a508e187cd15acff288681a8382fce17401131f08c32fdebd9ea20","38751886",NULL +"0x19b64494677f69b9e321ffc24d7eec1cc3b5606b30a487097265014be2619945","38751890",NULL +"0xfe3fe2b268a46fbe249789613b99b29f17e28242af6e2211ca896be43a179862","38762800","0xb9428bc6a223809cd8b3dd4027a87c0fb8e68ad2940db1837c8e7b994c637d88" +"0xc8a2e9637515519aee1e9354f1b9649cfab25484affc4bbdd74c90748fc5856b","38762848","0xaf638d574a8691175227496ff74ed64a660feb4b4c8b2bd5a992a82ac820c6af" +"0xee154ccd3c3f8221240907dc86424112c291a7f8ca739eabbe4c0a38a2c98138","38762915","0xaf638d574a8691175227496ff74ed64a660feb4b4c8b2bd5a992a82ac820c6af" +"0x0c05c9219ad89756c35d281fd215b03453b813afd606e1f6b2f155a5c5b9df6e","38762981",NULL +"0x975314bc925292c478943247e443df8839659d2800be5c73e588bb7bca51805a","38773825","0xcdc603407f20c256c78bec19ca7670c64727b3d8f9e7693e3a4239db99cfaa21" +"0xea6b00ee9574c3fbd847bfabca5c4dee91161c103d102d576b0e469296f75260","38809060",NULL +"0x77a0df251acca39d64dd8e9e384da7f5f32fb502374a00889b2b252bd8eb62e5","38824847",NULL +"0x500e2c91687ccada10566ecc45340c7d999b2bda4606df9093a06a1613e4b471","38833500","0xc1159ee9fe2cd430dfac09a5477c172e151622439139623a63a18e4f8ee73eed" +"0x4e228a285f3bea7da9c2babf2e6121789c4cbf125446df33278272bc981478ec","38833514","0xc1159ee9fe2cd430dfac09a5477c172e151622439139623a63a18e4f8ee73eed" +"0xa5c3f8fc8262ae93cd8a3bfc8daf2ffbf8c64147b77c7bbfbb66cd2ef278dcf4","38842254",NULL +"0xca41ed34b61919c4cec9da79a2d4471b73ad6e1ec7913043b41c105120dda9c5","38844687",NULL +"0x6c6926466bf9964ac31dfae4cd6da70c8ee4cfa8106ab97309582f3616173900","38844838",NULL +"0x8e35c7c29426624ada0873c95c5dbc037a5d5aa8ab87c8147e5d87ffe61cc8b6","38854934","0x037fba161713705424f6222bc776019ce41304d8ca1fe678deba5e0c478fbf63" +"0x756ad99b8636fcbdfb4052d1a312db8647e296f3a1de44a5b3618d371ef7c9e7","38854939","0x037fba161713705424f6222bc776019ce41304d8ca1fe678deba5e0c478fbf63" +"0x1ac67be9310252dfbbe2f179e84837894e327e533816e3b38713e2c5b54289ba","38854955","0x037fba161713705424f6222bc776019ce41304d8ca1fe678deba5e0c478fbf63" +"0xc1f05448da7d8a2ee0c63cf6361ecabd49cc287af86fb3bbd3d99780ec2e04d5","38854965","0x037fba161713705424f6222bc776019ce41304d8ca1fe678deba5e0c478fbf63" +"0xd16c5c9c636445b7ebc952856c0c278606ef659fdf30a897930cfa654835ca25","38854974","0x037fba161713705424f6222bc776019ce41304d8ca1fe678deba5e0c478fbf63" +"0xbb102ec92f15b74a81640a8000af207dc905ffb3d634fc5f9150f2ae4724e060","38854983","0x037fba161713705424f6222bc776019ce41304d8ca1fe678deba5e0c478fbf63" +"0x6b3995fb87246710219c5bd199859531454e5b9a77b90b34ea78bb8e3dd79ff5","38855004","0x037fba161713705424f6222bc776019ce41304d8ca1fe678deba5e0c478fbf63" +"0xd4f359fb015fe9ccf1fd60eacd7261e7464a393b196c03dac127a9c7c9fd478e","38855012","0x037fba161713705424f6222bc776019ce41304d8ca1fe678deba5e0c478fbf63" +"0x09718131eccac43287c25f0b059d9a4821337a63550c277939caafe968fdee4c","38855023","0x037fba161713705424f6222bc776019ce41304d8ca1fe678deba5e0c478fbf63" +"0x1801d2e00bc193487ef8e699acf3e77bc24827ed2cbe4fbec03c2c0e7cbb5e71","38865480","0x39b1c1810469e6798526201c12ac4c51c617eac0bf8e07d0f25bcdde6c3de16d" +"0xde9b65bcba53625b89e04062b570eb341f1d545c78b2847284507a75cb8c2d1d","38865497","0x39b1c1810469e6798526201c12ac4c51c617eac0bf8e07d0f25bcdde6c3de16d" +"0x5901703686e6d17fa2a4314f751f56e65e4abe3fab7307df0088772b69207d7c","38867157","0x4bab2808f19279b5a9da6aee021952e715806aba8da22a7b3ffdd68318c305cf" +"0x38b7ea21e7d291636b4087f875cadfd7903c2d337c71ef830f190ecf5b2e0b96","38867172","0x4bab2808f19279b5a9da6aee021952e715806aba8da22a7b3ffdd68318c305cf" +"0xb6ccf2eb311440f173b3b6a2d5d66f6bc278f2537426e05069dfcc58e0f091fa","38884751",NULL +"0x08cf953e7c5523f86cd00b3d3d087435def08d35e6add7577bb8ecf0de7f9f79","38884761",NULL +"0xe2c09c5387420e07c2142e7e232df291aee512c748fcf804f267b93313350e92","38885107","0x081786edbd072c1348f04958fb5406f3194e3a9f064934f0f0760076fb3e4318" +"0x594d4edaec1e9e0742239cdcf8c76481435a9bd00b653fd2ef9596f96def93c5","38885114","0x081786edbd072c1348f04958fb5406f3194e3a9f064934f0f0760076fb3e4318" +"0x1de7afa360b7171a9764c9cecaeb5ce9c1d0381e5e382401be140413fbeefcba","38888820",NULL +"0xbc2ff67e507aab900d3cdcbfff5f11f033720f2c9b5e2bde771ce4ec4d6e3b07","38890989","0xf0fec9e96c52edd34e089f02b3c56edbaf137c876ab638f5976c4b283d25862f" +"0xf1af1251f30e424faeb5206372b59ca0ad8f9daf71595d275ce0fa1aed4c1f3b","38903088",NULL +"0x358691b8de4bedc00b639dd65c6a647edbab940f90798f260a14a56019a53f61","38908434",NULL +"0xa3429cf46c514e5aa9621bbfb161ecec850e348cc457c66f074fd8fe657cf466","38913423",NULL +"0xb8c4c2d84d00c8c8af24c491e55de1a0be357c2f3de99f389779357fd3b79606","38913437",NULL +"0x72bd15a7d52088cb89c8a058956f0f05de23bc96ec017b5d89602d7b22d33c5c","38917519",NULL +"0xc5104dd57911c0e09e15cc8b40042193f233d3c6e08963ab1928e6d03e79a285","38920404",NULL +"0x5b7ccf046e42c68085ddd9b7048b9be146502154b125d56a1e0159df744da9c7","38920411",NULL +"0x0c829bf67a6b59b757d02a0effc30fa4a462eb5aa17c2469db81e7d8362e899b","38928475","0x8c346f6a404234229ce6098f494806e65c3585640cdd3a898e3fb7c2980d7a8b" +"0x68b1f2038629a34e95c7a18b8a43c28ac4917f8c710dd290accedd93100e0037","38933661",NULL +"0x748042bab91a999a36a3b7c97e7b14e8f466c4e780ac4fcd4571ca26a21325b1","38933682","0x8ba1ecfc255fcdae221c6afee5ae2f64d71ba4cf9cef5938a3693166164b773f" +"0x0d25e25f7fc1576a92a8c102d4bd70b17c78c5d8d6cb5a2e01c1e593a94f9b22","38940049",NULL +"0xbc910446c002b902b6898ceed3b47f17c8e8fb06f77640a3cff4305e1e25db98","38940071",NULL +"0x88f9e407983029a0eb8d1a73031f592ce8cf5f905c30b51ecb3a4d778dcf2274","38943918",NULL +"0xa7f93f737cde97c31493c9791ebbd9aaba0c284d1ff7295a68e30580a55ee0ec","38943923",NULL +"0x12587567f2928e3b746c9d6c87f1db160d511d4376784ff49e5e782967cafb1d","38946675","0x1f28811338c5a7f8b3c40c3cd6af8bd2eebb272313b16ddae1471e6012e99f0a" +"0x07649903db42ef8803384084d96b189a36f5308101079f3d92c95cfbd2784a4d","38946684","0x1f28811338c5a7f8b3c40c3cd6af8bd2eebb272313b16ddae1471e6012e99f0a" +"0x7b5676f8b43e690ed483566d047290067521a73ecbca17620c81465ee5d6d545","38946701","0x1f28811338c5a7f8b3c40c3cd6af8bd2eebb272313b16ddae1471e6012e99f0a" +"0x33f6e3b08ea1b443fd2a2f507efca0c701ab12623b7f6418e35530030d861dee","38946765",NULL +"0xd43acb315cb7f563e15f75a96ff8afb31257e766584c76d293310249dbf10a0b","38950092",NULL +"0x4220674f8b8d707328a26d2a41448b84ff0c74cdd20c65270916c6bf5b979c37","38950104",NULL +"0xb4ef9e814f5e9f1dd25d4b90e8b121e865cd3c56558cae719363165081da19eb","38961732",NULL +"0x81b30179316daea873bd23ee5640e65eb3b4ed4db3945fe11f6bf634980e28f7","38972915","0x97d0ea76491ff8cb6cd94399ee051eee022bd8deab8304bdc11a4b961330d014" +"0xa362cf35fba1196923214287d83dc94699faf761d0662fab624c28f5298cceb2","38972996","0x97d0ea76491ff8cb6cd94399ee051eee022bd8deab8304bdc11a4b961330d014" +"0xaccd3652457050900316fdba3f8b4214513fd6337ee7b10d1926415e24511ed0","38975757",NULL +"0x6459946bfaad619191d397365185b61d13e3b46fdb9aab9502196705b9df561c","39013388","0x6b9f22b82a33af665f30873e84f3c3a1e1e90427f7aa22e8f8e823c1f047e1b4" +"0x8344c08602a8c34f5c4bb49f398207ef9f33880b0d3d718ed10782ed9da816c2","39028298","0x77972fcba0156fb9a73c03e18bff55af3a5ef96fe871b6a26078feb96e97f12c" +"0xb0b770be7be1675ea725135c35541ccfa4c505845a669ed6c4c7eded7b45fc22","39028316","0x77972fcba0156fb9a73c03e18bff55af3a5ef96fe871b6a26078feb96e97f12c" +"0x2757a815b0a18af63663382495e96667093bc994c896859fd03f1108549dcd7f","39062456",NULL +"0x2fdef7f0081f1bf9782a3a22cfdb684c6c8a1b8948d16db8ebd9f2c5e528f259","39066219",NULL +"0xfa77d2f11497cf067fefee778fbfebcbb8bc23209aa8127bd2dfd37d7fd693b9","39068107",NULL +"0xdc81d0884b06e293228ea311def83d80fd48e99c4e104d966d0bf5fbaaadc5a8","39070547",NULL +"0xd905a68fe785a64c1a0cd09f5a2a5ee1c73bc3e031bd3470fad9285c5637f680","39070573",NULL +"0x011316fa42cb2c87c395727b5733f1e57cddbba8906721dc5646452c5ba39d01","39070582",NULL +"0x4980f130a1dd27cf41fc1930fc6bb503b330bc9cd3cdcaea19169fc2d9b416b1","39070590",NULL +"0xb82d46e3721c7f8994cd2c354f58ad2e7326b9affc29681fb2ebd627369715d1","39070597",NULL +"0x162e92555b4af8176efea2a7808211f0aead482e7d356dbb29137ec6c56bff9a","39070616",NULL +"0xe8b2d615e0fabf8b277545afe148f7a702da188c5d5c404660ed8ae031bf4969","39070624",NULL +"0xb77bc3141bade9e153156e395193e3734ccc6c9b5a517be4b82b8b0083494c77","39070630",NULL +"0x634dbe3869e8115ab136caa4e1ce7088cb6b6ba70ef54694dba540037381098e","39070636",NULL +"0x4c037687f8daa44b5a09e1e61981d3d35e3a9add69c33c0c58c83bd7e95b4801","39070644",NULL +"0x67f4363f3ec47015656df58d847d990e30e38477472fc6fdc4e301af07f485a3","39088757","0xf59f0636a2dd1fcecbb889b3a772b1b897e8ea55e35b298b0ab257d9640b7ab3" +"0x738036d3a2d381ececed46c98585465b0d04f6fa52a1e3c3c4187c558cca4952","39134142",NULL +"0xf3874860781f8fe637402d349b8bc9bdfa5372989740cd42c3a330684c8a433b","39134147",NULL +"0x50bb71cc3b30130a2e69f65ce308c34c9aea04ff8dcec376711e865f9c796389","39161652","0x8dc1da4bc798549061303b4b69ecb5762c9a6456e000ae0f298ca6113e1e0d7f" +"0x8e89e4d50b54f03a8eede359caa56d4c477894c2b8b5c09c988dfbb19dbfa70a","39167995","0x8dc1da4bc798549061303b4b69ecb5762c9a6456e000ae0f298ca6113e1e0d7f" +"0x2592fca2942c818fb1ffaadcb6906f3d0ceb3616677af074f7a878df30cd0db5","39181630","0xa1322bfb6298fbf435d2ee03fe86fd9489878f10008d6dab888c308260e7812d" +"0x9c5d83dab316a98656483076e794996edbcf8f6252f02d4488aa2c15dad9f80d","39181715","0xa1322bfb6298fbf435d2ee03fe86fd9489878f10008d6dab888c308260e7812d" +"0x870da3c93027790b420f45c2c2e2fabc0759036a4c2cd5d15d6d0c46804e5395","39182748","0x4546b21588db1d59a6bf973e17635b3b5feed942a5fc26a0ec371027fd27e8a7" +"0x5f80ff02436711ec1b055b2e92c189072246d6c470bc48f187c9dd9ebd3c26a2","39183712","0x1c482b6d5144db583f0194d0568d98fa922ead94b169e064a0cbb6092d325bd9" +"0xf15e8f5ab3324ed9cd3ecc4306b48e9e66e0f54bd647027681ffe0a0be2f3f86","39183722","0x1c482b6d5144db583f0194d0568d98fa922ead94b169e064a0cbb6092d325bd9" +"0x3aaabe8fade05ed4a6024b7fe70e3d3fe15c1f2185776f2a5fae51214d7bd50f","39183730",NULL +"0x195c5b08425a1843d1b35e740bc596e322891ff96a6658dc5527326a1f93a51d","39183736",NULL +"0x02d8da04b94221e793cf9b2cdc2957f5b65944215ced5513cf3c2b3b49cdc0ca","39183755","0x1c482b6d5144db583f0194d0568d98fa922ead94b169e064a0cbb6092d325bd9" +"0xacefa85182de541b4a3d3276776a89e8c60c4f88c99bdd1d715f911358833464","39183761","0x1c482b6d5144db583f0194d0568d98fa922ead94b169e064a0cbb6092d325bd9" +"0x07d4c67dd6b24c55a5c9759e033ef8a2053afcde3ba4cb709aad86de06dee304","39183807","0x1c482b6d5144db583f0194d0568d98fa922ead94b169e064a0cbb6092d325bd9" +"0x0f5e9326adfa2ec4cca25fd365548336a4b91087e173c56d8a269f0dd2cb4973","39187012","0x4546b21588db1d59a6bf973e17635b3b5feed942a5fc26a0ec371027fd27e8a7" +"0x48efaeba72fb297f55bde93727af2aea9b537c9a1f9d7ef92978bd42167f0503","39198176","0xb9dd488378ff5bf6e0ed45314df775222a4ccff5cff838b1cdc5e7a50a1030e7" +"0x41a566c53ab5a4e28d107baead48f8f99e7f2042cb23eea3c1cda4810b11e00f","39219686",NULL +"0x63aaae07431671180c94645a9457fdc0abe0df7076326afd8fdd9e53963d1400","39249502","0x1c482b6d5144db583f0194d0568d98fa922ead94b169e064a0cbb6092d325bd9" +"0x77704e35f6cff78a72811d95f40502c7ef7275eda9283a38b4107f88253d2337","39285622",NULL +"0x0b71ffbdf307bd0a7dad772cc8921bf1be2f4a08f3fef1e88256fc678f7a1c6a","39285672",NULL +"0x6d4d43931a0b7b7983a5cbe40c6e1df4edb5d1d3ec8d46b70835a0ab230b0d3e","39287774","0x8bb4029c31b71a3a61ad145ecfee77bdaece0e4561ecfb0a940183f539753562" +"0x9e6afe5c3b31123f7d589d95f9e423d108e2664430016a40b4e8bfea7928b3fe","39287781",NULL +"0xd07469cc8a96b60e25a1e8491ebf361759b6b7385cc919d5b2c684e536a38256","39301220","0xbc7bacc63bce84fac4ac7f20351464a1fddf23a7f6d1156a8d8d1c37fcec559a" +"0x5f8f2a98f9cb74a6b1884572b3f521cecac47a0e018f74e4b2dc8f0ec959f186","39314859","0xa9b2606c5734e9d5be733d31befd675f01af31e0145669eceedab8f33cf8744b" +"0x71416eb642066342e93c80a2af136b1ea6b5882a84ff752463ce37d0435a4a51","39315545","0xa9b2606c5734e9d5be733d31befd675f01af31e0145669eceedab8f33cf8744b" +"0x9c21862011c3ff11742ee083639d555df51965fa0264f0da8cea799f2e7c848b","39315573","0xa9b2606c5734e9d5be733d31befd675f01af31e0145669eceedab8f33cf8744b" +"0xe9a57ae049955a1ab8a93a5cbe7b815d4dafa111446db5d130cee317d30f6032","39319083",NULL +"0xee55ff8e8bef48a66f6b6e34d5873be29727df60ece42a89e4ccc5806207a7ae","39323975",NULL +"0x283e7d3b59f9fac1878eb4cf816b3933f15e45bbca62f2dffd4d99572961daa3","39323988","0xa9b2606c5734e9d5be733d31befd675f01af31e0145669eceedab8f33cf8744b" +"0x9857ccfc180ffcbe8803d6eb317091f9132ff62c41c21b2830cf61ca92601f22","39351804",NULL +"0xaf6a911c6bbf3146c0b8cf24dcfe05ef44d2420cd56ccbf7ebc421f4a5f2f1ec","39367829","0x92508f4fbe3a9f81388433c8db42d5c67d3cf0e611445acc8770600f359da691" +"0xe211a692dd2ce92d617334fd2a992bd292ac4256e07985a320c125ba1870a65f","39367852","0x92508f4fbe3a9f81388433c8db42d5c67d3cf0e611445acc8770600f359da691" +"0xb1d9d3ba219d3c8abfbd8666aafbdd48636e8667656d35deb69a43cfcbf51cfb","39388315","0xe0a76001b92063d4969531f49a27f8ef6e90594c4d75766379fb7c53b8d31f8c" +"0x9675ba5f5ce2ba6d443d297a3885c29ce4d8ce518d7cf3da4139fa00d2fa6590","39441845","0x8753bed5c34c31b9edb31efd9552ee6174d1f4e80626fe31cf4380edfbca9f45" +"0x68c974fe2a3fb92cd58f5c5cd291c7be3241489ba1539dcf2c7bd7cbf2a853b1","39473761","0x4bab2808f19279b5a9da6aee021952e715806aba8da22a7b3ffdd68318c305cf" +"0x62759de66cc0714f34a6c0644d93302d6447e0e3ea80d3f4955a58ea8ce0d1fd","39538317","0xfabdd23f17b3ec42b8a088802cd53f74f2381e100fb0185120a096921c152046" +"0xcb37735de2949b110f798048a71538494471c798f51c6e108817cbdca01a2579","39538370","0xa1322bfb6298fbf435d2ee03fe86fd9489878f10008d6dab888c308260e7812d" +"0xcb37735de2949b110f798048a71538494471c798f51c6e108817cbdca01a2579","39538370",NULL +"0xb311ecef92c2550a4529af62c976c1d41aaa39d048637fbc3c65b8c2e4a3def4","39560460","0x75d0368fbf6b6d52f02638ce753f0b9160f229188abad7360039d365944588cd" +"0x6d48ca83ffdae1a069b24b94d740c6dbed4e54bf650f5236c1e12bfeff5496fb","39619994","0x0f3b6aa21acef0fce2078cb4674e45ba4e0cacd2eab0fdb9f9f0a9990c8fa233" +"0x463a34c29fe06adccaabcf869f464e7fc09baca6f1dbf42cf6fbdbaa6e8b248e","39623579","0x6b1ecad40bf6852d1b9cbe28b5d387e6e0e4d79f0409e777a7292a908f6ea606" +"0xde2825d0a2afe1e5d18ea437f8a647f28bf3bb52800861bc3d6a0b914052f4d8","39623588",NULL +"0xc2c183c8f0ee2a031e5d011276d4def815accf98c101c6e5d7efdb74a4209ddb","39623602","0x6b1ecad40bf6852d1b9cbe28b5d387e6e0e4d79f0409e777a7292a908f6ea606" +"0x76b8454b46497d9b623c42cce74a479b08af7b5b49128805050e0e5d494945de","39623611","0x6b1ecad40bf6852d1b9cbe28b5d387e6e0e4d79f0409e777a7292a908f6ea606" +"0x76b8454b46497d9b623c42cce74a479b08af7b5b49128805050e0e5d494945de","39623611",NULL +"0x61d10787d4ef7af59354a15aa8727e327ba6dd37cfc4a4e4bb24cef393f0270b","39623620","0x6b1ecad40bf6852d1b9cbe28b5d387e6e0e4d79f0409e777a7292a908f6ea606" +"0xc38d8d01bbe309a485802aa66ae017656f24128b89e2ee9201c2b5e891a5677a","39623972","0x6b1ecad40bf6852d1b9cbe28b5d387e6e0e4d79f0409e777a7292a908f6ea606" +"0x27596bfaf24b4f102b8e986ea63d578a6908d5076a59bd54e960045b5c2e2e45","39623979",NULL +"0x27596bfaf24b4f102b8e986ea63d578a6908d5076a59bd54e960045b5c2e2e45","39623979","0x6b1ecad40bf6852d1b9cbe28b5d387e6e0e4d79f0409e777a7292a908f6ea606" +"0x8b7bec2c3d84862b0616d218c597ffd930a71d1e91c538659082ccd487ff314f","39624441",NULL +"0x12377adfb82c6f1048eed3332f922a4b5c44eeb745a35cdc0891025e6ebac9c3","39625612","0x6b1ecad40bf6852d1b9cbe28b5d387e6e0e4d79f0409e777a7292a908f6ea606" +"0xc599dd33fe98ca1345c2267369e6bd0a800736f3ee6768c211baffd762440ea8","39625676","0x6b1ecad40bf6852d1b9cbe28b5d387e6e0e4d79f0409e777a7292a908f6ea606" +"0x8da1885f0330c86bfe915e0b150107f3eabcde4cfd410ade382fd7eb2377ae65","39625807","0x6b1ecad40bf6852d1b9cbe28b5d387e6e0e4d79f0409e777a7292a908f6ea606" +"0x9b042d63bcde89e4f94a9bda4200ba4eabf64d1c7d5e3f6562f284af351dc291","39657798","0x05e9fdee3cebb624da2ff2b5666f0479af1a8e8b6c92d9126d8cd71f312ae951" +"0xacbd4e1df600e672f0b551c0405b73b665f60bac9c7b2d8e148be5e796dd9d35","39657852","0x05e9fdee3cebb624da2ff2b5666f0479af1a8e8b6c92d9126d8cd71f312ae951" +"0x024ac7aafb6932c8ca3a50ed67bd3f6918048083a126862c91dadecb372d3913","39692323","0x97d0ea76491ff8cb6cd94399ee051eee022bd8deab8304bdc11a4b961330d014" +"0xc456d5f077a71cebfa9bb0e2512fe09220904cff03da5217306e4dbd22c0619d","39719185","0x46da90a7ce78f65b09004e6b3f74d9411fc0afbe4efac9e368d1220887d84139" +"0x84571ec1ced45c1c0335ac706b731b3a1109524c225b95e1146f9addaf988575","39773185",NULL +"0xf7bf9969a011af8ee1a89e29b417e2864c80252b089c94a537ca62819819d451","39776498","0x0c2ca65a8e695025110953e5dd6ff914b3b763d865f068acbdbbe4439bb5b8ce" +"0x9f0f0a769ae892f0ad8432a14635a42cae388d96d00a1d3490d04bdd6d747e32","39776515",NULL +"0x9f0f0a769ae892f0ad8432a14635a42cae388d96d00a1d3490d04bdd6d747e32","39776515","0x0c2ca65a8e695025110953e5dd6ff914b3b763d865f068acbdbbe4439bb5b8ce" +"0xc83fbdeae28240e3cf70f68807b7a0687a8da060dc4e8762cc6dcec131854fa2","39805003","0xb51c2e1cd185439fbd87b3da8c2cadb5b6315eb4818250cbdb84c6861c70274a" +"0xd314508e88f5f5fa937348b8d2b1ef5e958f8179d9dd2b2eaaf010137f70da34","39805023","0xb51c2e1cd185439fbd87b3da8c2cadb5b6315eb4818250cbdb84c6861c70274a" +"0xc4b9f013ee80cf327af05f9be153eb815e6fbaf860c938c14fd4660271155115","39824099","0x0ccaea8b79535620903b94fb798e2e834a2f3f739344c9f347dd1680231b17cc" +"0x049e204efd44ca951ac09321c68c38bfbb60c373242fbb73b9c778bb78af70e9","39824109","0x0ccaea8b79535620903b94fb798e2e834a2f3f739344c9f347dd1680231b17cc" +"0x348c2a2b3c3f06212267e151d9843da5cc34c93a315b54d9c30a3f9dc62bc48f","39824119","0x0ccaea8b79535620903b94fb798e2e834a2f3f739344c9f347dd1680231b17cc" +"0xa07a02f25cace15f02c5880f1e3ea694d09ad6e9e55ee9be054a5f9b0c5d54d5","39843521","0x0c2ca65a8e695025110953e5dd6ff914b3b763d865f068acbdbbe4439bb5b8ce" +"0xab3fc558c842bdfbe9a001edf569c58d2c248677930ca246eb1ffd52d603b27e","39845925","0x548005d0e5dbb66f5786d0c8bbbd58cd2954f5e5c7e3125fe20a9de1758ce2b9" +"0x8fbbfd626a886f0612f43b105f157c259a95ef98afb061479eeb634fe8737ef2","39845935","0x548005d0e5dbb66f5786d0c8bbbd58cd2954f5e5c7e3125fe20a9de1758ce2b9" +"0xec10a8a201fa8b40a0b2bcac2a44b3277bd1d66055ffd370e4262154d1b2d8b6","39845971","0x548005d0e5dbb66f5786d0c8bbbd58cd2954f5e5c7e3125fe20a9de1758ce2b9" +"0xbb0f76e24bda1d17b19e4398fec1606e910af21db610cf6acd65addd48050836","39845981","0x548005d0e5dbb66f5786d0c8bbbd58cd2954f5e5c7e3125fe20a9de1758ce2b9" +"0xa69476c7b21e6e8f5fceb08f7b91860e50a7571ebca9f700f804e4199bae1af8","39845989","0x548005d0e5dbb66f5786d0c8bbbd58cd2954f5e5c7e3125fe20a9de1758ce2b9" +"0x609ad0837562597b79ee15df0e1b303b286562ec74007b8facdfec845aa5f84f","39845996","0x548005d0e5dbb66f5786d0c8bbbd58cd2954f5e5c7e3125fe20a9de1758ce2b9" +"0xaf9151e22fc4834111e0810b233d2cc253622d49a1f767b27e2ca19f505cfe3f","39846009","0x548005d0e5dbb66f5786d0c8bbbd58cd2954f5e5c7e3125fe20a9de1758ce2b9" +"0x01c658f723fee5f91a8d6d6793d7aadc0dc1343ab73c3982fc213f33f5744dca","39846017","0x548005d0e5dbb66f5786d0c8bbbd58cd2954f5e5c7e3125fe20a9de1758ce2b9" +"0x87df985ce7b7f5d1b16672ac2b7c0064b7f8c5117bc5d61f1b63bc54a9ed6789","39846025","0x548005d0e5dbb66f5786d0c8bbbd58cd2954f5e5c7e3125fe20a9de1758ce2b9" +"0x421c85a58a3b1bee5138ab9428444f9886faf69f0f06e83ad3114c3e6869f11f","39846031","0x548005d0e5dbb66f5786d0c8bbbd58cd2954f5e5c7e3125fe20a9de1758ce2b9" +"0x269354772083379def7110cb0492c7c1a478bca73dfcdefbfb4f48c6690e2ce2","39876793","0xd55c62e970b16e0ecc2320f517b133dfc5fb2459fc8107dee0153414022e0f62" +"0xe2b1bbc42552801622dd3ccf1ea83e7b2d79a9cc75f8a2531ebd6faaa9e67024","39876802","0xd55c62e970b16e0ecc2320f517b133dfc5fb2459fc8107dee0153414022e0f62" +"0xa1c8fc3d247eff046b574dfd2d736422d11a3c11e2e7a39fac97518ec1c4f412","39905646",NULL +"0x408285ecaf1ae6670e195088aaf49c78b35cf978ae69a8b127f6ceaf989a0d74","39926977","0x0726d4595d21791ea0dc9f2c2b677def6f9d5abdddffdde77285b02d46276cf9" +"0x1961813cc6360ce0009b078da936dd3f519d6b4187f5a43e13903a5e81bb7224","39926981","0x0726d4595d21791ea0dc9f2c2b677def6f9d5abdddffdde77285b02d46276cf9" +"0xd96a6ee2c6dca2e6f66e6f6433d9fdc9f690711b2c8cced8de8a6b1e1e3e789b","39937879","0xb5112180d29ddd0158ab8bc874c71ab8a2ba9e14b4d2c685b85819d25bc2c7ff" +"0x8b0ce759efbeb566da1656b21eaee01b4a469f231317ca3e22799443b0245a1b","39944550",NULL +"0x3708d47570e84c1053ebd67fe2af8b28e8011ca3e76a5717767baf7c36e9e04f","39944575","0x4bab2808f19279b5a9da6aee021952e715806aba8da22a7b3ffdd68318c305cf" +"0x73ec8f26be92e81655a4c9b94a7ef375161ca92ac917c823234514f3b29e1a3a","39968158","0xb51c2e1cd185439fbd87b3da8c2cadb5b6315eb4818250cbdb84c6861c70274a" +"0x1bd08aa7128989df5b480024e5689918d94c7c06eceed4989cfe1c6b837fe844","40072512","0x8a2ba0906fd94119094876735dad212fe23cef5fd9e4645b62115266f62ef650" +"0xe7f920bc2ba6aaf31a36b9d35607ebea8d110d13143df756496c2b5d0bbbc1cb","40072529","0x8a2ba0906fd94119094876735dad212fe23cef5fd9e4645b62115266f62ef650" +"0x179f98273de0b670604eb9a3ab3aa40d5ec198b2ad98955dc81251ec940e979c","40074930","0xafd80203aa0f4a94cab5d398c8a6b1b63585931066f52fe5a0f820ddd227af18" +"0x119dfb289539d90f9688e99765b1a48e37c671d0dc07d039a726724a369d7658","40074949","0xafd80203aa0f4a94cab5d398c8a6b1b63585931066f52fe5a0f820ddd227af18" +"0xa30474b9ab2676aa3441380b0d6e6e216f074a2a53607d4ce605bef5ea99cf45","40075047","0xafd80203aa0f4a94cab5d398c8a6b1b63585931066f52fe5a0f820ddd227af18" +"0xe571ef61197603e92b26be69c45cd31360c6eaed535aeded488f0318d79f309b","40075062","0xafd80203aa0f4a94cab5d398c8a6b1b63585931066f52fe5a0f820ddd227af18" +"0xf2411cee00fda5cd89fca9f8271cfadd855c399104b77ebadf8c3a4f3a7fe3ad","40089618","0x65fcb32e414869e427c326c6e88265d7ef8bbbc0fcc802093c621bfa8614db92" +"0xc9ffe48c1f33e31fb17eff9683dcfb064527b64be0fb7c2537c9be0599c7930c","40089884","0x65fcb32e414869e427c326c6e88265d7ef8bbbc0fcc802093c621bfa8614db92" +"0x90553a848c125e1b69518304c2ae9880b927eb406adf379913c03d7758663416","40089909","0x65fcb32e414869e427c326c6e88265d7ef8bbbc0fcc802093c621bfa8614db92" +"0xd18ffc84d3332dcf00f8ad9217d6916aee19951369a5e0ff13fe7d7705d85b08","40089914","0x65fcb32e414869e427c326c6e88265d7ef8bbbc0fcc802093c621bfa8614db92" +"0xa9468323ef171fefddaa54214f2de5357c4bdf3afc21f50fbe7bcef8aa4e017c","40103772","0x09ff255b61aa6bb6f7234fa249a32d34f1e434715894a370ec72880d0fbc4e9b" +"0x233120f8512cf1940f999fd0219fdbfdb84c0eae4f49466f0acb11aa89ef2786","40103826","0x09ff255b61aa6bb6f7234fa249a32d34f1e434715894a370ec72880d0fbc4e9b" +"0x5519968cd9a69b89427f08681dcf72cfcff3a858a4dfdc233ca7589bcf9cfbc2","40176493",NULL +"0xc04b9f570538f9719184e88d7740783ed5cd3ad2701452fdd0d03d9176628a7d","40184446","0x82d4d5166308a8ecea6c81d6e8ea7b8dcc51e957309285e42753d1322af0559a" +"0x7db07147da9435254fba1c7e9a03b168628bc710b7b960b5362aef3c6e15610a","40184486","0x82d4d5166308a8ecea6c81d6e8ea7b8dcc51e957309285e42753d1322af0559a" +"0xabb7f7ffc83c0d358884c170e7b3d74359cea0b7bd27d9a5da8a31a4fea06861","40215357","0xc25556f0d53e7f7fa5b64a98455007d915d191d74b353b777382630639480993" +"0x2dc6441c2133140e6e2af6637792ffc2b3f926c481e079dc1b775e43be2f3b2b","40215408","0xc25556f0d53e7f7fa5b64a98455007d915d191d74b353b777382630639480993" +"0x3ab4ebdbcf1566eb812967287305da5e3a225ddcea5409130dc866256c03fe57","40215415","0xc25556f0d53e7f7fa5b64a98455007d915d191d74b353b777382630639480993" +"0x87c6cbad691b0be8a64f46537c1a52194f15e193c00407a347ed208cc37582ba","40215419","0xc25556f0d53e7f7fa5b64a98455007d915d191d74b353b777382630639480993" +"0xdd656fa95c6a3346280f1a32aece69139f1907053d0c54e8f177db48b4644bdd","40217942","0xbf84fa7c1842648651c4ae75d5f0b2ce97e0b326033f83281db5f5e737d95bd2" +"0x9b756d3f9c3561775d766b0d733a263e98084a867e3584ea5605c7ef754d02ce","40217963","0xbf84fa7c1842648651c4ae75d5f0b2ce97e0b326033f83281db5f5e737d95bd2" +"0xdf4b0312de129252b4e633aecee34f900702614b15bba304f5bb47851dcf48a7","40240985","0x963b54f76e5243f96cf8fec303edd221e509d79ba86776a6b9e30e0a1a4f20bf" +"0xa90004b23301a9ea19909ba2855d9d94c0db8396efdf33def1b6111671b2e945","40241002","0x963b54f76e5243f96cf8fec303edd221e509d79ba86776a6b9e30e0a1a4f20bf" +"0x5bf215dae1ca0c897a1c82dcf631dcb0d1e3fddde6d19ce1430a08eaf9f031c1","40241013","0x963b54f76e5243f96cf8fec303edd221e509d79ba86776a6b9e30e0a1a4f20bf" +"0xd9e77da1f18cc6a357a3f750568e7d19673c05d23a8a322be1c4b3f7a429b45f","40250919","0xd33738f1b7598f26c48e731f72a099a6b4cdc67cd7472997e00ea66e5c57c851" +"0xe83916698fc53f24673f8951cef0dae652d3994e04c06f40b366b37021714d01","40251622","0xd33738f1b7598f26c48e731f72a099a6b4cdc67cd7472997e00ea66e5c57c851" +"0x456b7c31fa4d05a3858ce586a4d64aff2c1c56aef280b0b20e22a2383859d836","40346964",NULL +"0x7e62b8239262808cd70156b98c6481b590f9480ec94c6a7299b995d6d4e69ddb","40356641",NULL +"0xddaf0310eedd59e11362f98bd0d27ef11ca0272751b02d8b9520f224ef7a511b","40356649",NULL +"0xe8b80e551dd4dfd60addb9f7d324e54d75de1413850c37b23d94b1877df24b74","40356657",NULL +"0xc40aa3eadb0a210205a6e0764fa3a41ab1f25161a3864d193220a6497016498f","40401428",NULL +"0xc885467ae5cd391e3b793a9a857e849d613aef3802b76e387601bec75b46de20","40411467","0x77d3e5020c03fd51856eea6f31cfb6639a340587a9a5d3d9beb360e8c00e0878" +"0x724fae3cd6d5bfb8888bde2b718ea8be9748f0005b4166d4eca8b1f3037069ed","40427689",NULL +"0x7bb07bb8956e3edccbb2e391b3ed7752b53a9e4f32c213b5beea75b6d61ea670","40427881","0x6b9f22b82a33af665f30873e84f3c3a1e1e90427f7aa22e8f8e823c1f047e1b4" +"0x4dc00f3dad822955607788dd9fc65f4f52867485f2e1c1927c558ac94941395e","40467161","0x77d96a1d8348df325b55797b50e6eeee89b0b9904e6fe5e78717b2f431a2b5f1" +"0x1207e6e263e639d8d75e702887f2b716c54d73db33bc5525b1a294d8f1ef652d","40468416","0xfcd38926f614a7bb326804ceacbd75280c11d9730ac026e673705952061f723f" +"0x686710f2fe2bda722c4344f9bc82b0a18522eaba9e16bb2ef53c4a84183d886e","40468442","0xfcd38926f614a7bb326804ceacbd75280c11d9730ac026e673705952061f723f" +"0x0e93ca4ca99c878ae3d66f5c791f49deeaf02676b0db704c54942405a6c9901f","40476506","0x7f69cfaed05c265642c7e6cbf98719540798ca445f03806f39df393e7ee42167" +"0x5ea4605a3299158bd343aba0c6b58a2606244accb74a3baa524c5d15cb47af6e","40501380","0xca18bdb3e30f568ba516237c84a0a6ce16d5200b88edfcdf3053001581ba5dc5" +"0xf36edfb60fe1aa8db48676ac878b97795c8c694999952750c30dfaf358dcbfb4","40501387","0xca18bdb3e30f568ba516237c84a0a6ce16d5200b88edfcdf3053001581ba5dc5" +"0x12f2cf2f3b74c5f6726d778b50eefcfe541bc782c6daefe6662a0a92792aef05","40501394","0xca18bdb3e30f568ba516237c84a0a6ce16d5200b88edfcdf3053001581ba5dc5" +"0x211cfc7702ff6b65eb9f24e17fbf8b43866adfdd155e47303ac4714da8eee0ff","40501406","0xca18bdb3e30f568ba516237c84a0a6ce16d5200b88edfcdf3053001581ba5dc5" +"0xd0651059d7f57a81a67a3c62365fe15d13586bafae778e6ce1bdb451c2f5b459","40501420",NULL +"0x8c9fa65344d100cf842fc070c30a3cb2696b450775cb131a952d65db033e6983","40501426",NULL +"0x76b60bf3ba034f8a7e509484692713f916f7d394b45c08da4292a6beed7084c7","40501431",NULL +"0xf92071fcf74688d032853ab4532028d9ecb1b09266a18affe936c4e719268279","40501436",NULL +"0x75044295bcb9b68dc01b855e7b1d870ba834e31e4c3321ae5b012d4f0e0ac903","40501443",NULL +"0x4d579e06511c6c56f5da307c1521790c6b3ac1944abaaa3bb4f020ed103c95de","40509867","0x2c82c5cd1be0caa34adac53b29e1dc7e0797f960cc904798eb30420e7041c87e" +"0x39d1f442fb04f49c2d21fe315b4dce49c23deb52043d0cf046bb81633fe32598","40509936","0x2c82c5cd1be0caa34adac53b29e1dc7e0797f960cc904798eb30420e7041c87e" +"0xaad2deb407736fcb76ef64ac53e381bcf80e7b3f39c10493e6d98d228dbda6a5","40521001","0x46da90a7ce78f65b09004e6b3f74d9411fc0afbe4efac9e368d1220887d84139" +"0xba8dec1cc531f1deaf1beb3aadf7468e55c3f179c331bff595c64ade45e76f0d","40529825",NULL +"0x5c06b16527522f535bf2381c85c8251a368a6e3ae207f6d0a8d8c29bd9fd00e2","40529864",NULL +"0xa136cb3908ddf7e3e25e3525e7b9caa819f10e877049d0c6e0f7aaf1dabe32e0","40529893",NULL +"0xd8a98830eb5ff5af24582c5ace4ac5cade2ce65c51bb8e934e29368f33266b3e","40543452","0x92508f4fbe3a9f81388433c8db42d5c67d3cf0e611445acc8770600f359da691" +"0x7921f690f394e1045f7ccb20f6010ae32afa58353def8183747bcf56b4a85779","40546322",NULL +"0x9f556fa6279b39311ad1924b997e75389d6288ae1e2c42ef4ba2c3a41621a3d6","40546434",NULL +"0x7261750ca8512ef0aebe9809db77319d2d4137856a72f12c19a20c5e208f6315","40586646","0xb93cd1593597cdc2062431c8c0ed18f0001063987d67c1126692c687d1a5d6e3" +"0x96a2ba5c1734c8a03fd1e308789afb04bd9ae2221851cec7679be311244df0f1","40586674","0xb93cd1593597cdc2062431c8c0ed18f0001063987d67c1126692c687d1a5d6e3" +"0x96bcd518c13d730eae26e0f6fabeb04fca6a407e142816f71c6b065ef8c247d7","40610859","0x6207cb5e64f1828e7b3e34d87770aba7c180c40156e18271c4fe71259aeb0f73" +"0x4542afe66867de0914bd179a4f12db1ce2dc345463b136da47d83f2b1bd8fb4f","40616332","0xd5a68f57d27fc14a957af910e028f3b19d5a3d75826621a9f2a7519fc9adc4dc" +"0x32cd1bec1755094293518cd36c5c4278d6b544e4b0fd2d3de83c1889d9bac80e","40616363","0xd5a68f57d27fc14a957af910e028f3b19d5a3d75826621a9f2a7519fc9adc4dc" +"0xc2e5fac9f64d02837203dcf2754aec4965e610c43268a10db81bc06103697fab","40616374","0xd5a68f57d27fc14a957af910e028f3b19d5a3d75826621a9f2a7519fc9adc4dc" +"0x10f6d1cba058274ec924c3800fae44de7a99557324cf3dbfe3335e821be88981","40619727",NULL +"0xbcdfd48266fa95a5f8e8f9cc3eb13ba4174a5c5886f9738e96adcd94be4817ac","40628157",NULL +"0x5a5faf6036b629fd18d28491dddf544f9a5955b16d72d4a5086b7b4221728b2f","40628165",NULL +"0x6634d9508c0b0e04569c5ce24d1a5a2cc74d27616f8cccf8dd773e4c3bc9207c","40642989",NULL +"0x4501f72d29339f250ce928d8f5cc2b1f1d1a7775643cc566532734c039338ea9","40714103","0x1c482b6d5144db583f0194d0568d98fa922ead94b169e064a0cbb6092d325bd9" +"0x789948b40f86aa05c529c4f2a404c959090c20e4690fa21524a48115c538180e","40732729","0x3928fdf7166a13508f7ab830f55209db2b541739a0f6fec2ef0d1bb124a33b41" +"0xe0a1db1a5fb1a5029df99cde3fc6e823ca4a8e4b1f6441f72689ff80f4299ede","40732730","0x0a47b331c5e80f5d62055bf87f32d2bdd6f3a19b83fda2bf520c74181e6a78f0" +"0x3b1380bf904195912143604ef179e2325a1d870fe4774816d1f17eb4d03ad953","40732731","0x91d5733ae1ae3266cb5b19a7f20d5ff7dff86cded593418ba56177d1920c82e7" +"0xb030796a49b019545aced2bfc255c3f735168f24c700fdf6032d8fb592a44825","40732733","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0x7f16fe58c221162debf3f4dd9feda68ed8bb34929adb8502bcf587f41499769e","40732734","0x5136a8249ccddba0bba5180b202c257b343a38ee8bb6313a1264dba4fb50f75e" +"0xc644c3dbe873bfe574fb674eff0bce624e006e281e34e8e8f8d565c8d4321160","40732735","0x6d6b34c3e6aee97d7b97683cc0e40aba2aa1be93699c41ac70aefef75db2b4ac" +"0x1ff991d62940ef0607993e699dc241eb77c7ba78fdc29d40c75ced44b3576d67","40732736","0xb1a6fdaf4f208787cc36583f6a481f4ce38bb2ddc9b6f9a81a08ef1c347c4ab4" +"0xff39b996a55c5c98c527e849a9633104afe347367afedc7196319a51222a1f7e","40732737","0x9172d4d90d2ad0b66eb5dac768b0088127b383936e598ec62a7b648a3ba2aae0" +"0x170d2799ffad2cdc83ea4fb96f18968e340e37edb978fa1aa7d3d69a2d6cc7b0","40732738","0x6703b1ab41296aac2cf886e70d8f569082a5d08c5e0370ef26094a7f50b24c29" +"0x31019ae723427aa017488182ec6047ac972fe7d388273e52eb80670d2b3a8963","40732739","0xb4b3a20b39e86cc66a7407e0596183c704882d9b3d764a4752fc4bc48ff63177" +"0x45d548ae00c7b68c804706c8cf20f2b240ae4e1568b4f4147ebf478468d6779d","40732740","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0x76e2b10ce4f7dc1c0777836e8d95e1cd2b0dcd8684f3eb086852fb9deaa86579","40732741","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0x3a71f17aead81ef548614b51386c196d9e7a2fd9fede0d83a026e84e2a96a11a","40732743","0x0a47b331c5e80f5d62055bf87f32d2bdd6f3a19b83fda2bf520c74181e6a78f0" +"0x372766badec0a6675e0756bb39cff09dd05f54984e05e8d28eae2c8ad9957c39","40732745","0xee9636fbadefeff6c56928f61b9654efa407ebcaa5d46eacac0cae210889f66a" +"0x5cb65baf9379ab3ab9c3b1706a2dc5bf24e12aa2cf27ac414cd58d4303c9f22c","40732746","0xc5e1a06f00d15d8407b1d1c4566d473f967341ed2960645562cdd9cc218820d2" +"0xefeee254503d1c0bb0b2a9dc388e64c725476a96c89e250e42f264c2c49346c2","40732747","0xfc8c45c2720046e8dee593e7fe41a44b1a7e1e3d4f7ff4b458af8e2a3bb6de22" +"0x4799c13447cf0b8a7e7cb3dc0a57a5fe40341590ac5092325b3d9f5fb6de9ee3","40732748","0x030bfd358dbf3808c4cfb85fa5549b7176c18b466f9039ccf3157a7b3d97a0ba" +"0xbdc6bb95dd0a2979fa3633dfb97eb0da691e17815abaaaedc3dcca5a069c1de4","40732749","0xab10e211b743d3cce49740827b3d00fc75f642b23ad0e1bce795dc9aaa6a7a14" +"0xfb6e10c3b3d9057cf5aa0bb5a67c41c63af402e504b5afb1c56e8c810dba5a7f","40732750","0x2cec94103e3f79b1eae8ca39cdeceb323067942d5e520ed18dbfb7e0e16466c8" +"0x9c2a7056a426d780b6b290a0a1c192fb6166de177a0e06b8e5e49f86fe497717","40732751","0xcda41ab7602408b57f3cb12bb9fe09817f2e0690044446c731bcdf4eec278294" +"0xf0f68e07784c8e694d9a61a15df82320e6a89d0be7ba28bdf0b5bdaf75a56638","40732752","0x7d4eaa07a2cde76f1712b5fb0f92a0c5aa2738803b42771699d62d4d1d896db8" +"0x254feeaae499a95fc5bc76dc57e412a2083d649aa9aff559e01114cd060ab389","40732753","0xf561b959e99f0b3e0ffe6d25e68b468dfa1ad5fc32acb62f60f47c4bb590bc12" +"0xd85e3b5bb18c3f092623e2a23ba08cf3c23a31151b0fc07cf502b8bb3622dd9a","40732754","0x2eb949555e3a946541dd29e50d32f638e09a8c2fafbb98225d79c51f790e2000" +"0x1046c4ce31b2e023a126a43cc0a0da6d424fd5c60f9441ceec47c75daa88946b","40732755","0xa248706e0d908997b5c0ea0ec88df443666b7c9131aff9bd1f2f53dde9bb14e9" +"0x9c0a5d3e84081202bc5310a37b0a44b345dff52516c310597c90cfc88abd50ae","40732756","0x3928fdf7166a13508f7ab830f55209db2b541739a0f6fec2ef0d1bb124a33b41" +"0xc54660982853ca61ed0fb6f81a316c66022773ebabb6869e8d6a6aef182aca01","40732757","0x02935a34a97186bee5b9be40eac405c8ae515919c4913d3041b99d7d65c49e41" +"0x1b635c948bbaeacb3295c72bfe45f6c2f9e40361519fcb048aa5957b2ab5641d","40732759","0x6d6b34c3e6aee97d7b97683cc0e40aba2aa1be93699c41ac70aefef75db2b4ac" +"0x15d0e3b2f5b9ca4897fce8fbb27f4f36e24f48ee0d659a881d31013421f7ea97","40732760","0x15d30d5b2c1fd5f75b5d0cb5a1993e85b8229f6716b82aa98e4e5c9aa028cc93" +"0xbab86755d7c321a60b2a946355db19c902f4fb6a66d907ab3a27e88e47ab912a","40732761","0x0dd8ac3fc737f151f49b4473ccfc205c3b4e75ad4c5eeee7ca3c9e796e93525c" +"0x9608a8511b8169536b04fbe80024936ced57ab7842cbe335895af8455a5fea38","40732762","0xb53c25c0d1f6bb7de77e440c9564ccbde9d6b9aade6e8cdf8d1f7ea943955de6" +"0x60e7d4432a3dd0b89bde7ce1bd77b6ade8485bb2c0b626a73d4de173f73180cd","40732763","0x9aaf032a59971d4edcd4162eceef84441eea31204949c0f1d9c601d394671988" +"0xc67ce8167a3c80d3e385d0e6640807c7688da85229b8bac95130a80721d83948","40732764","0x9172d4d90d2ad0b66eb5dac768b0088127b383936e598ec62a7b648a3ba2aae0" +"0xad44bd35bedb0c29617862eb74ef4858f62d5cd4d620249ed98a63fd08852d06","40732765","0x7d4eaa07a2cde76f1712b5fb0f92a0c5aa2738803b42771699d62d4d1d896db8" +"0xbda7fee4583a1fa8df34f97e658f78ad2a243a78ebc9406f846c16f83c58f9f9","40732766","0x1f978626508514272392acad49d2ac75740e467594d8bfdbc2f1085d857b1647" +"0xbdb87a5ab4c786ed76f0e30b85057bd3024d51a059e74972152925db1a5c63a9","40732767","0x763c21d861a746cc852a59b4e8803db5160ce47ff8748d3e3d652cfe3fe79ac5" +"0x29d0a51bf9e9cd7ca61eeef017ba88198d11c67786a5277ef69500520a6c7402","40732768","0x469d8614f605c8a4fb8a0a5fdca23c612ba199004df8f005ce08b27a87a03f8e" +"0xc6f345cae0afdb6a7641578b7776988472688d5067ea8a598471bf81c4554f3c","40732769","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0xe263a2a1c92b089a100c0f5a8f78677d6d39aa43fdbf16a25a2c1de1215c8567","40732770","0xef7d0645043921508dbafefdf596fedea64a6077b48821023fdf5909edcfc7f0" +"0x7ed106736360984213a7e9a8b448b7464b4494366193893c6796507b591781d6","40732771","0xd89842943937e94a6f643353a578d7dd2ea2e69230d1510c57352276a996b89d" +"0x223e6c3043089890f4597558e4d132109c6988fbe6d677513439121a5bc7be1e","40732772","0xc4be2fb9acb4710567311ccd06c0e43cf8aaee6bae23d926f28bbee820ddd895" +"0xce68fdc867813d3ccc0e767f207f95e69cf6269874fa78f601253387270b8fb7","40732773","0x3445a00a9260108ac3567ff5e298b58e64285604b7ebe220504b86603ab859f5" +"0x9168e3d9315294d24ed02b4930892990a35e1819ef17700e171b175da4c752dd","40732774","0x3928fdf7166a13508f7ab830f55209db2b541739a0f6fec2ef0d1bb124a33b41" +"0xad487638dbdeb2bde8d0f62b33c41b48196338dccc57e4b23ed04e1d6cd2689d","40732775","0x0a47b331c5e80f5d62055bf87f32d2bdd6f3a19b83fda2bf520c74181e6a78f0" +"0x6364b0821e12620d370e95068cf29180d71f2f6742660bf9b41dfd04b3d363ad","40732776","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0xb212b4be3352e5b2476cb6f4b8a01541d75e0456df97a5925f0fe48f29ae2275","40732777","0x97a63538f3225b09145c4514c95f8d2e43affb9b8a0f95cb6247f837dd16a2be" +"0x4d850bc20ab245871206506200e258f0a7d2c257985ee5f9f8f429bba886f03d","40732778","0xcda41ab7602408b57f3cb12bb9fe09817f2e0690044446c731bcdf4eec278294" +"0x09945d5537b073b423ab81f6fcbe59ec7c6bd84c303b54b70c9752484bc068ae","40732779","0x43d351308393d2f160b589a64384e109c87448e2d3661a6b9e226c44d8b6f231" +"0xfae000efb34c6368b63bd5d8e443c22b7a1449c6a021afb6361d889310241e13","40732780","0x9172d4d90d2ad0b66eb5dac768b0088127b383936e598ec62a7b648a3ba2aae0" +"0xfc5c3e25b90334ef1db8573c970740e3f8aadbf42a84eb9c6b28f3ae99d696b7","40732781","0x8614477d5c99ede5cfebc5168f1053f26973075ec3707d7ee2d922c96df7cdd4" +"0xea5bb3cd1a2198dbc656573abeef5e8365b55ed3bc2b6c119b3d19f15bb3d4d2","40732782","0x6eac5143908106be1617a8c6215ec2a3bc0b09252593f214a685768e0f81f546" +"0x263497e665ffd5de4a6df5c02840332bccd02f0ad4b1e39001e962b9b6d9bd48","40732783","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0xb26e35134736f829c6f2fed7b67545b28a80ae0e2a1870a30cba1a42f4609cbb","40732784","0x654dbc5844781cbf7652d280f07fe7a9e1049605c6b7315cb5624f3586a9009f" +"0xd99b42aa40b3941e65ad58b1766789b4528304c56e1435ab915619aa66da5f8f","40732785","0xb1a6fdaf4f208787cc36583f6a481f4ce38bb2ddc9b6f9a81a08ef1c347c4ab4" +"0xe7779b635626caae0a08d12c0705133de19efb5427ae9960f281f1b51897af9b","40732786","0xbb81afc12b35f18ecf683ae26c009643ca9bc34d8fe84bdb75d1aa338b131a9b" +"0x27a6a6d15713e6297a4d47ec18f21ac0fab8dfab2ed1cf5d4a75b8cf395adb01","40732787","0xdac16eb1645c0681ed45e9f89475f1deda363fabfcf83e6df280713d3aa1488a" +"0x5d89468cd5c2f59d8eb98b9ea3805dec02cd08f7ae855bcdf0e39edea811c04c","40732789","0xcfa9dc1c9d75bba371f71c757c7d82334957b0a54914f658e726a7a6e415e40f" +"0xa663604849c52cb20bd5e46f4a1b794b28885f3a91203ee5a32b7f5ec3288951","40732790","0x0a47b331c5e80f5d62055bf87f32d2bdd6f3a19b83fda2bf520c74181e6a78f0" +"0xcf8d9db5ca0c44b4aa37472957afd1a9f85634c57dad15a63e4c7b3ba17c323d","40732791","0x30803c7349405b294e19887e4973711000060c145fb85e4cc01ff9ae3d656fc6" +"0xff36d71b8fc2255d2c41063ab56af492bedca4cd039d7dfc5c234b9a678b3d17","40732792","0x401344901ac7018143d568d39b0e77836c8e12ac264451f6c7e72f248ab07125" +"0x82b35814eda65a48efea7b0505a785c6ab727522c9928ad91e401fb76061988c","40732793","0x059b1ca8706c3488a649b3fc197730f82913192066efcd231aeb913836ba5461" +"0x5adb9b91e6da56f17703b60dd9a199b746205b48be88e0af7ee7d3848f18ae9b","40732795","0x063a1fd53feb0854c1f90b263127e5e2069bfbb6c851d92bc4afda431dd0c8e1" +"0x8f6b2ce5e6ca6168ad2ad462ea903b95b9ceeaf7e77483cec85df52cefdd0c0d","40732796","0x242fb191f199d71be970b12fc3e067c04ac8dd0f9d01fbb1689caa243a2f8d84" +"0x2a19cd706a80a9195f7d979218ce684e94543411c3dea6c561b793387f87f0e7","40732797","0x2fac46803f4c98c15017f34050acb1843662d629d0196af3c7d5dd630dbed916" +"0x78ee2d0a11f4fab5cfad1f66d8a6e4c1f700f7b9f23c719416b1b1bf0110a9ad","40732798","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0xc5d2bfce5f918e12efd28081cd456e7ad8d07c8269432f3867698e1b48c54432","40732799","0xdeaf4f03819355d219290f3879a9d50b6aed5d7c2e9551714e41d79be14cde1f" +"0xb565daaf72f7a27c24814b9b74ffb16cd18e4d6f0a7333829384e51689bfea43","40732800","0x9c6c7cc929c0d4f4c6301221af2047247aaec4a8e79455ca53682ad935d9cdeb" +"0x58ff4fc831fcb29cbfcf9f6fc4da141420aa62340188ca1e51454b2253fb16b5","40732801","0x2e6e883ce8f21ab4c990005a1e919bd951bbcc1bee845e5df5b7097fe88ce2d4" +"0x2b122a69d8262a3ecf03bd66387fb19be3188b1ecc58e55b94f6134b198e9f9b","40732802","0x0a47b331c5e80f5d62055bf87f32d2bdd6f3a19b83fda2bf520c74181e6a78f0" +"0xa8f369a601b1b201bf8c8412f1dc140c1b60a5af650ce32696a5fdbc6635f3f2","40732803","0x26760db25c047b551afeb35e6d06d79603679e46730852ac59ff0a9561cbd5ed" +"0x39723bae7ad58bc733c1212eda17607b084b2d472e261aead6e013365597f518","40732804","0x00e1709b42758e5ba8411752658620af47de6a86046fa88dc0ec3e14b2950ca9" +"0x44a9874dcc4ea891138fcb7a52194fc30ea6ae1ffa21639f07f7d9f34d0564a5","40732805","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0xca3441ab362bfa0d5022986691cb8e6aa95efeca2c43db4310b5acde7a0402ce","40732806","0x3dd3cc21ce2c6463a64fa22fd79dc974fa99930230c2f65b395f23814a974b81" +"0x1955a946b2cb040307680f297af230ef8e68b7e30fc37da1e3ac3d1fbd2f7701","40732807","0x327827c7844f284f7de81abfd4c60c4a57a42cab0178051df9ed74a83b599dc8" +"0x33df8d3d66ea2d18b00ca1a46dda95ad630488982a6dee15461b56db90f9f63b","40732808","0x3928fdf7166a13508f7ab830f55209db2b541739a0f6fec2ef0d1bb124a33b41" +"0x1b8729955d9dd81c4c400cb8344f051996dc4a4254f0af90437d517b91718549","40732809","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0x6fc4ccb8a824f2074da66378e190d7cbc93d715cfacc48f2e3ef8f4e12ededcb","40732810","0xc5e1a06f00d15d8407b1d1c4566d473f967341ed2960645562cdd9cc218820d2" +"0x0fdebefad263d931ed4261a99ceb168babba442e90d1ea9e6e6c6726b9ce0f40","40732811","0x7d9026fa617ab13ec6f9f1ea78b21c94e5e7c95fc585ba74390504371ab0d36e" +"0xe07f40ea6ab6a53c5d6763cbb50cbb380abb609562480ba283731ed1d95c622e","40732812","0x4e7905af243b3f05c048d21e4ebec3ca6dc3b145acab32d8684d4e7325ed0c1d" +"0x37e438ef52dc0f665fa7dbd84676a2379b3e9f0c8da21c2fb8c3d94443ff0aa4","40732813","0x97a63538f3225b09145c4514c95f8d2e43affb9b8a0f95cb6247f837dd16a2be" +"0x13b6b89364417d51050dc1d13e9c3d55173e18cdfc17077090248c065f88fb63","40732814","0xa3847c937e3f11e3ec4c138fbaf519e22f89235004bfc68e3437ac54af143e7c" +"0xfaf8cf95f06970a48dbc25489be52eea9a2565599b8ed8dce914370edd2311b7","40732815","0x59e32c9fb06e15954627dcb39df5ec420b567ec28c666f89f3c3f9921f7baf37" +"0x540a5bea8c2d9f471388b86cd9508aaba0e934bc29fc83c500935aacac4a821d","40732816","0x18a303bbb55d9f47a8b36eb291be335c3a03f555fa2780950085c3dae35d8d43" +"0xa0388416109c3f797bb5d7e734d2eddb47e85535bac50e3c0d1897983d0c6d9d","40732817","0xcdd9722e06d196b14b0dbe0216a1d383357e956a0e46b5e680d2432c17ac9172" +"0x3b83b31a671d89d7d923722f554d44d2292b58ba3fb97ca76fdd276310b6fa5b","40732818","0xc4be2fb9acb4710567311ccd06c0e43cf8aaee6bae23d926f28bbee820ddd895" +"0x163a56139477e1910cf158649dfd9e7698babda6f26077a34021e3fcce4320a0","40732819","0xcda41ab7602408b57f3cb12bb9fe09817f2e0690044446c731bcdf4eec278294" +"0x4e124f01d85571c96c6b139b8cc09186981800b2013206d81520fb80937152ed","40732820","0x36bbbd2bd05e1fc52d6f0ceb9ffe1938dbd9f715761b1cd8f77eb32371942866" +"0x0ad0bb06004497bb4195e72d1014c5d7307cc81f654916d9f396086e274a3c2b","40732822","0xe417f781001fd234b9ef0d422a0cecc41db45782a2c85d16b7d8a088c528ea85" +"0x17f8ad332150176d93c6676fbbad56a487db33f4c0ee94bee3bb55fdefbabe11","40732823","0x916987d4b79bd57b62f800dd78b098930ba6143c2a1ed283edfdec8d5c38ab72" +"0x485723051426fcb087d20eb1646589919beee19b940106dbcdb31c49a2ab4a02","40732824","0x05d7321b48d191dfcdac610f1c6efe71584738f4b1770940d5f9ceadd0037409" +"0x375d62b15f4b8ab4a97cca16db723bee4ee1a4c4b23d863b406a180936079192","40732825","0x3d8b29bc234cd312ea8dfe7094c38b3f1676071a4e02e08031d5b1ff600181b3" +"0xfca3a818b8f759dc6a7d6c03a22fc0f1239145ce2776ce75582dc66b088c199e","40732826","0xffa2335d85d531b650192ce820fb45761ed3eae704ebfd467ee29c5cc5267322" +"0x2c0c7f135ea112218ddfd98b026b7c957a2292387b008dbe057a5751775e0ab0","40732827","0xb4b3a20b39e86cc66a7407e0596183c704882d9b3d764a4752fc4bc48ff63177" +"0x8c09f4d34bea8c0a3d0dc3a51df7554ec18dd063659d4dd9ffdeb8f35582c1b6","40732828","0x86ea5800258a508debf48a06a07ac23d9de4eed46ecb2b9bdb59d1b851d6d1ff" +"0x3427079ffd2946212e6e48a10d85db9c1f9a592b0e23281484457a789aa7ce8f","40732829","0xea2fbb070178e5823b7e621619d1be6caf7be1617d7bfaf7ac89f197b8bc08ce" +"0x6c84515cdfec7452756e0242028081e4505a7579e24615508979ae6259968858","40732830","0x39e9d246512c0f8a6dc3fa9682695c03cecd26681ea39405c694bc0d265f5d89" +"0x81eb9d7a8ac854f7514ce0b71a7919392193319ca832a28bab799ecb8011d45d","40732831","0xf5361472e0d5e2c4c37cfab866d118624d3672fbd048fabd3e7914c8568d8385" +"0xe5f6f2915d91e196e73285d6322c7d55980285a1066e8097c3030f89dab3e3f4","40732832","0x469d8614f605c8a4fb8a0a5fdca23c612ba199004df8f005ce08b27a87a03f8e" +"0xfe30f9b3e9924535359f0de5310694672d9d925c5dadc5ccf5443b1d9e9ec510","40732834","0xda7c104a3d469b1864d5f59daca53cf4ffd1656ce4526faef5c166f73866596d" +"0xba356678df146c0d6f6c97e186c8fc99577986963750a4193b9327e31c207f52","40732835","0x105ae36e40d599feb5ce16be7b140a5631d5e46ad5607ff913abc4bc22a3b1fa" +"0x5f7e0a4335f56a6757a330178764fca57336ec599edcb86bc6c1ace4ef2c979b","40732836","0xdcd21a6fa50ac6a7f9890cfe4474733cafe2663e855dd38a48abc2bb1048e073" +"0xfedc9e3563920e97d1640b4e7f7e1585c2fd9b69477268752f929f6bbf335870","40732837","0xee9636fbadefeff6c56928f61b9654efa407ebcaa5d46eacac0cae210889f66a" +"0xe6957f8fdcc4fe995102a5adc973265f6ecccce01745d45209434ae44a9fb538","40732838","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0x5ad1ca7b0ab8126b1b36a20edd479934a9b16f0b1e64b485f4f72f8810102cef","40732839","0x123710d6c151b51425b4b092d5dc904b03d73fec442a77519c7fe7a7e3e5fe1d" +"0xcc8c6af7a4a40641afc151ed3591f40f3b4f99065478c81cf393d84a22c64367","40732840","0xb272ebf70dd4bdd90f5807a289ada31f2107e03d5de8d85250df05c6a427edff" +"0xbebd2f6e096bedadb87f5bc0a5af6f270e2bbabbd78bc495511b71e1641140d5","40732841","0x8f33591db154205b514e9f4ca3fc4e7d58d018614ead3c07dce399a51d96d0eb" +"0xd7d3b7393e2d3b622f309c823d3ca3612d55f796bd46bc14870f2aaa30cb3098","40732842","0x30803c7349405b294e19887e4973711000060c145fb85e4cc01ff9ae3d656fc6" +"0x5e2079aa19eb802453a65a003f32ea0aa81359512123aa6632b0876b434cdec0","40732843","0x14ac4392147895c6b79122b0c54369bda33a48e7792f7999e77be4477a48d09c" +"0xa06bec35ee901ace188608341bed73466d3142de250f70a4feebf592602d80e8","40732844","0x689f57c6676fe4e792994d95a48f52ba13eae92203e9d709696785e9608332f4" +"0x3c291a2a93bac1473a9bfd5fdb937dacaafcf3ffb49c50be92dbbc5fa50af826","40732845","0x4d561afc079c8a23d40d001994e4cf5f4b66b30dd805f8466186eab6b7ac8389" +"0xcaa70fb6ec758987d974c7c277a8093b8454b87d04e1633711762a20736a0b95","40732846","0x401344901ac7018143d568d39b0e77836c8e12ac264451f6c7e72f248ab07125" +"0xefb313ef49dc25e4276e5cc4ba3214ac75ede2ef94d4ba3b28fd473365761471","40732847","0x4d1653a7f786bbfe7765688efbff649b11b784bc1176a93247f948a95271f616" +"0x1b3f38879875f0dd777fc8f53c847ad2fa023f0b3f1c0cb508505a6a2805e54f","40732848","0xfaa9e51e561dee9459fe53a2371a846842bc4522c2563a01615812f83132727a" +"0xe11c719659f1a43c5cfb098958bf0f5cbead563d27325b5d63ab2231f6572789","40732849","0xfaaaa7dfe78dd6994a6e21cad1c4df2d32af97254b6c28180e6aaa0b253628b0" +"0x7d36b61145abfa0589487aaf594a0458f2a0acdbd946673d956814c2bec52be4","40732850","0x1279a9eb7f9d88db964207ff96275e992d797b6d1191520d722fd571ed926f6c" +"0x6bd6a6c1866272677ecd7f1b5e64d16b87fa6a6041da313cd3c7121dc139352f","40732851","0x7adf5052f17e42720dd2bf7fc6db8b54819e29a3ce53b5b27dc42233bb5576a5" +"0xabc46f9344c424f2606f2f628825a82031b4802cff761867c1692801bf5e7ffe","40732852","0x0d963d60afbe3368f6a60237789610533a177fb84f1ac146be00b9b793bde4d6" +"0x34edc292561692abb6087f5efea4e11e133b54dae72a93bc4aadda8b63e77784","40732853","0x5bbd968bf499854fa2ca6fc9f42b5fd72c65200c192c59c637b119644d36daaf" +"0x298896d7aea358a635dec5f0fa326c5850d05efebc1d92547b86a7e9da1971c3","40732854","0x5852a2a385a8fe175128501ecb4c7371e10bc4d7fe605796e10a8c13759755a3" +"0xd53c651cc25a5b547602c3e733cec1f35479d9abd15e2c298c59d088b0c0b206","40732855","0xa20fa8f5962070780c4886718f768b5704416633164fbb7ef54c343145f77895" +"0x626c5198f249c46ecd1d8d7a7ae55cbf4eeccef649edbca045bf8408d35ae9cd","40732856","0x02256ae9e6a94bb107fb3677e4a7283650d702476104499645eb2e4ef5648209" +"0x8d9b4d97388429a870b9110fe8d4dc74ec3fb526abe40dd8cbd71cee53c300e8","40732857","0xcdc258c2a7a5fc3b323feb14fe7bbeee0a44197071cff3055e96c40311e449ea" +"0xfde93093ae1df3bddc9d11035067158287bb8176595358b39c373cd57d6373c4","40732858","0x18504a7c0461e0220ccfa0fee46eb1c092e12bed412cf3cc2c71d4fc319820ea" +"0xb25847fc63596cda28daf59f6bd2c4abb9a4f6f901b1d7da90224aff8a49cc8b","40732859","0xc4be2fb9acb4710567311ccd06c0e43cf8aaee6bae23d926f28bbee820ddd895" +"0xa2297d8d4b245238d6ad6107351ce8ca727831faeb56dc2a0cf861efd683c860","40732860","0x667e312e1a24a2185b793aa42ed0236a32797e177cf8fe2095b2b2e7139f25ef" +"0x37439958fbdfea2224899b7d492f4de3afabe5fb56bf71cea52f05ba27d4c0c8","40732861","0xbd461b5b7dd325593ec7f57744e7165058cf79edcee07e0efa15df0a9db5aa31" +"0x85e62911e08fb89b9baffe8fcc1cd6f99f63a731396aa1c1f46bb0cfb5dea4ad","40732862","0xcc850c809b9a92949529a78fdcd1506aab9a73f4b4a3c107bfbee0a3ce10f300" +"0x95d8721fc0644ef798228684cf7c1d53e5410cf6dba83663e3564d4a4ca823e1","40732863","0xe417f781001fd234b9ef0d422a0cecc41db45782a2c85d16b7d8a088c528ea85" +"0xc755a0f9505ea9d590a8fab72503d170f5061573d153b6fd26389d6fd9510ea8","40732864","0x327827c7844f284f7de81abfd4c60c4a57a42cab0178051df9ed74a83b599dc8" +"0x6834c2170651b8d85f345939ea5b1fe85b3b5fcb72e4804e1763f2c27dd6a2f0","40732865","0xc0741ffc75a80662cd34f102d9937d2fd108f413a550dc953617afbb21aa774a" +"0xb58698ac84bf99881fc6dbc971aa4168842a1a2b07e05e45ae89b1581c9a101f","40732866","0xf561b959e99f0b3e0ffe6d25e68b468dfa1ad5fc32acb62f60f47c4bb590bc12" +"0x17a2ed4d1fa9d6bfe77a303a143f1fe20ff01da033265ac4cb7218745314b81d","40732868","0xea2fbb070178e5823b7e621619d1be6caf7be1617d7bfaf7ac89f197b8bc08ce" +"0x0e1f4cf16b08c9117fbba78fe99bf2b008c75b3d07e88d31dab4ba1681eb3d11","40732869","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0x261e88d94868e002e952cf127856e07af203fa9a13f5feaf82448f04061130cc","40732870","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0xdb7a64a5fee52e9104269e68326cdcf841ac2fbfd7fdffb67ef259ce603f0f5c","40732871","0x98be841e7aa7ba4a3297c0a7db19cf4d30b76711e8e2ce62960611677dcc82c3" +"0x357fa19bd40a7bb2d0dfb1bbb87c69a7e0493067346e419a3ef29a011ee7395b","40732872","0xc4be2fb9acb4710567311ccd06c0e43cf8aaee6bae23d926f28bbee820ddd895" +"0x5771e887288f4640c708cbdbb7ca718cfe2b8a133e372f85f694ee14cb704fe2","40732873","0xa85d4f060ee4c9e73ffbdd28a57623dad3d169001fd12d01b9bc55b1a3c76501" +"0x663ca4cf98dd9fc21cbe85f950d83ca64ee4ecf363319069827c6d7fb406d231","40732874","0x0a47b331c5e80f5d62055bf87f32d2bdd6f3a19b83fda2bf520c74181e6a78f0" +"0x0d1e74f1af29de40e9a1b04ae81616cf86d79f70d2ab96046e1dc2201946bef2","40732875","0x97a63538f3225b09145c4514c95f8d2e43affb9b8a0f95cb6247f837dd16a2be" +"0x210e05f474cbc7f0119bffda85f4172d8316b7020b3c326e4b22eb0b2b105dcf","40732876","0x26760db25c047b551afeb35e6d06d79603679e46730852ac59ff0a9561cbd5ed" +"0x1dbd0c6394aed81010ff3a9efb25be2a1c3e2543d25389212de7acc4c42637e5","40732877","0xf5932eca8982bfcd53aa7e3b6c133a410e535bcedc54ae37fcbd79114484205b" +"0xbde67b39763c450760da0ee4c9402ec81e92daeb96469fdce2e524d543e61eee","40732879","0x8e56f919ec9f8301171f322500c54620e03f9563f88e1ab6e3f57d7df8ac85f9" +"0xd3bce390d1200ac621bdc4859e1a7c1c7d317ef37f8c6e6e87fa64608e097a95","40732880","0x3928fdf7166a13508f7ab830f55209db2b541739a0f6fec2ef0d1bb124a33b41" +"0xd83b66135ff3580a3c19cd39758df7c67b9bbafe23cea2b9b3dd120beb541841","40732881","0xf9ec454d392f515018669c14b98b5dbbbc4973c5a4ebff8f7c72f2a82d1615e8" +"0x6ea79d8f181aacd1cc817afbf3732f4d4282c1c79ad3a6365fe8aba4d12beb40","40732882","0x8614477d5c99ede5cfebc5168f1053f26973075ec3707d7ee2d922c96df7cdd4" +"0xcd32a01502d8bdfed4a624e948c42d1a0522b4e4f3e1f26891807a6afd9cd0f1","40732883","0x469d8614f605c8a4fb8a0a5fdca23c612ba199004df8f005ce08b27a87a03f8e" +"0x764eaf2e756715a184b39502b31e578ae3c0e5dbe14a5cf845088f4bf4db27fb","40732884","0x223f815800becf692012dbb6d1b89e3f66ada624d7c5b00840c146dec51ca624" +"0x2d9c2a304768754102a7c0500df1e1fad364f36fd82c42fed4d40276e401e189","40732885","0xdcd21a6fa50ac6a7f9890cfe4474733cafe2663e855dd38a48abc2bb1048e073" +"0x41dfac98664c18f204ca77d5b6040472acbbb0bcdafa1b1ff1a219d3c60fa88c","40732886","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0xa17fed131bb9d005a0e49f1f1ec67c48516f2d4515a5817f73539de1dd6ea8dd","40732887","0x157beeba24d41b5f2c30ccb53fcb6cc4cf3cc998d2b8d06d8bc156d0925d1ec8" +"0x9b3e3981f65076b672903d60dceac51abe4f761ea2fc97036939a0cd3c1a470d","40732888","0x9a8da6cb489f32805820b9be8f16a8c1c652c792456f7c7e53d27cbed34a21c0" +"0x1fa0eb97f1cc258c4ce0ad4d2745cf403e9da2c89b4031313748ba8fbefd10a7","40732890","0x7adf5052f17e42720dd2bf7fc6db8b54819e29a3ce53b5b27dc42233bb5576a5" +"0xca44d5280941be5ee462d5e4f9a6855e1fe044413a0c7e5808930fa79ec435e5","40732891","0x02256ae9e6a94bb107fb3677e4a7283650d702476104499645eb2e4ef5648209" +"0x816c9d2bc015e21412072e347f271e23a87731cd98618eac20709f8961ca267e","40732892","0xde720aa0491c0388b8b55923ca33416962cbd17215824e6cef242fad72876657" +"0x6e8fb404675ef68c3dfa619841a69f6f1061ee13f2bb1bbc0ed469190e83088a","40732893","0xc4be2fb9acb4710567311ccd06c0e43cf8aaee6bae23d926f28bbee820ddd895" +"0x5cdeed85b1c3649839fcba13ace92163b3ba82174060bc5611b3227a3f77595c","40732894","0x36bbbd2bd05e1fc52d6f0ceb9ffe1938dbd9f715761b1cd8f77eb32371942866" +"0x204cf4018b9810dc7189d8e1210fbe52375b3925d2ff6c6a86f5e9f42d14c039","40732895","0x5de51fe91274c2eb85713d40ba99f453a5a8a25158e381128dc433ad055d66b8" +"0x757c334805ff1eb84fdb4d8711aed267589fac9f71c05e99f8e6dec1b3799aae","40732896","0x98814e958c0f4f78f8ce56a998d9fdffe73f617784909fbaaad08c87b1f6d3aa" +"0x0f03d03f1d6fae1a7f797e2553f7dadfa55a3831d628b037cf1b211a65d7bd30","40732897","0x8e56f919ec9f8301171f322500c54620e03f9563f88e1ab6e3f57d7df8ac85f9" +"0x04367ccad0f3cbd617a85194f387b77da38ffb4670a22c1af63a5de4b8d750c2","40732898","0xe058e9b6118345123ee3eb0422c0a570c01177a2fe7365e0e1bfd47d099100d2" +"0xfe77a3a6466aa5cc1ecd7551706c4504ce03bd378521ac456ed2ac3d55ca5218","40732899","0x00dede0a58d6990acb6a361835144409e3d9779bc86e170d468990fbc42a3b72" +"0x47bc960f9c37d44a0b9f0022334145c9183949fe7fa636abc3ffd586ae1f460d","40732900","0xdac16eb1645c0681ed45e9f89475f1deda363fabfcf83e6df280713d3aa1488a" +"0x98cc330ce9729d83e7e80f82c481329fa3fc1b89de8d22452a00db37d18515ea","40732902","0x26907db6333beff501f67eec3792ef6b7ee895c309472756cbfb096bffb0e6ed" +"0x9d2fc0fa953fce5e8cc0676ae4b5bb03327aff31b8fc8c62133f36f2e5acbe90","40732903","0x0cff6bd5ddebe612e11371aa04a3fb847077cf2e8bd68db803549d44d0d1151b" +"0xadc6cbdc9ae1aee62f4079808d86c7dd27e0e775bd5accff002223ac3f995083","40732904","0x4f06a9e818d58e301638620c10cc8210bb78676e408ed97bca5ede6fced3a660" +"0x99f4a21bdc6a174e7ca53d46d6bded3c137444223ae5c022b386e42d5e66d634","40732905","0x8cd29ca81e7a201a1657728a9a3809714a525eeccf6497756870340ed1b8f75c" +"0x3c9a2d1207e6a98e40bed1ae560564749902b93111baaa61adc8530e7672d542","40732906","0x07d877ccd4d4de64a6fc426e55788f16eebac786152475086a70817a83f64935" +"0xaa0028f077759856dd2e289b22486c0ba6f7f93e790d7c864a29d6ec85e3afb6","40732907","0x2cec94103e3f79b1eae8ca39cdeceb323067942d5e520ed18dbfb7e0e16466c8" +"0xc900a17bc975d82963c6277ee4f7c219f365829cb4cce9bd0b6b3c6c67b63f82","40732908","0x9e4b4e179b9a763c9158507f9377a2bc7ae787a701713db38762c0a3de364f79" +"0xd9ac43f812b0ab5f64fcee3e0980bbb7e476f1580fa315c8576131a121411f61","40732909","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0x66ce3b78e6cf9ff8f21fb2adb78e1c9e9a74f8385b6dcb724d2b23c8e5d3197e","40732910","0xabc3f972b9f812d2f58f4b9b772935811381d12200d5288e1214f086e6a2c6b2" +"0xdf0399a96a9d3560fcccacdc9022bca7d3926919d4e32d73842e6a0499fcec0c","40732911","0x4f06a9e818d58e301638620c10cc8210bb78676e408ed97bca5ede6fced3a660" +"0x84b54873b83636e7a066feeda6a2f734a338958c7792afc7d12d8ea2dfbf6c75","40732912","0x97954078fb19e00173084f264296cc7c183db41ba95d7fd2ca03625bbd65213a" +"0x140aace048e94767a7605eb591c6be251fb881f929aa7e7f3dc8753edc045be1","40732913","0xd89842943937e94a6f643353a578d7dd2ea2e69230d1510c57352276a996b89d" +"0xf4a03202339b3dfce156953f0967c4fb9aeebc858730d9076f0c0dcb5d01d607","40732914","0x60bf377aa6dad48eeff488cec454ace37e74d8d3623584dc6b94e601505707f6" +"0x004c7c0527a65f0d12d5cef418613467f8cfa58c06e2ec98d03566aabb156dac","40732915","0xec968439239fc8d6af235d8b23b0c542617bd246e6a28ad740f31c8361513faf" +"0x97891ca037ef81a285b9302786ad6bcb0f6d2f96c3fdfe0450a4e1025ab1ac48","40732916","0xc4be2fb9acb4710567311ccd06c0e43cf8aaee6bae23d926f28bbee820ddd895" +"0x400a076f8b2af5cedc95a9c378454f4cdbb1c5eb0a314ddaee168241131d4311","40732917","0x7624563c10b9ad850f152bb7313934a2e60bbe24b40a37bad0d3d919a0543212" +"0x07eafcc50771e348a07ede625f3b5b0d0d56b920ce03bbea9db91b129b829f01","40732918","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0xcc8ff57c4205229c13e9232431c510cb63339e59db5113a12780d0fa461f44b3","40732919","0xd48d3985152a7a4905b7c32032aa38b31f30dbe13a98875512c55eaccf7a9c73" +"0x306d84f594436f86b2798c206ef9976f384913fa0e86b8250ebc6984c021b2c0","40732920","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0x38764ccd089631a6dfc9a3191aed1f1d173450c707ec5eefa7c2b1d05ed780ff","40732921","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0x38527f1ff16e78d4075cd7ed3b22c8fa0e596748516e9668c4aec7130fe14f9e","40732922","0x3928fdf7166a13508f7ab830f55209db2b541739a0f6fec2ef0d1bb124a33b41" +"0xb30c1ac60051deb6aeed577e868b7a405b44537305775cd0fd6b0979f4d7dd97","40732923","0x3928fdf7166a13508f7ab830f55209db2b541739a0f6fec2ef0d1bb124a33b41" +"0x9abf39867279c94328c7456fdf78bc8a3efc345799f75a437a36c2606e92c200","40732924","0x5aebc1d2698badacbd9ec88e976d5d14b7805d75047b127a8b1fbaf4ad13447f" +"0xf7ccad7387dd5ca5236a650119319870c523b5ea648653fe5ddc8f0d92ab3e85","40732925","0x381b9dd9bcb00606fcdb4b41b526b2ef67585f077970c9ba86a1eb4fe010a53d" +"0xe3d45ed83a945cf86fd7791a34175df4792786c212e550a9fffc406610171ecb","40732926","0xf4bfcbd64dfda81d2bc42a88e47a6168483c85f9f942bde61e4a7561afb775ec" +"0x90a5eb050602d6f51ee34eff470a22a1e4828d37829ebd5546b3d9f629a4a4d9","40732927","0x3928fdf7166a13508f7ab830f55209db2b541739a0f6fec2ef0d1bb124a33b41" +"0xe8b658189ffc493befdca634eaa2de396b0e8a3f2d48dbe47932720a69124135","40732928","0x3928fdf7166a13508f7ab830f55209db2b541739a0f6fec2ef0d1bb124a33b41" +"0xff3ae3a779dcaf88b752b36a54d0391812bd9a520507e603bedcbd35a24e1685","40732929","0xf4bfcbd64dfda81d2bc42a88e47a6168483c85f9f942bde61e4a7561afb775ec" +"0x3526f009520e0ff73d0391f7eb7c7a78cf819e7fd201fa7d696eb67dd0af27b6","40732930","0x3928fdf7166a13508f7ab830f55209db2b541739a0f6fec2ef0d1bb124a33b41" +"0x7dc37bd21840ba8cd07ebe0e8e9222773be071f0093274d3f71a34f19452840f","40732931","0xd491e9497cb6b20b1d7ee1fb733a01974f82f8104a5c447bfaa90ec9abde36ac" +"0x8e17e719c4c8e550014229ffa5e45eb552fb2e61b66253ab32bc12b63519e7f4","40732932","0xc4be2fb9acb4710567311ccd06c0e43cf8aaee6bae23d926f28bbee820ddd895" +"0xd086eed7df2e29ea9ab9366e3bbc419786e7923668dcea2b139b85a4975e2d22","40762494","0x2b9594b0ed3c41745504430d39616ef8da7609b6dff35038df14e9405dad0454" +"0x1cb97a8231d4bb921340fedf3a533bf1b37b2c7e73a46e7761932dc47c8c4830","40765738","0x7035601b19e5797ca0a893b3e12aa2495aaf8c4e727b83f0aa5d2ffeea749f31" +"0x959db8f15175bc51039b8b02857fc732378159d2ca9aa7117974046365b682ce","40765986","0x7035601b19e5797ca0a893b3e12aa2495aaf8c4e727b83f0aa5d2ffeea749f31" +"0x7dee4e9718951c412be932b58ed858333a0419ed7a96af687ff70291dc236cad","40766410",NULL +"0xc1316376566c120c06cd121e740aa5d2aff39fc6a68bd3c118b1795266c1a2f2","40766497",NULL +"0x8d82bb50f305aa52a4ee2f713ac19c2bd3c839bab5cf58a739b250ef4c394f3c","40766531",NULL +"0x9a8c3219d2d2f24d100544639c72a1cff2aa2ee55b0166198be0d6107d582f12","40766567",NULL +"0xde7222f7be6ff7479c2e66fe6701ba0697aead44252c8506c8742156cc9a5212","40766908",NULL +"0x91852939d05268424b57efb577e4faa0978ec8ce813c2029df6a6fbccf0231c6","40780387",NULL +"0x84c210297d07a27b95bf8610594de00eba674eb7e0b711d1dcbd937deedc4b6c","40780398",NULL +"0x735832cf1ada6b76979835a825baad8dea1fbd4eddca0b4fe62ade6aa1b4345e","40780405",NULL +"0x8068f5c1a3e91fca8dcc88d67dfc273c51865570a3b3c1f355120a8d3ce8322b","40780727",NULL +"0x77ae1b7472c6c6c7f72891050536b812540f4db2f43eff9c891667ebdcf2893c","40780796","0x7035601b19e5797ca0a893b3e12aa2495aaf8c4e727b83f0aa5d2ffeea749f31" diff --git a/deploy/010_deploy_epoch_storage.ts b/deploy/010_deploy_epoch_storage.ts index f4c34d8c..84cc79d6 100644 --- a/deploy/010_deploy_epoch_storage.ts +++ b/deploy/010_deploy_epoch_storage.ts @@ -2,12 +2,10 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { DeployFunction } from 'hardhat-deploy/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - if (hre.network.config.environment !== 'development') { - await hre.helpers.deploy({ - newContractName: 'EpochStorage', - newContractNameInHub: 'EpochStorageV6', - }); - } + await hre.helpers.deploy({ + newContractName: 'EpochStorage', + newContractNameInHub: 'EpochStorageV6', + }); await hre.helpers.deploy({ newContractName: 'EpochStorage', diff --git a/deploy/022_deploy_v6_delegators_info.ts b/deploy/022_deploy_v6_delegators_info.ts new file mode 100644 index 00000000..b92f9915 --- /dev/null +++ b/deploy/022_deploy_v6_delegators_info.ts @@ -0,0 +1,12 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { DeployFunction } from 'hardhat-deploy/types'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + await hre.helpers.deploy({ + newContractName: 'V6_DelegatorsInfo', + }); +}; + +export default func; +func.tags = ['V6_DelegatorsInfo']; +func.dependencies = ['Hub']; diff --git a/deploy/023_deploy_v6_random_sampling_storage.ts b/deploy/023_deploy_v6_random_sampling_storage.ts new file mode 100644 index 00000000..daeee2db --- /dev/null +++ b/deploy/023_deploy_v6_random_sampling_storage.ts @@ -0,0 +1,37 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { DeployFunction } from 'hardhat-deploy/types'; + +// Network-specific parameters for V6_RandomSamplingStorage +type RandomSamplingStorageNetworkConfig = { + proofingPeriodDurationInBlocks: string; + W1: string; + W2: string; +}; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + // Load parameters from the same section used by the original RandomSamplingStorage script + const randomSamplingStorageParametersConfig = hre.helpers.parametersConfig[ + hre.network.config.environment + ].RandomSamplingStorage[ + hre.network.name + ] as unknown as RandomSamplingStorageNetworkConfig; + + if (!randomSamplingStorageParametersConfig) { + throw new Error( + `RandomSamplingStorage parameters config not found for network: ${hre.network.name}`, + ); + } + + await hre.helpers.deploy({ + newContractName: 'V6_RandomSamplingStorage', + additionalArgs: [ + randomSamplingStorageParametersConfig.proofingPeriodDurationInBlocks, + randomSamplingStorageParametersConfig.W1, + randomSamplingStorageParametersConfig.W2, + ], + }); +}; + +export default func; +func.tags = ['V6_RandomSamplingStorage']; +func.dependencies = ['Hub']; diff --git a/deploy/024_deploy_claim_v6_helper.ts b/deploy/024_deploy_claim_v6_helper.ts new file mode 100644 index 00000000..a8d03573 --- /dev/null +++ b/deploy/024_deploy_claim_v6_helper.ts @@ -0,0 +1,12 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { DeployFunction } from 'hardhat-deploy/types'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + await hre.helpers.deploy({ + newContractName: 'ClaimV6Helper', + }); +}; + +export default func; +func.tags = ['ClaimV6Helper']; +func.dependencies = ['Hub', 'V6_RandomSamplingStorage', 'StakingStorage']; diff --git a/deploy/023_deploy_staking.ts b/deploy/025_deploy_staking.ts similarity index 89% rename from deploy/023_deploy_staking.ts rename to deploy/025_deploy_staking.ts index 14af5809..7c47775a 100644 --- a/deploy/023_deploy_staking.ts +++ b/deploy/025_deploy_staking.ts @@ -21,6 +21,9 @@ func.dependencies = [ 'NodeOperatorFeesStorage', 'Ask', 'DelegatorsInfo', + 'V6_DelegatorsInfo', + 'V6_RandomSamplingStorage', + 'ClaimV6Helper', 'Chronos', 'RandomSamplingStorage', 'EpochStorage', diff --git a/deploy/026_deploy_v6_claim.ts b/deploy/026_deploy_v6_claim.ts new file mode 100644 index 00000000..81408239 --- /dev/null +++ b/deploy/026_deploy_v6_claim.ts @@ -0,0 +1,29 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { DeployFunction } from 'hardhat-deploy/types'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + await hre.helpers.deploy({ + newContractName: 'V6_Claim', + }); +}; + +export default func; +func.tags = ['V6_Claim']; +func.dependencies = [ + 'Hub', + 'Chronos', + 'Ask', + 'ShardingTable', + 'ShardingTableStorage', + 'IdentityStorage', + 'ParametersStorage', + 'ProfileStorage', + 'StakingStorage', + 'DelegatorsInfo', + 'V6_DelegatorsInfo', + 'Token', + 'V6_RandomSamplingStorage', + 'EpochStorage', + 'ClaimV6Helper', + 'Staking', +]; diff --git a/deploy/024_deploy_staking_kpi.ts b/deploy/027_deploy_staking_kpi.ts similarity index 100% rename from deploy/024_deploy_staking_kpi.ts rename to deploy/027_deploy_staking_kpi.ts diff --git a/deploy/025_deploy_profile.ts b/deploy/028_deploy_profile.ts similarity index 100% rename from deploy/025_deploy_profile.ts rename to deploy/028_deploy_profile.ts diff --git a/deploy/026_deploy_knowledge_collection.ts b/deploy/029_deploy_knowledge_collection.ts similarity index 100% rename from deploy/026_deploy_knowledge_collection.ts rename to deploy/029_deploy_knowledge_collection.ts diff --git a/deploy/027_deploy_paranet_staging_registry.ts b/deploy/030_deploy_paranet_staging_registry.ts similarity index 100% rename from deploy/027_deploy_paranet_staging_registry.ts rename to deploy/030_deploy_paranet_staging_registry.ts diff --git a/deploy/028_deploy_paranet.ts b/deploy/031_deploy_paranet.ts similarity index 100% rename from deploy/028_deploy_paranet.ts rename to deploy/031_deploy_paranet.ts diff --git a/deploy/029_deploy_paranet_Incentives_pool_factory_helper.ts b/deploy/032_deploy_paranet_Incentives_pool_factory_helper.ts similarity index 100% rename from deploy/029_deploy_paranet_Incentives_pool_factory_helper.ts rename to deploy/032_deploy_paranet_Incentives_pool_factory_helper.ts diff --git a/deploy/030_deploy_paranet_incentives_pool_factory.ts b/deploy/033_deploy_paranet_incentives_pool_factory.ts similarity index 100% rename from deploy/030_deploy_paranet_incentives_pool_factory.ts rename to deploy/033_deploy_paranet_incentives_pool_factory.ts diff --git a/deploy/031_deploy_random_sampling.ts b/deploy/034_deploy_random_sampling.ts similarity index 100% rename from deploy/031_deploy_random_sampling.ts rename to deploy/034_deploy_random_sampling.ts diff --git a/deploy/035_deploy_v6_random_sampling.ts b/deploy/035_deploy_v6_random_sampling.ts new file mode 100644 index 00000000..8d874a86 --- /dev/null +++ b/deploy/035_deploy_v6_random_sampling.ts @@ -0,0 +1,25 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { DeployFunction } from 'hardhat-deploy/types'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + await hre.helpers.deploy({ + newContractName: 'V6_RandomSampling', + }); +}; + +export default func; +func.tags = ['V6_RandomSampling']; +func.dependencies = [ + 'Hub', + 'Chronos', + 'V6_RandomSamplingStorage', + 'StakingStorage', + 'ProfileStorage', + 'EpochStorage', + 'AskStorage', + 'DelegatorsInfo', + 'KnowledgeCollectionStorage', + 'IdentityStorage', + 'ShardingTableStorage', + 'ParametersStorage', +]; diff --git a/deploy/036_deploy_v8_1_1_rewards_period_storage.ts b/deploy/036_deploy_v8_1_1_rewards_period_storage.ts new file mode 100644 index 00000000..a4ddc9f8 --- /dev/null +++ b/deploy/036_deploy_v8_1_1_rewards_period_storage.ts @@ -0,0 +1,12 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { DeployFunction } from 'hardhat-deploy/types'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + await hre.helpers.deploy({ + newContractName: 'V8_1_1_Rewards_Period_Storage', + }); +}; + +export default func; +func.tags = ['V8_1_1_Rewards_Period_Storage']; +func.dependencies = ['Hub']; diff --git a/deploy/037_deploy_v8_1_1_rewards_period.ts b/deploy/037_deploy_v8_1_1_rewards_period.ts new file mode 100644 index 00000000..84570bfc --- /dev/null +++ b/deploy/037_deploy_v8_1_1_rewards_period.ts @@ -0,0 +1,25 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { DeployFunction } from 'hardhat-deploy/types'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + await hre.helpers.deploy({ + newContractName: 'V8_1_1_Rewards_Period', + }); +}; + +export default func; +func.tags = ['V8_1_1_Rewards_Period']; +func.dependencies = [ + 'Hub', + 'V8_1_1_Rewards_Period_Storage', + 'StakingStorage', + 'ShardingTableStorage', + 'ShardingTable', + 'ParametersStorage', + 'Ask', + 'DelegatorsInfo', + 'RandomSamplingStorage', + 'Chronos', + 'Staking', + 'ClaimV6Helper', +]; diff --git a/deploy/038_deploy_staking_manager.ts b/deploy/038_deploy_staking_manager.ts new file mode 100644 index 00000000..e1616729 --- /dev/null +++ b/deploy/038_deploy_staking_manager.ts @@ -0,0 +1,23 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { DeployFunction } from 'hardhat-deploy/types'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + await hre.helpers.deploy({ + newContractName: 'StakingManager', + }); +}; + +export default func; +func.tags = ['StakingManager']; +func.dependencies = [ + 'Hub', + 'Staking', + 'V6_Claim', + 'ProfileStorage', + 'Chronos', + 'DelegatorsInfo', + 'V6_DelegatorsInfo', + 'V8_1_1_Rewards_Period_Storage', + 'V8_1_1_Rewards_Period', + 'ClaimV6Helper', +]; diff --git a/deployments/base_mainnet_contracts.json b/deployments/base_mainnet_contracts.json deleted file mode 100644 index 19a389f1..00000000 --- a/deployments/base_mainnet_contracts.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "contracts": { - "Token": { - "deployed": true, - "evmAddress": "0xa81a52b4dda010896cdd386c7fbdc5cdc835ba23" - }, - "NeurowebERC20": { - "deployed": true, - "evmAddress": "0x2548c27A04e49B412DD887b08d062D34C72ad2B6" - }, - "OldHub": { - "evmAddress": "0xaBfcf2ad1718828E7D3ec20435b0d0b5EAfbDf2c" - }, - "OldIdentityStorage": { - "evmAddress": "0xD40c74f5D1Ee382deb63Fa92d7ff5bC65415bBEE" - }, - "Hub": { - "evmAddress": "0x99Aa571fD5e681c2D27ee08A7b7989DB02541d13", - "version": "1.0.0", - "gitBranch": "v8-contracts", - "gitCommitHash": "e8eb0e42e23f4e7d0d957266d83d25d9407ff656", - "deploymentBlock": 24189831, - "deploymentTimestamp": 1735169014502, - "deployed": true - }, - "ParametersStorage": { - "evmAddress": "0x474f2E0551F35b58A9c75653787059902E3b8e45", - "version": "1.0.0", - "gitBranch": "main", - "gitCommitHash": "39b15d4ca8613442ded7fc928aedfd928b6f059e", - "deploymentBlock": 32076119, - "deploymentTimestamp": 1750941589253, - "deployed": true - }, - "WhitelistStorage": { - "evmAddress": "0x5d7aCedD766b39aa6f20BC49D8F36D2665cdcEb2", - "version": "1.0.0", - "gitBranch": "v8-contracts", - "gitCommitHash": "e8eb0e42e23f4e7d0d957266d83d25d9407ff656", - "deploymentBlock": 24189840, - "deploymentTimestamp": 1735169032135, - "deployed": true - }, - "IdentityStorage": { - "evmAddress": "0xDc67F8Fc0021b20db24701dfA6E67E5739bf094b", - "version": "1.0.0", - "gitBranch": "v8-contracts", - "gitCommitHash": "e8eb0e42e23f4e7d0d957266d83d25d9407ff656", - "deploymentBlock": 24189844, - "deploymentTimestamp": 1735169040198, - "deployed": true - }, - "ShardingTableStorage": { - "evmAddress": "0xF59ca9Eb70D7af0700394924B1053548dE6aE7aF", - "version": "1.0.0", - "gitBranch": "v8-contracts", - "gitCommitHash": "e8eb0e42e23f4e7d0d957266d83d25d9407ff656", - "deploymentBlock": 24189848, - "deploymentTimestamp": 1735169048165, - "deployed": true - }, - "StakingStorage": { - "evmAddress": "0x57307C87E95a372C5D94BCC372bb7304505A739D", - "version": "1.0.0", - "gitBranch": "v8-contracts", - "gitCommitHash": "e8eb0e42e23f4e7d0d957266d83d25d9407ff656", - "deploymentBlock": 24189852, - "deploymentTimestamp": 1735169053403, - "deployed": true - }, - "ProfileStorage": { - "evmAddress": "0x62Ac6414857FAaa08eadA066F3370e7fB3010ed3", - "version": "1.0.0", - "gitBranch": "v8-contracts", - "gitCommitHash": "e8eb0e42e23f4e7d0d957266d83d25d9407ff656", - "deploymentBlock": 24189856, - "deploymentTimestamp": 1735169062573, - "deployed": true - }, - "Chronos": { - "evmAddress": "0x07B1442717bbeD003ab2B2165B1b020F3F6B924B", - "version": null, - "gitBranch": "v8-contracts", - "gitCommitHash": "e8eb0e42e23f4e7d0d957266d83d25d9407ff656", - "deploymentBlock": 24189860, - "deploymentTimestamp": 1735169070781, - "deployed": true - }, - "EpochStorageV6": { - "evmAddress": "0x390B6Dc895D5C815FDC85023d6FB1261fe62c9F7", - "version": "1.0.0", - "gitBranch": "v8-contracts", - "gitCommitHash": "e8eb0e42e23f4e7d0d957266d83d25d9407ff656", - "deploymentBlock": 24189864, - "deploymentTimestamp": 1735169080177, - "deployed": true - }, - "EpochStorageV8": { - "evmAddress": "0x271Dd66348844bbe1d8bf838a4DAE5b4B7f558A1", - "version": "1.0.0", - "gitBranch": "v8-contracts", - "gitCommitHash": "e8eb0e42e23f4e7d0d957266d83d25d9407ff656", - "deploymentBlock": 24189869, - "deploymentTimestamp": 1735169088758, - "deployed": true - }, - "KnowledgeCollectionStorage": { - "evmAddress": "0xc28F310A87f7621A087A603E2ce41C22523F11d7", - "version": "1.0.0", - "gitBranch": "v8-contracts", - "gitCommitHash": "e8eb0e42e23f4e7d0d957266d83d25d9407ff656", - "deploymentBlock": 24189873, - "deploymentTimestamp": 1735169097530, - "deployed": true - }, - "PaymasterManager": { - "evmAddress": "0x937f4A6299ae22DB3f1990aFff8513F2a181eA7C", - "version": "1.0.0", - "gitBranch": "v8-contracts", - "gitCommitHash": "e8eb0e42e23f4e7d0d957266d83d25d9407ff656", - "deploymentBlock": 24189877, - "deploymentTimestamp": 1735169102947, - "deployed": true - }, - "AskStorage": { - "evmAddress": "0xDBdfe1628B4700f2D45Cb2292F905e56F06B8802", - "version": "1.0.0", - "gitBranch": "v8/pricing-fix", - "gitCommitHash": "41db1cea6e655a8a7d869814b05f0d49dcaed9d4", - "deploymentBlock": 24224668, - "deploymentTimestamp": 1735238686973, - "deployed": true - }, - "Identity": { - "evmAddress": "0x57fE6A6f56191bEcfAC857778FdB002803cd2EB2", - "version": "1.0.0", - "gitBranch": "v8-contracts", - "gitCommitHash": "e8eb0e42e23f4e7d0d957266d83d25d9407ff656", - "deploymentBlock": 24189884, - "deploymentTimestamp": 1735169119909, - "deployed": true - }, - "ShardingTable": { - "evmAddress": "0x825B05c8838A8D939EADd08D80e4bce980059b70", - "version": "1.0.0", - "gitBranch": "v8-contracts", - "gitCommitHash": "e8eb0e42e23f4e7d0d957266d83d25d9407ff656", - "deploymentBlock": 24189888, - "deploymentTimestamp": 1735169127961, - "deployed": true - }, - "Ask": { - "evmAddress": "0x8aA667303c37CD1EF4e7C102140879e10A4F233c", - "version": "1.0.0", - "gitBranch": "main", - "gitCommitHash": "926644c7a81783b1fab1bd28e850968c295ff80a", - "deploymentBlock": 31989591, - "deploymentTimestamp": 1750768531561, - "deployed": true - }, - "Staking": { - "evmAddress": "0xa35195779eA99240acC8a0a95ae78E5424d739b9", - "version": "1.0.1", - "gitBranch": "main", - "gitCommitHash": "39b15d4ca8613442ded7fc928aedfd928b6f059e", - "deploymentBlock": 32076123, - "deploymentTimestamp": 1750941595664, - "deployed": true - }, - "Profile": { - "evmAddress": "0xADf1382CAE65Cb81EF5bd889c49d4576Ea610413", - "version": "1.0.0", - "gitBranch": "main", - "gitCommitHash": "39b15d4ca8613442ded7fc928aedfd928b6f059e", - "deploymentBlock": 32076126, - "deploymentTimestamp": 1750941600874, - "deployed": true - }, - "Migrator": { - "evmAddress": "0xc6B8b1E3EFE8BE4dA79f3C2Cfc36d37d6F3DC8CB", - "version": null, - "gitBranch": "v8-contracts", - "gitCommitHash": "e8eb0e42e23f4e7d0d957266d83d25d9407ff656", - "deploymentBlock": 24189909, - "deploymentTimestamp": 1735169166389, - "deployed": true - }, - "KnowledgeCollection": { - "evmAddress": "0x14E11E9117545704474437B910a9350edBdB54CF", - "version": "1.0.0", - "gitBranch": "main", - "gitCommitHash": "926644c7a81783b1fab1bd28e850968c295ff80a", - "deploymentBlock": 31989606, - "deploymentTimestamp": 1750768563478, - "deployed": true - }, - "ParanetsRegistry": { - "evmAddress": "0x798Bc515ee10722Fd2A9d88c96B3CF5BD0F43fA2", - "version": "1.0.1", - "gitBranch": "v8-paranet-update", - "gitCommitHash": "c6cd370193d87cd6a83784ec9774e449591bd7b3", - "deploymentBlock": 26892412, - "deploymentTimestamp": 1740574174916, - "deployed": true - }, - "ParanetServicesRegistry": { - "evmAddress": "0x17b3f93cFC600BA3a89c121615f77C7BCc5162a6", - "version": "1.0.0", - "gitBranch": "v8-paranet-update", - "gitCommitHash": "c6cd370193d87cd6a83784ec9774e449591bd7b3", - "deploymentBlock": 26892415, - "deploymentTimestamp": 1740574181678, - "deployed": true - }, - "ParanetKnowledgeCollectionsRegistry": { - "evmAddress": "0x008a9B78cffb601Ce8Ebe4a1CD5C7DC610e8EB3F", - "version": "1.0.1", - "gitBranch": "v8-paranet-update", - "gitCommitHash": "c6cd370193d87cd6a83784ec9774e449591bd7b3", - "deploymentBlock": 26892419, - "deploymentTimestamp": 1740574188215, - "deployed": true - }, - "ParanetKnowledgeMinersRegistry": { - "evmAddress": "0x86D90A84963312Ba9a9Cb94Ae4711D245E5aFff2", - "version": "1.0.0", - "gitBranch": "v8-paranet-update", - "gitCommitHash": "c6cd370193d87cd6a83784ec9774e449591bd7b3", - "deploymentBlock": 26892422, - "deploymentTimestamp": 1740574195117, - "deployed": true - }, - "ParanetStagingRegistry": { - "evmAddress": "0x21279F69F79B22f45a11fC22bC423A1F4f716d63", - "version": "1.0.0", - "gitBranch": "v8-paranet-update", - "gitCommitHash": "c6cd370193d87cd6a83784ec9774e449591bd7b3", - "deploymentBlock": 26892425, - "deploymentTimestamp": 1740574202118, - "deployed": true - }, - "Paranet": { - "evmAddress": "0x5b4f3D49a0d451124740A1e8c76E08D0FcF50B5E", - "version": "1.0.0", - "gitBranch": "main", - "gitCommitHash": "97e1e0ee3e261dfd76a86ec5a2ec0380cbf2cf37", - "deploymentBlock": 29002075, - "deploymentTimestamp": 1744793501563, - "deployed": true - }, - "ParanetIncentivesPoolFactoryHelper": { - "evmAddress": "0x74D7774cC00caa099fD20D959943dc381DB424a2", - "version": "1.0.0", - "gitBranch": "test/debug-incentives-pool-deployment", - "gitCommitHash": "3b4b684c8029529bc19e891896077a5655a40162", - "deploymentBlock": 27232202, - "deploymentTimestamp": 1741253755086, - "deployed": true - }, - "ParanetIncentivesPoolFactory": { - "evmAddress": "0x6563b9ED5AD41F04a0Bd92b8De890dcF5b50DEf0", - "version": "1.0.0", - "gitBranch": "test/debug-incentives-pool-deployment", - "gitCommitHash": "3b4b684c8029529bc19e891896077a5655a40162", - "deploymentBlock": 27232205, - "deploymentTimestamp": 1741253761713, - "deployed": true - }, - "MigratorM1V8": { - "evmAddress": "0xF596158d084befFF3a21B7bBB90366364bEd742F", - "version": null, - "gitBranch": "main", - "gitCommitHash": "2adb14a072438559a57c10baa8bccd3a96cd3b62", - "deploymentBlock": 31949959, - "deploymentTimestamp": 1750689269245, - "deployed": true - }, - "MigratorM1V8_1": { - "evmAddress": "0xfF7Fab2ba3C9B07d26a951195F4e2809c2F6d4C0", - "version": null, - "gitBranch": "main", - "gitCommitHash": "9b20f049a2602b40eb93af1833e0d35564d2d487", - "deploymentBlock": 31983030, - "deploymentTimestamp": 1750755411443, - "deployed": true - }, - "DelegatorsInfo": { - "evmAddress": "0xbc50dAB30f5f549eAAeFF6738fc62013F3011589", - "version": "1.0.0", - "gitBranch": "main", - "gitCommitHash": "926644c7a81783b1fab1bd28e850968c295ff80a", - "deploymentBlock": 31989593, - "deploymentTimestamp": 1750768536710, - "deployed": true - }, - "RandomSamplingStorage": { - "evmAddress": "0x1fa06DC62de288A1DB21B39afc93e44EE2a8623d", - "version": "1.0.0", - "gitBranch": "main", - "gitCommitHash": "926644c7a81783b1fab1bd28e850968c295ff80a", - "deploymentBlock": 31989596, - "deploymentTimestamp": 1750768542071, - "deployed": true - }, - "StakingKPI": { - "evmAddress": "0xa9F363FE928752dc3d8Aa3F52c1E00416d464919", - "version": "1.0.0", - "gitBranch": "main", - "gitCommitHash": "926644c7a81783b1fab1bd28e850968c295ff80a", - "deploymentBlock": 31989601, - "deploymentTimestamp": 1750768552616, - "deployed": true - }, - "RandomSampling": { - "evmAddress": "0x79E237d4d10c0f7A04E7cCf5E4c6943044eb1606", - "version": "1.0.0", - "gitBranch": "main", - "gitCommitHash": "f6fb99309b856df0ab9092f2dc551e98f9af5c97", - "deploymentBlock": 32033610, - "deploymentTimestamp": 1750856571360, - "deployed": true - } - } -} diff --git a/test/integration/V6.test.ts b/test/integration/V6.test.ts new file mode 100644 index 00000000..1a8ebbbb --- /dev/null +++ b/test/integration/V6.test.ts @@ -0,0 +1,3435 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { time } from '@nomicfoundation/hardhat-network-helpers'; +// @ts-expect-error: No type definitions available for assertion-tools +import { kcTools } from 'assertion-tools'; +import { expect } from 'chai'; +import hre, { ethers } from 'hardhat'; + +import { + Hub, + Token, + Chronos, + StakingStorage, + RandomSamplingStorage, + ParametersStorage, + ProfileStorage, + EpochStorage, + DelegatorsInfo, + Ask, + Staking, + StakingKPI, + RandomSampling, + Profile, + KnowledgeCollection, + AskStorage, + ClaimV6Helper, +} from '../../typechain'; +import { createKnowledgeCollection } from '../helpers/kc-helpers'; +import { createProfile } from '../helpers/profile-helpers'; + +// Sample data for KC +const quads = [ + ' "468.9 sq mi" .', + ' "New York" .', + ' "8,336,817" .', + ' "New York" .', + ' .', + ' "0xaac2a420672a1eb77506c544ff01beed2be58c0ee3576fe037c846f97481cefd" .', + ' .', + ' .', + ' .', + // Add more quads to ensure we have enough chunks + ...Array(1000).fill( + ' .', + ), +]; +const merkleRoot = kcTools.calculateMerkleRoot(quads, 32); + +const toTRAC = (x: string | number) => ethers.parseUnits(x.toString(), 18); + +// ================================================================================================================ +// HELPER FUNCTIONS: Extract common functionality for better readability and reusability +// ================================================================================================================ + +type TestContracts = { + hub: Hub; + token: Token; + chronos: Chronos; + stakingStorage: StakingStorage; + randomSamplingStorage: RandomSamplingStorage; + parametersStorage: ParametersStorage; + profileStorage: ProfileStorage; + epochStorage: EpochStorage; + delegatorsInfo: DelegatorsInfo; + staking: Staking; + stakingKPI: StakingKPI; + profile: Profile; + randomSampling: RandomSampling; + kc: KnowledgeCollection; + askStorage: AskStorage; + ask: Ask; + claimV6Helper: ClaimV6Helper; +}; + +type TestAccounts = { + owner: SignerWithAddress; + node1: { operational: SignerWithAddress; admin: SignerWithAddress }; + node2: { operational: SignerWithAddress; admin: SignerWithAddress }; + delegator1: SignerWithAddress; + delegator2: SignerWithAddress; + delegator3: SignerWithAddress; + kcCreator: SignerWithAddress; + receiver1: { operational: SignerWithAddress; admin: SignerWithAddress }; + receiver2: { operational: SignerWithAddress; admin: SignerWithAddress }; + receiver3: { operational: SignerWithAddress; admin: SignerWithAddress }; +}; + +/** + * Calculate expected node score manually to verify contract calculation + * This implements the same logic as RandomSampling.calculateNodeScore() + */ +async function calculateExpectedNodeScore( + nodeId: bigint, + contracts: TestContracts, +): Promise { + const SCALE18 = ethers.parseUnits('1', 18); + + // 1. Node stake factor calculation + const maximumStake = await contracts.parametersStorage.maximumStake(); + let nodeStake = await contracts.stakingStorage.getNodeStake(nodeId); + nodeStake = nodeStake > maximumStake ? maximumStake : nodeStake; + + const stakeRatio18 = (nodeStake * SCALE18) / maximumStake; + const nodeStakeFactor18 = (2n * stakeRatio18 * stakeRatio18) / SCALE18; + + // 2. Node ask factor calculation + const nodeAsk18 = (await contracts.profileStorage.getAsk(nodeId)) * SCALE18; + const [askLowerBound18, askUpperBound18] = + await contracts.askStorage.getAskBounds(); + + let nodeAskFactor18 = 0n; + if ( + askUpperBound18 > askLowerBound18 && + nodeAsk18 >= askLowerBound18 && + nodeAsk18 <= askUpperBound18 + ) { + const askDiffRatio18 = + ((askUpperBound18 - nodeAsk18) * SCALE18) / + (askUpperBound18 - askLowerBound18); + nodeAskFactor18 = (stakeRatio18 * askDiffRatio18 ** 2n) / SCALE18 ** 2n; + } + + // 3. Node publishing factor calculation + const nodePub = + await contracts.epochStorage.getNodeCurrentEpochProducedKnowledgeValue( + nodeId, + ); + const maxNodePub = + await contracts.epochStorage.getCurrentEpochNodeMaxProducedKnowledgeValue(); + + let nodePublishingFactor18 = 0n; + if (maxNodePub > 0n) { + const pubRatio18 = (nodePub * SCALE18) / maxNodePub; + nodePublishingFactor18 = (nodeStakeFactor18 * pubRatio18) / SCALE18; + } + + return nodeStakeFactor18 + nodeAskFactor18 + nodePublishingFactor18; +} + +/** + * Calculate expected delegator score earned during a period + */ +function calculateExpectedDelegatorScore( + delegatorStake: bigint, + nodeScorePerStake: bigint, + delegatorLastSettledNodeScorePerStake: bigint, +): bigint { + const diff = nodeScorePerStake - delegatorLastSettledNodeScorePerStake; + const SCALE18 = ethers.parseUnits('1', 18); + return (delegatorStake * diff) / SCALE18; +} + +async function epochRewardsPoolPrecisionLoss( + contracts: TestContracts, + claimEpoch: bigint, + netNodeRewards: bigint, + expectedRewardsPool: bigint, +): Promise { + const epochRewardsPool = await contracts.epochStorage.getEpochPool( + 1, + claimEpoch, + ); + console.log( + ` ✅ Epoch rewards pool: ${ethers.formatUnits(epochRewardsPool, 18)} TRAC`, + ); + expect(epochRewardsPool).to.equal(netNodeRewards); + console.log( + ` ✅ Expected rewards pool: ${ethers.formatUnits(expectedRewardsPool, 18)} TRAC`, + ); + console.log( + ` ⚠️ [Epoch ${claimEpoch}] Precision loss: ${ethers.formatUnits( + epochRewardsPool - expectedRewardsPool, + 18, + )} TRAC`, + ); + expect(epochRewardsPool).to.be.closeTo( + expectedRewardsPool, + ethers.parseUnits('0.0000002', 18), + ); +} + +/** + * Submit a proof for a node and verify the score calculation + */ +async function submitProofAndVerifyScore( + nodeId: bigint, + node: { operational: SignerWithAddress; admin: SignerWithAddress }, + contracts: TestContracts, + epoch: bigint, + expectedTotalStake: bigint, +): Promise<{ nodeScore: bigint; nodeScorePerStake: bigint }> { + console.log(` 📋 Submitting proof for node ${nodeId}...`); + + // Get scores before proof submission + const nodeScoreBeforeProofSubmission = + await contracts.randomSamplingStorage.getNodeEpochScore(epoch, nodeId); + const nodeScorePerStakeBeforeProofSubmission = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + epoch, + nodeId, + ); + + // Create challenge + await contracts.randomSampling.connect(node.operational).createChallenge(); + const challenge = + await contracts.randomSamplingStorage.getNodeChallenge(nodeId); + + // Generate and submit proof + const chunks = kcTools.splitIntoChunks(quads, 32); + const chunkId = Number(challenge[1]); + const { proof } = kcTools.calculateMerkleProof(quads, 32, chunkId); + await contracts.randomSampling + .connect(node.operational) + .submitProof(chunks[chunkId], proof); + + // Get actual score from contract + const nodeScoreAfterProofSubmission = + await contracts.randomSamplingStorage.getNodeEpochScore(epoch, nodeId); + const nodeScorePerStake = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + epoch, + nodeId, + ); + + // Calculate expected scores + const nodeScoreIncrement = await calculateExpectedNodeScore( + nodeId, + contracts, + ); + console.log(` ✅ Node score expected increment: ${nodeScoreIncrement}`); + const expectedNodeScore = nodeScoreBeforeProofSubmission + nodeScoreIncrement; + console.log( + ` ✅ Expected node score: ${nodeScoreBeforeProofSubmission} + ${nodeScoreIncrement} = ${expectedNodeScore}, actual ${nodeScoreAfterProofSubmission}`, + ); + // Verify scores match + expect(nodeScoreAfterProofSubmission).to.be.gt( + 0, + 'Node score should be positive', + ); + expect(nodeScoreAfterProofSubmission).to.be.equal(expectedNodeScore); + + const nodeScorePerStakeIncrement = + (nodeScoreIncrement * ethers.parseUnits('1', 18)) / expectedTotalStake; + console.log( + ` ✅ Node score per stake expected increment: ${nodeScorePerStakeIncrement}`, + ); + const expectedNodeScorePerStake = + nodeScorePerStakeBeforeProofSubmission + nodeScorePerStakeIncrement; + console.log( + ` ✅ Node score per stake: expected ${nodeScorePerStakeBeforeProofSubmission} + ${nodeScorePerStakeIncrement} = ${expectedNodeScorePerStake}, actual ${nodeScorePerStake}`, + ); + expect(nodeScorePerStake).to.be.gt( + 0, + 'Node score per stake should be positive', + ); + expect(nodeScorePerStake).to.be.equal(expectedNodeScorePerStake); + + return { + nodeScore: nodeScoreAfterProofSubmission, + nodeScorePerStake: nodeScorePerStake, + }; +} + +/** + * Advance to next proofing period by mining blocks + */ +async function advanceToNextProofingPeriod( + contracts: TestContracts, +): Promise { + const proofingPeriodDuration = + await contracts.randomSamplingStorage.getLatestProofingPeriodDurationInBlocks(); + const activeProofPeriodStartBlock = + await contracts.randomSamplingStorage.getActiveProofPeriodStartBlock(); + + // Find out how many blocks are left in the current proofing period + const currentBlock = Number( + await hre.network.provider.send('eth_blockNumber'), + ); + const blocksLeft = + Number(activeProofPeriodStartBlock) + + Number(proofingPeriodDuration) - + currentBlock + + 1; + + for (let i = 0; i < blocksLeft; i++) { + await hre.network.provider.send('evm_mine'); + } + + await contracts.randomSampling.updateAndGetActiveProofPeriodStartBlock(); +} + +async function ensureNodeHasChunksThisEpoch( + nodeId: bigint, + node: { operational: SignerWithAddress; admin: SignerWithAddress }, + contracts: TestContracts, + accounts: TestAccounts, + receivingNodes: { + operational: SignerWithAddress; + admin: SignerWithAddress; + }[], + receivingNodesIdentityIds: number[], + chunkSize: number, +): Promise { + const produced = + await contracts.epochStorage.getNodeCurrentEpochProducedKnowledgeValue( + nodeId, + ); + + if (produced === 0n) { + if ( + !receivingNodes.some( + (r) => r.operational.address === node.operational.address, + ) + ) { + receivingNodes.unshift(node); + receivingNodesIdentityIds.unshift(Number(nodeId)); + } + + await createKnowledgeCollection( + node.operational, // signer = node.operational + node, // publisher-node + Number(nodeId), + receivingNodes, + receivingNodesIdentityIds, + { KnowledgeCollection: contracts.kc, Token: contracts.token }, + merkleRoot, + `ensure-chunks-${Date.now()}`, + 1, // holders + chunkSize, // byteSize - must be >= CHUNK_BYTE_SIZE to avoid division by zero + 1, // replicas + toTRAC(1), + ); + + await contracts.randomSampling.updateAndGetActiveProofPeriodStartBlock(); + } +} + +/** + * Setup initial test environment with accounts and contracts + */ +async function setupTestEnvironment(): Promise<{ + accounts: TestAccounts; + contracts: TestContracts; + nodeIds: { node1Id: bigint; node2Id: bigint }; + chunkSize: number; +}> { + await hre.deployments.fixture(); + + const signers = await hre.ethers.getSigners(); + const accounts: TestAccounts = { + owner: signers[0], + node1: { operational: signers[1], admin: signers[2] }, + node2: { operational: signers[3], admin: signers[4] }, + delegator1: signers[5], + delegator2: signers[6], + delegator3: signers[7], + kcCreator: signers[8], + receiver1: { operational: signers[9], admin: signers[10] }, + receiver2: { operational: signers[11], admin: signers[12] }, + receiver3: { operational: signers[13], admin: signers[14] }, + }; + + const contracts: TestContracts = { + hub: await hre.ethers.getContract('Hub'), + token: await hre.ethers.getContract('Token'), + chronos: await hre.ethers.getContract('Chronos'), + stakingStorage: + await hre.ethers.getContract('StakingStorage'), + randomSamplingStorage: await hre.ethers.getContract( + 'RandomSamplingStorage', + ), + parametersStorage: + await hre.ethers.getContract('ParametersStorage'), + profileStorage: + await hre.ethers.getContract('ProfileStorage'), + epochStorage: await hre.ethers.getContract('EpochStorageV8'), + delegatorsInfo: + await hre.ethers.getContract('DelegatorsInfo'), + staking: await hre.ethers.getContract('Staking'), + stakingKPI: await hre.ethers.getContract('StakingKPI'), + profile: await hre.ethers.getContract('Profile'), + randomSampling: + await hre.ethers.getContract('RandomSampling'), + kc: await hre.ethers.getContract( + 'KnowledgeCollection', + ), + askStorage: await hre.ethers.getContract('AskStorage'), + ask: await hre.ethers.getContract('Ask'), + claimV6Helper: await hre.ethers.getContract('ClaimV6Helper'), + }; + + // Get chunk size to avoid division by zero in challenge generation + const chunkSize = Number( + await contracts.randomSamplingStorage.CHUNK_BYTE_SIZE(), + ); + + await contracts.hub.setContractAddress('HubOwner', accounts.owner.address); + + // Mint tokens for all participants + for (const delegator of [ + accounts.delegator1, + accounts.delegator2, + accounts.delegator3, + ]) { + await contracts.token.mint(delegator.address, toTRAC(100_000)); + } + // const d2Balance = await contracts.token.balanceOf( + // accounts.delegator2.address, + // ); + /* console.log( + `\n💰💰💰 INITIAL BALANCE 💰💰💰 Delegator2 balance after minting: ${ethers.formatUnits( + d2Balance, + await contracts.token.decimals(), + )} TRAC\n`, + ); */ + await contracts.token.mint(accounts.owner.address, toTRAC(1_000_000)); + await contracts.token.mint( + accounts.node1.operational.address, + toTRAC(1_000_000), + ); + await contracts.token.mint(accounts.kcCreator.address, toTRAC(1_000_000)); + + await contracts.parametersStorage + .connect(accounts.owner) // HubOwner + .setOperatorFeeUpdateDelay(0); + + // Create node profiles + const { identityId: node1Id } = await createProfile( + contracts.profile, + accounts.node1, + ); + const { identityId: node2Id } = await createProfile( + contracts.profile, + accounts.node2, + ); + console.log(`\n📚 Node1 ID = ${node1Id}, operator fee=0`); + await contracts.profile + .connect(accounts.node1.admin) + .updateOperatorFee(node1Id, 0); // 0 % + + expect(await contracts.profileStorage.getOperatorFee(node1Id)).to.equal(0); + + console.log(`\n📚 Node2 ID = ${node2Id}, operator fee=0`); + await contracts.profile + .connect(accounts.node2.admin) + .updateOperatorFee(node2Id, 0); // 0 % + + expect(await contracts.profileStorage.getOperatorFee(node2Id)).to.equal(0); + // Initialize ask system (required to prevent division by zero in RandomSampling) + await contracts.parametersStorage.setMinimumStake(toTRAC(100)); + + // Jump to clean epoch start + const timeUntilNextEpoch = await contracts.chronos.timeUntilNextEpoch(); + await time.increase(timeUntilNextEpoch + 1n); + + return { + accounts, + contracts, + nodeIds: { node1Id: BigInt(node1Id), node2Id: BigInt(node2Id) }, + chunkSize, + }; +} + +type V6Delegator = { + address: string; + amount: bigint; +}; +let v6_delegators: V6Delegator[] = []; + +describe(`Full complex scenario`, function () { + let accounts: TestAccounts; + let contracts: TestContracts; + let nodeIds: { node1Id: bigint; node2Id: bigint }; + let node1Id: bigint; + let d1Key: string, d2Key: string, d3Key: string; + let epoch1: bigint; + let receivingNodes: { + operational: SignerWithAddress; + admin: SignerWithAddress; + }[]; + let receivingNodesIdentityIds: number[]; + let TOKEN_DECIMALS = 18; + let chunkSize: number; + + it('Should execute steps 1-7 with detailed score calculations and verification', async function () { + // ================================================================================================================ + // SETUP: Initialize test environment + // ================================================================================================================ + const setup = await setupTestEnvironment(); + accounts = setup.accounts; + contracts = setup.contracts; + nodeIds = setup.nodeIds; + chunkSize = setup.chunkSize; + node1Id = nodeIds.node1Id; + + v6_delegators = [ + { address: accounts.delegator1.address, amount: toTRAC(500) }, + { address: accounts.delegator2.address, amount: toTRAC(1000) }, + { address: accounts.delegator3.address, amount: toTRAC(1500) }, + ]; + + TOKEN_DECIMALS = Number(await contracts.token.decimals()); + + epoch1 = await contracts.chronos.getCurrentEpoch(); + const epochLength = await contracts.chronos.epochLength(); + const leftUntilNextEpoch = await contracts.chronos.timeUntilNextEpoch(); + console.log(`\n🏁 Starting test in epoch ${epoch1}`); + console.log(`\n🏁 Epoch length ${epochLength}`); + console.log(`\n🏁 Time until next epoch ${leftUntilNextEpoch}`); + console.log( + `\n🏁 Remaining percentage of time until next epoch ${leftUntilNextEpoch / epochLength}`, + ); + // Create delegator keys for state verification + d1Key = ethers.keccak256( + ethers.solidityPacked(['address'], [accounts.delegator1.address]), + ); + d2Key = ethers.keccak256( + ethers.solidityPacked(['address'], [accounts.delegator2.address]), + ); + d3Key = ethers.keccak256( + ethers.solidityPacked(['address'], [accounts.delegator3.address]), + ); + + // ================================================================================================================ + // SETUP: Create Knowledge Collection for reward pool + // ================================================================================================================ + console.log(`\n📚 Creating Knowledge Collection for reward pool...`); + + receivingNodes = [ + accounts.receiver1, + accounts.receiver2, + accounts.receiver3, + ]; + receivingNodesIdentityIds = []; + for (const recNode of receivingNodes) { + const { identityId } = await createProfile(contracts.profile, recNode); + receivingNodesIdentityIds.push(Number(identityId)); + } + + const kcTokenAmount = toTRAC(48_000); + const numberOfEpochs = 10; + console.log( + `\n📚 Reward pool = ${ethers.formatUnits(kcTokenAmount, 18)} TRAC, for ${numberOfEpochs} epochs = ${kcTokenAmount / BigInt(numberOfEpochs)} per epoch`, + ); + await createKnowledgeCollection( + accounts.kcCreator, + accounts.node1, + Number(node1Id), + receivingNodes, + receivingNodesIdentityIds, + { KnowledgeCollection: contracts.kc, Token: contracts.token }, + merkleRoot, + 'test-op-id', + 10, + chunkSize * 10, // byteSize - use multiple of chunkSize for proper chunk generation + numberOfEpochs, + kcTokenAmount, + ); + + // we're sure tokens are well distributed to epochs + + // ================================================================================================================ + // STEP 1: Delegator1 stakes 10,000 TRAC + // ================================================================================================================ + console.log(`\n📊 STEP 1: Delegator1 stakes 10,000 TRAC`); + + const epochBeforeStake = await contracts.chronos.getCurrentEpoch(); + console.log(` ℹ️ Current epoch before staking: ${epochBeforeStake}`); + + await contracts.token + .connect(accounts.delegator1) + .approve(await contracts.staking.getAddress(), toTRAC(10_000)); + await contracts.staking + .connect(accounts.delegator1) + .stake(node1Id, toTRAC(10_000)); + + // Verify state + const totalStakeAfterStep1 = + await contracts.stakingStorage.getNodeStake(node1Id); + console.log( + ` ✅ Node1 total stake: ${ethers.formatUnits(totalStakeAfterStep1, 18)} TRAC`, + ); + expect(totalStakeAfterStep1).to.equal(toTRAC(10_000)); + const totalDelegatorStakeAfterStep1 = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d1Key); + console.log( + ` ✅ Delegator1 total stake: ${ethers.formatUnits(totalDelegatorStakeAfterStep1, 18)} TRAC`, + ); + expect(totalDelegatorStakeAfterStep1).to.equal(toTRAC(10_000)); + + // ================================================================================================================ + // STEP 2: Delegator2 stakes 20,000 TRAC + // ================================================================================================================ + console.log(`\n📊 STEP 2: Delegator2 stakes 20,000 TRAC`); + + await contracts.token + .connect(accounts.delegator2) + .approve(await contracts.staking.getAddress(), toTRAC(20_000)); + await contracts.staking + .connect(accounts.delegator2) + .stake(node1Id, toTRAC(20_000)); + + const totalStakeAfterStep2 = + await contracts.stakingStorage.getNodeStake(node1Id); + console.log( + ` ✅ Node1 total stake: ${ethers.formatUnits(totalStakeAfterStep2, 18)} TRAC`, + ); + expect(totalStakeAfterStep2).to.equal(toTRAC(30_000)); + const totalDelegatorStakeAfterStep2 = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d2Key); + console.log( + ` ✅ Delegator2 total stake: ${ethers.formatUnits(totalDelegatorStakeAfterStep2, 18)} TRAC`, + ); + expect(totalDelegatorStakeAfterStep2).to.equal(toTRAC(20_000)); + + // ================================================================================================================ + // STEP 3: Delegator3 stakes 30,000 TRAC + // ================================================================================================================ + console.log(`\n📊 STEP 3: Delegator3 stakes 30,000 TRAC`); + + await contracts.token + .connect(accounts.delegator3) + .approve(await contracts.staking.getAddress(), toTRAC(30_000)); + await contracts.staking + .connect(accounts.delegator3) + .stake(node1Id, toTRAC(30_000)); + + const totalStakeAfterStep3 = + await contracts.stakingStorage.getNodeStake(node1Id); + console.log( + ` ✅ Node1 total stake: ${ethers.formatUnits(totalStakeAfterStep3, 18)} TRAC`, + ); + expect(totalStakeAfterStep3).to.equal(toTRAC(60_000)); + const totalDelegatorStakeAfterStep3 = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d3Key); + console.log( + ` ✅ Delegator3 total stake: ${ethers.formatUnits(totalDelegatorStakeAfterStep3, 18)} TRAC`, + ); + expect(totalDelegatorStakeAfterStep3).to.equal(toTRAC(30_000)); + + // ================================================================================================================ + // STEP 4: Node1 submits first proof with score verification + // ================================================================================================================ + console.log(`\n🔬 STEP 4: Node1 submits first proof`); + + await contracts.randomSampling.updateAndGetActiveProofPeriodStartBlock(); + const { + nodeScore: scoreAfter1, + nodeScorePerStake: nodeScorePerStakeAfter1, + } = await submitProofAndVerifyScore( + node1Id, + accounts.node1, + contracts, + epoch1, + totalStakeAfterStep3, + ); + + // ================================================================================================================ + // STEP 5: Delegator1 stakes additional 10,000 TRAC with score settlement verification + // ================================================================================================================ + console.log(`\n📊 STEP 5: Delegator1 stakes additional 10,000 TRAC`); + + // Get delegator1's score before staking + const d1ScoreBeforeStake = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + epoch1, + node1Id, + d1Key, + ); + + // Get delegator1's last settled node score per stake + const d1LastSettledNodeScorePerStake = + await contracts.randomSamplingStorage.getDelegatorLastSettledNodeEpochScorePerStake( + epoch1, + node1Id, + d1Key, + ); + + // Stake additional 10,000 TRAC + await contracts.token + .connect(accounts.delegator1) + .approve(await contracts.staking.getAddress(), toTRAC(10_000)); + await contracts.staking + .connect(accounts.delegator1) + .stake(node1Id, toTRAC(10_000)); + + // Verify node1's total stake + const totalStakeAfterStep5 = + await contracts.stakingStorage.getNodeStake(node1Id); + console.log( + ` ✅ Node1 total stake: ${ethers.formatUnits(totalStakeAfterStep5, 18)} TRAC`, + ); + expect(totalStakeAfterStep5).to.equal(toTRAC(70_000)); + const totalDelegator1StakeAfterStep5 = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d1Key); + console.log( + ` ✅ Delegator1 total stake: ${ethers.formatUnits(totalDelegator1StakeAfterStep5, 18)} TRAC`, + ); + expect(totalDelegator1StakeAfterStep5).to.equal(toTRAC(20_000)); + + // Verify delegator1's score settlement from first proof period + const d1ScoreAfterStake = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + epoch1, + node1Id, + d1Key, + ); + const expectedD1ScoreIncrement = calculateExpectedDelegatorScore( + toTRAC(10_000), + nodeScorePerStakeAfter1, + d1LastSettledNodeScorePerStake, + ); + const expectedD1Score = d1ScoreBeforeStake + expectedD1ScoreIncrement; + + console.log(` 🧮 Delegator1 score settlement verification:`); + console.log( + ` ✅ Expected: ${expectedD1Score}, Actual: ${d1ScoreAfterStake}`, + ); + expect(d1ScoreAfterStake).to.equal( + expectedD1Score, + 'Delegator1 score settlement mismatch', + ); + + // ================================================================================================================ + // STEP 6: Node1 submits second proof + // ================================================================================================================ + console.log(`\n🔬 STEP 6: Node1 submits second proof`); + + await advanceToNextProofingPeriod(contracts); + const { + nodeScore: scoreAfter2, + nodeScorePerStake: nodeScorePerStakeAfter2, + } = await submitProofAndVerifyScore( + node1Id, + accounts.node1, + contracts, + epoch1, + totalStakeAfterStep5, + ); + + expect(scoreAfter2).to.be.gt( + scoreAfter1, + 'Second proof should increase total score', + ); + expect(nodeScorePerStakeAfter2).to.be.gt( + nodeScorePerStakeAfter1, + 'Score per stake should increase', + ); + + // ================================================================================================================ + // ADVANCE TO NEXT EPOCH AND FINALIZE + // ================================================================================================================ + console.log(`\n⏭️ Advancing to next epoch and finalizing...`); + + const timeUntilNextEpoch = await contracts.chronos.timeUntilNextEpoch(); + await time.increase(timeUntilNextEpoch + 1n); + const epoch2 = await contracts.chronos.getCurrentEpoch(); + console.log(` ✅ Advanced to epoch ${epoch2}`); + + // Create another KC to trigger epoch finalization + await createKnowledgeCollection( + accounts.kcCreator, + accounts.node1, + Number(node1Id), + receivingNodes, + receivingNodesIdentityIds, + { KnowledgeCollection: contracts.kc, Token: contracts.token }, + merkleRoot, + 'dummy-op-id-2', + 1, // holders + chunkSize * 5, // byteSize - use multiple of chunkSize + 1, // replicas + toTRAC(10), // small fee for finalization + ); + + expect(await contracts.epochStorage.lastFinalizedEpoch(1)).to.be.gte( + epoch1, + ); + console.log(` ✅ Epoch ${epoch1} finalized`); + + // ================================================================================================================ + // STEP 7: Delegator1 claims rewards with detailed verification + // ================================================================================================================ + console.log(`\n💰 STEP 7: Delegator1 claims rewards for epoch ${epoch1}`); + + const d1StakeBaseBefore = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d1Key); + + // Get node score + const nodeFinalScore = + await contracts.randomSamplingStorage.getNodeEpochScore(epoch1, node1Id); + const netNodeRewards = await contracts.stakingKPI.getNetNodeRewards( + node1Id, + epoch1, + ); + + const epocRewardsPool = await contracts.epochStorage.getEpochPool( + 1, + epoch1, + ); + expect(netNodeRewards).to.equal(epocRewardsPool); + + console.log(` 🧮 Reward calculation verification:`); + console.log(` 📊 Node1 final score: ${nodeFinalScore}`); + console.log( + ` 💎 Net delegator rewards: ${ethers.formatUnits(netNodeRewards, 18)} TRAC should be equal to epoch rewards pool: ${ethers.formatUnits(epocRewardsPool, 18)} TRAC`, + ); + + // Claim rewards + await contracts.staking + .connect(accounts.delegator1) + .claimDelegatorRewards(node1Id, epoch1, accounts.delegator1.address); + + const d1FinalScore = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + epoch1, + node1Id, + d1Key, + ); + + // Calculate expected reward: (delegator_score / node_score) * available_rewards + const expectedReward = (d1FinalScore * netNodeRewards) / nodeFinalScore; + + console.log(` 📊 Delegator1 final score: ${d1FinalScore}`); + console.log( + ` 💰 Expected reward for Delegator1: ${ethers.formatUnits(expectedReward, 18)} TRAC`, + ); + + const d1StakeBaseAfter = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d1Key); + const d1LastClaimedEpoch = + await contracts.delegatorsInfo.getLastClaimedEpoch( + node1Id, + accounts.delegator1.address, + ); + + // Verify reward was restaked (since gap is only 1 epoch) + const actualReward = d1StakeBaseAfter - d1StakeBaseBefore; + console.log( + ` ✅ Actual reward for Delegator1: ${ethers.formatUnits(actualReward, 18)} TRAC`, + ); + + // TODO: Fix manual reward calculation - delegator accumulates score across multiple proof periods + // The actual reward is higher because delegator1 earned score in both periods: + // Period 1: 10k stake * score_per_stake_1 + // Period 2: 20k stake * (score_per_stake_2 - score_per_stake_1) + console.log( + ` 📝 Note: Manual calculation needs to account for multi-period accumulation`, + ); + expect(actualReward).to.equal( + expectedReward, + 'Reward should equal expected calculation', + ); + expect(d1LastClaimedEpoch).to.equal( + epoch1, + 'Last claimed epoch not updated', + ); + + // Verify other delegators haven't claimed yet + expect( + await contracts.delegatorsInfo.getLastClaimedEpoch( + node1Id, + accounts.delegator2.address, + ), + ).to.equal(epoch1 - 1n); + expect( + await contracts.delegatorsInfo.getLastClaimedEpoch( + node1Id, + accounts.delegator3.address, + ), + ).to.equal(epoch1 - 1n); + + // ================================================================================================================ + // FINAL VERIFICATION: Test completed successfully + // ================================================================================================================ + console.log( + `\n✨ STEPS 1-7 COMPLETED SUCCESSFULLY WITH FULL VERIFICATION ✨`, + ); + console.log( + `📈 Final Node1 total stake: ${ethers.formatUnits(await contracts.stakingStorage.getNodeStake(node1Id), 18)} TRAC`, + ); + console.log( + `👤 Final Delegator1 stake: ${ethers.formatUnits(await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d1Key), 18)} TRAC`, + ); + console.log( + `👤 Final Delegator2 stake: ${ethers.formatUnits(await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d2Key), 18)} TRAC`, + ); + console.log( + `👤 Final Delegator3 stake: ${ethers.formatUnits(await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d3Key), 18)} TRAC`, + ); + + // Key verifications completed: + // ✅ 1. Delegators can stake on a node + // ✅ 2. Node can submit proofs and accumulate score (with manual verification) + // ✅ 3. Delegator scores are properly settled when additional stakes are made (with manual verification) + // ✅ 4. Epochs can be finalized + // ✅ 5. Delegators can claim rewards based on their proportional score (with manual verification) + // ✅ 6. Rewards are auto-staked when epoch gap ≤ 1 + // ✅ 7. All score calculations match manual computations + }); + + /****************************************************************************************** + * Steps 8 → 14 (continues from the chain state left after Step 7) * + ******************************************************************************************/ + + it('Should execute steps 8-14 with detailed score calculations and verification', async function () { + /* Epoch markers */ + const currentEpoch = await contracts.chronos.getCurrentEpoch(); // == 2 + const previousEpoch = currentEpoch - 1n; // == 1 + + /********************************************************************** + * STEP 8 – Delegator2 claims rewards for previousEpoch * + **********************************************************************/ + console.log( + `\n💰 STEP 8: Delegator2 claims rewards for epoch ${previousEpoch}`, + ); + + const d2BaseBefore = await contracts.stakingStorage.getDelegatorStakeBase( + node1Id, + d2Key, + ); + + const nodeScorePrev = + await contracts.randomSamplingStorage.getNodeEpochScore( + previousEpoch, + node1Id, + ); + const netRewardsPrev = await contracts.stakingKPI.getNetNodeRewards( + node1Id, + previousEpoch, + ); + + await contracts.staking + .connect(accounts.delegator2) + .claimDelegatorRewards( + node1Id, + previousEpoch, + accounts.delegator2.address, + ); + + const d2ScorePrev = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + previousEpoch, + node1Id, + d2Key, + ); + const d2ExpectedReward = (d2ScorePrev * netRewardsPrev) / nodeScorePrev; + + const d2BaseAfter = await contracts.stakingStorage.getDelegatorStakeBase( + node1Id, + d2Key, + ); + const d2ActualReward = d2BaseAfter - d2BaseBefore; + + console.log( + ` ✅ D2 staked reward ${ethers.formatUnits(d2ActualReward, 18)} TRAC (expected ${ethers.formatUnits( + d2ExpectedReward, + 18, + )})`, + ); + expect(d2ActualReward).to.equal(d2ExpectedReward); + + const expectedDelegatorRewards = + await contracts.stakingKPI.getDelegatorReward( + node1Id, + previousEpoch, + accounts.delegator2.address, + ); + console.log( + ` ✅ Expected delegator rewards from StakingKPI: ${ethers.formatUnits(expectedDelegatorRewards, 18)} TRAC`, + ); + expect(d2ActualReward).to.equal(expectedDelegatorRewards); + + await epochRewardsPoolPrecisionLoss( + contracts, + previousEpoch, + netRewardsPrev, + (await contracts.stakingKPI.getDelegatorReward( + node1Id, + previousEpoch, + accounts.delegator1.address, + )) + + d2ActualReward + + (await contracts.stakingKPI.getDelegatorReward( + node1Id, + previousEpoch, + accounts.delegator3.address, + )), + ); + + /********************************************************************** + * STEP 9 – Delegator3 attempts withdrawal before claim → revert * + **********************************************************************/ + console.log( + '\n⛔ STEP 9: Delegator3 withdrawal should revert because they did not claim rewards for all previous epochs', + ); + + await expect( + contracts.staking + .connect(accounts.delegator3) + .requestWithdrawal(node1Id, ethers.parseUnits('5000', 18)), + ).to.be.revertedWith( + 'Must claim the previous epoch rewards before changing stake', + ); + console.log(' ✅ revert received as expected'); + + /********************************************************************** + * STEP 10 – Node1 submits first proof in currentEpoch * + **********************************************************************/ + console.log( + `\n🔬 STEP 10: Node1 submits first proof in epoch ${currentEpoch}`, + ); + + /* move to the next proof-period so the challenge is fresh */ + await advanceToNextProofingPeriod(contracts); + + /* --- BEFORE snapshot ------------------------------------------------ */ + const stakeBeforeProof = + await contracts.stakingStorage.getNodeStake(node1Id); + const scoreBeforeProof = + await contracts.randomSamplingStorage.getNodeEpochScore( + currentEpoch, + node1Id, + ); + const perStakeBefore = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + currentEpoch, + node1Id, + ); + + console.log( + ` ℹ️ before-proof: score=${scoreBeforeProof}, nodeScorePerStake=${perStakeBefore}, stake=${ethers.formatUnits(stakeBeforeProof, 18)} TRAC`, + ); + + /* --- Submit proof & verify internal math --------------------------- */ + await submitProofAndVerifyScore( + node1Id, + accounts.node1, + contracts, + currentEpoch, + stakeBeforeProof, + ); + + /* --- AFTER snapshot ------------------------------------------------- */ + const scoreAfterProof = + await contracts.randomSamplingStorage.getNodeEpochScore( + currentEpoch, + node1Id, + ); + const perStakeAfter = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + currentEpoch, + node1Id, + ); + + /* --- Assertions ----------------------------------------------------- */ + expect(scoreAfterProof).to.be.gt( + scoreBeforeProof, + 'Node epoch score must increase after proof', + ); + expect(perStakeAfter).to.be.gt( + perStakeBefore, + 'Score-per-stake must increase after proof', + ); + + console.log( + ` ✅ score increased: ${scoreBeforeProof} → ${scoreAfterProof}; ` + + `nodeScorePerStake increased: ${perStakeBefore} → ${perStakeAfter}`, + ); + + /********************************************************************** + * STEP 11 – Delegator 2 requests withdrawal of 10 000 TRAC * + **********************************************************************/ + console.log('\n📤 STEP 11: Delegator2 requests withdrawal of 10 000 TRAC'); + + /* ---------- BEFORE snapshot -------------------------------------- */ + const d2StakeBaseBefore = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d2Key); + const nodeStakeBefore11 = + await contracts.stakingStorage.getNodeStake(node1Id); + + const scorePerStakeCur = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + currentEpoch, + node1Id, + ); + const d2LastSettledBefore = + await contracts.randomSamplingStorage.getDelegatorLastSettledNodeEpochScorePerStake( + currentEpoch, + node1Id, + d2Key, + ); + const d2ScoreBefore = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + currentEpoch, + node1Id, + d2Key, + ); + + /* how much score should be settled by _prepareForStakeChange() */ + const expectedScoreIncrement = calculateExpectedDelegatorScore( + d2StakeBaseBefore, // stake before withdrawal + scorePerStakeCur, + d2LastSettledBefore, + ); + + /* ---------- perform withdrawal request --------------------------- */ + await contracts.staking + .connect(accounts.delegator2) + .requestWithdrawal(node1Id, ethers.parseUnits('10000', 18)); + + /* ---------- AFTER snapshot --------------------------------------- */ + const d2StakeBaseAfter = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d2Key); + const nodeStakeAfter11 = + await contracts.stakingStorage.getNodeStake(node1Id); + + const d2ScoreAfter = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + currentEpoch, + node1Id, + d2Key, + ); + const d2LastSettledAfter = + await contracts.randomSamplingStorage.getDelegatorLastSettledNodeEpochScorePerStake( + currentEpoch, + node1Id, + d2Key, + ); + + const [withdrawAmount] = + await contracts.stakingStorage.getDelegatorWithdrawalRequest( + node1Id, + d2Key, + ); + + /* ---------- Assertions ------------------------------------------- */ + expect(withdrawAmount).to.equal( + ethers.parseUnits('10000', 18), + 'withdrawal request amount', + ); + expect(nodeStakeAfter11).to.equal( + nodeStakeBefore11 - ethers.parseUnits('10000', 18), + 'node total stake should fall by 10 000 TRAC', + ); + expect(d2StakeBaseAfter).to.equal( + d2StakeBaseBefore - ethers.parseUnits('10000', 18), + 'delegator base stake should fall by 10 000 TRAC', + ); + expect(d2ScoreAfter).to.equal( + d2ScoreBefore + expectedScoreIncrement, + 'delegator score must be lazily settled before stake change', + ); + expect(d2LastSettledAfter).to.equal( + scorePerStakeCur, + 'lastSettled index must be bumped to current nodeScorePerStake', + ); + + console.log( + ` ✅ withdrawal request stored (${ethers.formatUnits(withdrawAmount, 18)} TRAC)`, + ); + console.log( + ` ✅ node stake decreased: ${ethers.formatUnits(nodeStakeBefore11, 18)} → ${ethers.formatUnits(nodeStakeAfter11, 18)} TRAC`, + ); + console.log( + ` ✅ D2 stakeBase decreased: ${ethers.formatUnits(d2StakeBaseBefore, 18)} → ${ethers.formatUnits(d2StakeBaseAfter, 18)} TRAC`, + ); + console.log( + ` ✅ D2 epochScore increased: ${d2ScoreBefore} → ${d2ScoreAfter} (settled +${expectedScoreIncrement})`, + ); + + /********************************************************************** + * STEP 12 – Node1 submits **second** proof in currentEpoch * + **********************************************************************/ + console.log( + `\n🔬 STEP 12: Node1 submits second proof in epoch ${currentEpoch}`, + ); + + /* --------------------------------------------------------------- + * 1️⃣ Shift to new proof-period so challenge is valid + * ------------------------------------------------------------- */ + await advanceToNextProofingPeriod(contracts); + + /* --------------------------------------------------------------- + * 2️⃣ BEFORE snapshot + * ------------------------------------------------------------- */ + const nodeStakeBefore12 = + await contracts.stakingStorage.getNodeStake(node1Id); // ≈ 62 100 TRAC + const nodeScoreBefore12 = + await contracts.randomSamplingStorage.getNodeEpochScore( + currentEpoch, + node1Id, + ); + const perStakeBefore12 = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + currentEpoch, + node1Id, + ); + const allNodesScoreBefore12 = + await contracts.randomSamplingStorage.getAllNodesEpochScore(currentEpoch); + + console.log( + ` ℹ️ before-proof: nodeScore=${nodeScoreBefore12}, nodeScorePerStake=${perStakeBefore12}, ` + + `allNodesScore=${allNodesScoreBefore12}, nodeStake=${ethers.formatUnits(nodeStakeBefore12, 18)} TRAC`, + ); + + /* --------------------------------------------------------------- + * 3️⃣ Perform proof + builtin math-check + * ------------------------------------------------------------- */ + await submitProofAndVerifyScore( + node1Id, + accounts.node1, + contracts, + currentEpoch, + nodeStakeBefore12, + ); + + /* --------------------------------------------------------------- + * 4️⃣ AFTER snapshot + * ------------------------------------------------------------- */ + const nodeStakeAfter12 = + await contracts.stakingStorage.getNodeStake(node1Id); + const nodeScoreAfter12 = + await contracts.randomSamplingStorage.getNodeEpochScore( + currentEpoch, + node1Id, + ); + const perStakeAfter12 = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + currentEpoch, + node1Id, + ); + const allNodesScoreAfter12 = + await contracts.randomSamplingStorage.getAllNodesEpochScore(currentEpoch); + + /* --------------------------------------------------------------- + * 5️⃣ Assertions – strict before/after checks + * ------------------------------------------------------------- */ + expect(nodeStakeAfter12).to.equal( + nodeStakeBefore12, + 'Node stake must not change when only submitting a proof', + ); + + expect(nodeScoreAfter12).to.be.gt( + nodeScoreBefore12, + 'Node epoch score must increase after second proof', + ); + + expect(perStakeAfter12).to.be.gt( + perStakeBefore12, + 'Score-per-stake must increase after second proof', + ); + + expect(allNodesScoreAfter12).to.be.gt( + allNodesScoreBefore12, + 'Global all-nodes score must increase after proof', + ); + + console.log( + ` ✅ nodeScore: ${nodeScoreBefore12} → ${nodeScoreAfter12}\n` + + ` ✅ scorePerStake: ${perStakeBefore12} → ${perStakeAfter12}\n`, + ); + + /********************************************************************** + * STEP 13 – Delegator 1 claims rewards for epoch `claimEpoch` + * (diagram "Delegator1 claims reward for epoch 2") + **********************************************************************/ + + console.log('\n💰 STEP 13: Delegator1 claims rewards for previous epoch'); + + /* --------------------------------------------------------------- + * 1️⃣ Finalise currentEpoch so rewards become claimable + * – we need to be in epoch 3 and claim for epoch 2 + * ------------------------------------------------------------- */ + const timeUntilNextEpoch = await contracts.chronos.timeUntilNextEpoch(); + await time.increase(timeUntilNextEpoch + 1n); + + const epochAfterFinalize = await contracts.chronos.getCurrentEpoch(); // == currentEpoch + 1 + const claimEpoch = epochAfterFinalize - 1n; // epoch we are claiming for + + /* one more dummy KC → triggers epoch finalisation */ + await createKnowledgeCollection( + accounts.kcCreator, + accounts.node1, + Number(node1Id), + receivingNodes, + receivingNodesIdentityIds, + { KnowledgeCollection: contracts.kc, Token: contracts.token }, + merkleRoot, + 'finalise-epoch', + 10, // holders + chunkSize * 15, // byteSize - use multiple of chunkSize for proper chunk generation + 10, // replicas + toTRAC(50_000), // <-- epoch fee identical to the diagram + ); + + /* epoch really finalised? */ + expect(await contracts.epochStorage.lastFinalizedEpoch(1)).to.be.gte( + claimEpoch, + 'Epoch must be finalised before claiming', + ); + + /* --------------------------------------------------------------- + * 2️⃣ BEFORE snapshot – **manual** reward calculation + * ------------------------------------------------------------- */ + const SCALE18 = ethers.parseUnits('1', 18); + + const d1BaseBefore = await contracts.stakingStorage.getDelegatorStakeBase( + node1Id, + d1Key, + ); + const nodeScore = await contracts.randomSamplingStorage.getNodeEpochScore( + claimEpoch, + node1Id, + ); + const perStake = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + claimEpoch, + node1Id, + ); + const d1LastSettled = + await contracts.randomSamplingStorage.getDelegatorLastSettledNodeEpochScorePerStake( + claimEpoch, + node1Id, + d1Key, + ); + const d1StoredScore = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + claimEpoch, + node1Id, + d1Key, + ); + + /* "lazy-settle" delta that _will_ be written inside claim() */ + const d1SettleDiff = perStake - d1LastSettled; + const earnedScore = (BigInt(d1BaseBefore) * d1SettleDiff) / SCALE18; + + /* total score that delegator should have after settle */ + const d1TotalScore = d1StoredScore + earnedScore; + + /* net pool for delegators that epoch */ + const netDelegatorRewards13 = await contracts.stakingKPI.getNetNodeRewards( + node1Id, + claimEpoch, + ); + + /* expected TRAC reward (18 decimals) */ + const expectedReward13 = + nodeScore === 0n + ? 0n + : (d1TotalScore * netDelegatorRewards13) / nodeScore; + + console.log( + ` ℹ️ claimEpoch=${claimEpoch}, nodeScore=${nodeScore}, d1Score(before)=${d1StoredScore}, earned score=${earnedScore}, pool=${ethers.formatUnits(netDelegatorRewards13, 18)} TRAC`, + ); + console.log( + ` 🔢 nodeScore = ${nodeScore}`, + `\n 🔢 d1StoredScore = ${d1StoredScore}`, + `\n 🔢 d1EarnedScore = ${earnedScore}`, + ); + + /* --------------------------------------------------------------- + * 3️⃣ Perform claim + * ------------------------------------------------------------- */ + await contracts.staking + .connect(accounts.delegator1) + .claimDelegatorRewards(node1Id, claimEpoch, accounts.delegator1.address); + + /* --------------------------------------------------------------- + * 4️⃣ AFTER snapshot + * ------------------------------------------------------------- */ + const d1BaseAfter = await contracts.stakingStorage.getDelegatorStakeBase( + node1Id, + d1Key, + ); + const nodeStakeAfter13 = + await contracts.stakingStorage.getNodeStake(node1Id); + const d1LastClaimed13 = await contracts.delegatorsInfo.getLastClaimedEpoch( + node1Id, + accounts.delegator1.address, + ); + + /* --------------------------------------------------------------- + * 5️⃣ Assertions + * ------------------------------------------------------------- */ + const actualReward13 = d1BaseAfter - d1BaseBefore; + const expectedDelegatorRewardKPI = + await contracts.stakingKPI.getDelegatorReward( + node1Id, + claimEpoch, + accounts.delegator1.address, + ); + + expect(actualReward13, 'restaked reward amount').to.equal(expectedReward13); + expect(expectedDelegatorRewardKPI).to.equal(actualReward13); + expect(d1LastClaimed13, 'lastClaimedEpoch update').to.equal(claimEpoch); + expect(nodeStakeAfter13).to.equal( + nodeStakeAfter12 + actualReward13, + 'node total stake must include newly auto-staked reward', + ); + console.log( + ` 🧮 EXPECTED reward = ${ethers.formatUnits(expectedReward13, 18)} TRAC`, + `\n ✅ ACTUAL reward = ${ethers.formatUnits(actualReward13, 18)} TRAC`, + ); + + /* nice console output */ + console.log( + ` ✅ D1 reward ${ethers.formatUnits(actualReward13, 18)} TRAC ` + + `staked → new base ${ethers.formatUnits(d1BaseAfter, 18)} TRAC`, + ); + console.log(` ✅ lastClaimedEpoch set to ${d1LastClaimed13}\n`); + + /********************************************************************** + * STEP 14 – Delegator 2 claims rewards for epoch `claimEpoch` (= 2) + **********************************************************************/ + + console.log( + '\n💰 STEP 14: Delegator2 claims rewards for epoch', + claimEpoch, + ); + + /* --------------------------------------------------------------- + * 1️⃣ Pre-claim snapshot + * ------------------------------------------------------------- */ + const d2BaseBefore14 = await contracts.stakingStorage.getDelegatorStakeBase( + node1Id, + d2Key, + ); + const d2LastClaimed14 = await contracts.delegatorsInfo.getLastClaimedEpoch( + node1Id, + accounts.delegator2.address, + ); + + // Must be claiming the next unclaimed epoch (1 → 2) + expect(d2LastClaimed14).to.equal( + claimEpoch - 1n, + 'Delegator2 is not claiming the oldest pending epoch', + ); + + /* --------------------------------------------------------------- + * 2️⃣ Manual reward calculation + * ------------------------------------------------------------- */ + const nodeScoreClaim = + await contracts.randomSamplingStorage.getNodeEpochScore( + claimEpoch, + node1Id, + ); + const perStakeClaim = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + claimEpoch, + node1Id, + ); + const d2LastSettledClaim = + await contracts.randomSamplingStorage.getDelegatorLastSettledNodeEpochScorePerStake( + claimEpoch, + node1Id, + d2Key, + ); + const d2StoredScore = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + claimEpoch, + node1Id, + d2Key, + ); + + /* "lazy-settle" part to be added inside claim() */ + const d2SettleDiff = perStakeClaim - d2LastSettledClaim; + const d2EarnedScore = (d2BaseBefore14 * d2SettleDiff) / SCALE18; + const d2TotalScore = d2StoredScore + d2EarnedScore; + + const netDelegatorRewards14 = await contracts.stakingKPI.getNetNodeRewards( + node1Id, + claimEpoch, + ); + + const expectedReward14 = + nodeScoreClaim === 0n + ? 0n + : (d2TotalScore * netDelegatorRewards14) / nodeScoreClaim; + + console.log( + ` 🔢 nodeScore = ${nodeScoreClaim}`, + `\n 🔢 d2StoredScore = ${d2StoredScore}`, + `\n 🔢 d2EarnedScore = ${d2EarnedScore}`, + `pool=${ethers.formatUnits(netDelegatorRewards14, 18)} TRAC`, + ); + + /* --------------------------------------------------------------- + * 3️⃣ Claim transaction + * ------------------------------------------------------------- */ + await contracts.staking + .connect(accounts.delegator2) + .claimDelegatorRewards(node1Id, claimEpoch, accounts.delegator2.address); + + /* --------------------------------------------------------------- + * 4️⃣ Post-claim snapshot + * ------------------------------------------------------------- */ + const d2BaseAfter14 = await contracts.stakingStorage.getDelegatorStakeBase( + node1Id, + d2Key, + ); + const d2LastClaimedAfter = + await contracts.delegatorsInfo.getLastClaimedEpoch( + node1Id, + accounts.delegator2.address, + ); + const actualReward14 = d2BaseAfter14 - d2BaseBefore14; + const expectedDelegatorRewardKPI14 = + await contracts.stakingKPI.getDelegatorReward( + node1Id, + claimEpoch, + accounts.delegator2.address, + ); + + console.log( + ` 🧮 EXPECTED reward = ${ethers.formatUnits(expectedReward14, 18)} TRAC`, + `\n ✅ ACTUAL reward = ${ethers.formatUnits(actualReward14, 18)} TRAC`, + ); + + /* --------------------------------------------------------------- + * 5️⃣ Assertions + * ------------------------------------------------------------- */ + expect(actualReward14, 'staked reward mismatch').to.equal(expectedReward14); + expect(expectedDelegatorRewardKPI14).to.equal(actualReward14); + expect(d2LastClaimedAfter, 'lastClaimedEpoch not updated').to.equal( + claimEpoch, + ); + + // Node stake should grow by the auto-staked reward + const nodeStakeAfter14 = + await contracts.stakingStorage.getNodeStake(node1Id); + expect(nodeStakeAfter14).to.equal( + nodeStakeAfter13 + actualReward14, + 'Node total stake did not include Delegator2 reward', + ); + + // Pending withdrawal request must stay untouched + const [withdrawPending] = + await contracts.stakingStorage.getDelegatorWithdrawalRequest( + node1Id, + d2Key, + ); + expect(withdrawPending).to.equal( + ethers.parseUnits('10000', 18), + 'Withdrawal request amount changed after claim', + ); + + console.log( + ` ✅ D2 reward ${ethers.formatUnits(actualReward14, 18)} TRAC ` + + `restaked → new base ${ethers.formatUnits(d2BaseAfter14, 18)} TRAC`, + ); + console.log(` ✅ lastClaimedEpoch set to ${d2LastClaimedAfter}\n`); + console.log('\n✨ Steps 8-14 completed – ready for next tests ✨\n'); + + await epochRewardsPoolPrecisionLoss( + contracts, + claimEpoch, + netDelegatorRewards14, + actualReward13 + + actualReward14 + + (await contracts.stakingKPI.getDelegatorReward( + node1Id, + claimEpoch, + accounts.delegator3.address, + )), + ); + }); + + /****************************************************************************************** + * Steps 15 – 21 (continue from the chain-state left after Step 14) * + ******************************************************************************************/ + it('Should execute steps 15-23 with detailed score calculations and verification', async function () { + /* helpers already in scope from previous tests */ + const toTRAC18 = (x: number | string) => + ethers.parseUnits(x.toString(), 18); + + const TEN_K = ethers.parseUnits('10000', TOKEN_DECIMALS); + + /********************************************************************** + * STEP 15 – Delegator 2 finalises withdrawal of 10 000 TRAC + **********************************************************************/ + console.log('\n📤 STEP 15: Delegator2 finalises withdrawal of 10 000 TRAC'); + + /* 1️⃣ Make sure the request exists and the delay has passed */ + const [pending, , releaseTs] = + await contracts.stakingStorage.getDelegatorWithdrawalRequest( + node1Id, + d2Key, + ); + + expect(pending, 'pending amount mismatch').to.equal(TEN_K); + + const now = BigInt(await time.latest()); + if (now < releaseTs) await time.increase(releaseTs - now + 1n); + + /* 2️⃣ Snapshot BEFORE */ + const balBefore = await contracts.token.balanceOf(accounts.delegator2); + const nodeStakeBefore15 = + await contracts.stakingStorage.getNodeStake(node1Id); + + console.log( + ` 🪙 Wallet BEFORE: ${ethers.formatUnits(balBefore, TOKEN_DECIMALS)} TRAC`, + ); + + /* 3️⃣ Finalise */ + await contracts.staking + .connect(accounts.delegator2) + .finalizeWithdrawal(node1Id); + + /* 4️⃣ Snapshot AFTER */ + const balAfter = await contracts.token.balanceOf(accounts.delegator2); + const nodeStakeAfter15 = + await contracts.stakingStorage.getNodeStake(node1Id); + const [reqAfter] = + await contracts.stakingStorage.getDelegatorWithdrawalRequest( + node1Id, + d2Key, + ); + + /* 5️⃣ Assertions */ + expect(balAfter - balBefore, 'wallet diff').to.equal(TEN_K); // ← BigInt diff + expect(nodeStakeAfter15, 'node stake already reduced in step 11').to.equal( + nodeStakeBefore15, + ); + expect(reqAfter, 'withdrawal request should be cleared').to.equal(0n); + + console.log( + ` 🪙 Wallet AFTER : ${ethers.formatUnits(balAfter, TOKEN_DECIMALS)} TRAC`, + `\n ✅ 10 000 TRAC transferred successfully`, + ); + + /********************************************************************** + * STEP 16 – Delegator 3 tries to stake extra 5 000 TRAC (must revert) * + **********************************************************************/ + console.log( + '\n⛔ STEP 16: Delegator3 attempts to stake 5 000 TRAC – should revert', + ); + + await contracts.token + .connect(accounts.delegator3) + .approve(await contracts.staking.getAddress(), toTRAC18(5_000)); + + await expect( + contracts.staking + .connect(accounts.delegator3) + .stake(node1Id, toTRAC18(5_000)), + ).to.be.revertedWith( + 'Must claim all previous epoch rewards before changing stake', + ); + + console.log( + ' ✅ Revert received as expected – Delegator3 must claim epochs 2 & 3 first', + ); + + /********************************************************************** + * STEP 17 – Delegator 3 claims rewards for epoch 1 + **********************************************************************/ + console.log('\n💰 STEP 17: Delegator3 claims rewards for epoch 2'); + + const claimEpoch17 = 2n; + + const SCALE18 = ethers.parseUnits('1', 18); + + /* ── 1. Preconditions ────────────────────────────────────────────── */ + const lastClaimedBefore = + await contracts.delegatorsInfo.getLastClaimedEpoch( + node1Id, + accounts.delegator3.address, + ); + + /** + * 0 – sentinel "never claimed" (default) + * n–1 – standard "oldest un-claimed epoch" + */ + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect( + lastClaimedBefore === 0n || lastClaimedBefore === claimEpoch17 - 1n, + 'Delegator-3 must claim the oldest pending epoch first', + ).to.be.true; + + /* ── 2. Manual reward calculation (for assertions) ───────────────── */ + const stakeBaseBefore = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d3Key); + + const nodeScore17 = await contracts.randomSamplingStorage.getNodeEpochScore( + claimEpoch17, + node1Id, + ); + const perStake17 = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + claimEpoch17, + node1Id, + ); + + const lastSettled17 = + await contracts.randomSamplingStorage.getDelegatorLastSettledNodeEpochScorePerStake( + claimEpoch17, + node1Id, + d3Key, + ); + const storedScore17 = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + claimEpoch17, + node1Id, + d3Key, + ); + + const earnedScore17 = + (stakeBaseBefore * (perStake17 - lastSettled17)) / SCALE18; + const totalScore17 = storedScore17 + earnedScore17; + + const rewardsPool17 = await contracts.stakingKPI.getNetNodeRewards( + node1Id, + claimEpoch17, + ); + + const expectedReward17 = + nodeScore17 === 0n ? 0n : (totalScore17 * rewardsPool17) / nodeScore17; + + /* ── 3. Claim transaction ────────────────────────────────────────── */ + await contracts.staking + .connect(accounts.delegator3) + .claimDelegatorRewards( + node1Id, + claimEpoch17, + accounts.delegator3.address, + ); + + /* ── 4. Post-claim checks ────────────────────────────────────────── */ + const stakeBaseAfter = await contracts.stakingStorage.getDelegatorStakeBase( + node1Id, + d3Key, + ); // must stay 30 000 + const rollingRewards = + await contracts.delegatorsInfo.getDelegatorRollingRewards( + node1Id, + accounts.delegator3.address, + ); + const lastClaimedAfter = await contracts.delegatorsInfo.getLastClaimedEpoch( + node1Id, + accounts.delegator3.address, + ); + + expect( + stakeBaseAfter, + 'stakeBase unchanged while older epochs remain', + ).to.equal(stakeBaseBefore); + expect(rollingRewards, 'rollingRewards incorrect').to.equal( + expectedReward17, + ); + expect(lastClaimedAfter, 'lastClaimedEpoch not updated').to.equal( + claimEpoch17, + ); + + console.log( + ` ✅ rollingRewards = ${ethers.formatUnits(rollingRewards, 18)} TRAC`, + `\n ✅ lastClaimedEpoch = ${lastClaimedAfter}\n`, + ); + + /********************************************************************** + * STEP 18 – Delegator 3 claims rewards for epoch 2 + * -------------------------------------------------------------------- + **********************************************************************/ + console.log('\n💰 STEP 18: Delegator3 claims rewards for epoch 3'); + + const claimEpoch18 = 3n; + + /* ── 1. PRE-CONDITIONS ──────────────────────────────────────────────── */ + const d3LastClaimedBefore18 = + await contracts.delegatorsInfo.getLastClaimedEpoch( + node1Id, + accounts.delegator3.address, + ); + + // Must be claiming the oldest pending epoch (1 → 2) + expect(d3LastClaimedBefore18).to.equal( + claimEpoch18 - 1n, + 'Delegator-3 is skipping an older unclaimed epoch', + ); + + /* ── 2. MANUAL REWARD CALCULATION ───────────────────────────────────── */ + const d3BaseBefore18 = await contracts.stakingStorage.getDelegatorStakeBase( + node1Id, + d3Key, + ); + const d3RollingBefore18 = + await contracts.delegatorsInfo.getDelegatorRollingRewards( + node1Id, + accounts.delegator3.address, + ); + + const nodeScoreEp2 = + await contracts.randomSamplingStorage.getNodeEpochScore( + claimEpoch18, + node1Id, + ); + const perStakeEp2 = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + claimEpoch18, + node1Id, + ); + + const d3LastSettledEp2 = + await contracts.randomSamplingStorage.getDelegatorLastSettledNodeEpochScorePerStake( + claimEpoch18, + node1Id, + d3Key, + ); + const d3StoredScoreEp2 = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + claimEpoch18, + node1Id, + d3Key, + ); + + /* "lazy-settle" part that will be written inside claim() */ + const d3EarnedScore = + (d3BaseBefore18 * (perStakeEp2 - d3LastSettledEp2)) / SCALE18; + const d3TotalScore = d3StoredScoreEp2 + d3EarnedScore; + + const netDelegatorRewardsEp2 = await contracts.stakingKPI.getNetNodeRewards( + node1Id, + claimEpoch18, + ); + + // New reward for epoch 2 + const rewardEp2 = + nodeScoreEp2 === 0n + ? 0n + : (d3TotalScore * netDelegatorRewardsEp2) / nodeScoreEp2; + + // ► what will actually be auto-staked: + const expectedStakeIncrease18 = d3RollingBefore18 + rewardEp2; + + /* ── 3. CLAIM TRANSACTION ───────────────────────────────────────────── */ + await contracts.staking + .connect(accounts.delegator3) + .claimDelegatorRewards( + node1Id, + claimEpoch18, + accounts.delegator3.address, + ); + + /* ── 4. POST-CLAIM SNAPSHOT ─────────────────────────────────────────── */ + const d3BaseAfter18 = await contracts.stakingStorage.getDelegatorStakeBase( + node1Id, + d3Key, + ); + const d3RollingAfter18 = + await contracts.delegatorsInfo.getDelegatorRollingRewards( + node1Id, + accounts.delegator3.address, + ); + const d3LastClaimedAfter18 = + await contracts.delegatorsInfo.getLastClaimedEpoch( + node1Id, + accounts.delegator3.address, + ); + const nodeStakeAfter18 = + await contracts.stakingStorage.getNodeStake(node1Id); + + /* ── 5. ASSERTIONS ──────────────────────────────────────────────────── */ + expect( + d3BaseAfter18 - d3BaseBefore18, + 'auto-staked amount mismatch', + ).to.equal(expectedStakeIncrease18); + + expect(d3RollingAfter18, 'rollingRewards should now be 0').to.equal(0n); + + expect(d3LastClaimedAfter18, 'lastClaimedEpoch not updated').to.equal( + claimEpoch18, + ); + + // nodeStakeAfter14 must be in scope from previous step + expect(nodeStakeAfter18).to.equal( + nodeStakeAfter15 + expectedStakeIncrease18, + 'Node total stake should include D3 reward', + ); + + console.log( + ` 🧮 reward(epoch3) = ${ethers.formatUnits(rewardEp2, 18)} TRAC`, + `\n 🧮 rolling(before) = ${ethers.formatUnits(d3RollingBefore18, 18)} TRAC`, + `\n ✅ total reward = ${ethers.formatUnits(expectedStakeIncrease18, 18)} TRAC`, + ); + console.log( + ` ✅ new D3 stakeBase = ${ethers.formatUnits(d3BaseAfter18, 18)} TRAC`, + `\n ✅ rolling(after) = ${ethers.formatUnits(d3RollingAfter18, 18)} TRAC`, + `\n ✅ lastClaimedEpoch = ${d3LastClaimedAfter18}\n`, + ); + + /********************************************************************** + * STEP 19 – Delegator 3 requests withdrawal of 10 000 TRAC * + **********************************************************************/ + console.log('\n📤 STEP 19: Delegator3 requests withdrawal of 10 000 TRAC'); + + await expect( + contracts.staking + .connect(accounts.delegator3) + .requestWithdrawal(node1Id, TEN_K), + ).to.be.revertedWith( + 'Must claim the previous epoch rewards before changing stake', + ); + + console.log( + ' ✅ Revert received as expected – Delegator3 must claim v6 rewards', + ); + + await contracts.staking + .connect(accounts.delegator3) + .claimDelegatorRewards(node1Id, 3, accounts.delegator3.address); + + console.log(' ✅ Delegator3 claimed v6 rewards for epoch 3'); + + /* ---------- BEFORE snapshot -------------------------------------- */ + const d3StakeBaseBefore19 = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d3Key); + const nodeStakeBefore19 = + await contracts.stakingStorage.getNodeStake(node1Id); + + // latest epoch (== 4) + const currentEpoch19 = await contracts.chronos.getCurrentEpoch(); + console.log(` ℹ️ current epoch = ${currentEpoch19}`); + + const scorePerStakeCur19 = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + currentEpoch19, + node1Id, + ); + const d3LastSettledBefore19 = + await contracts.randomSamplingStorage.getDelegatorLastSettledNodeEpochScorePerStake( + currentEpoch19, + node1Id, + d3Key, + ); + const d3ScoreBefore19 = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + currentEpoch19, + node1Id, + d3Key, + ); + + /* how much score will be lazily settled by _prepareForStakeChange() */ + const expectedScoreInc19 = calculateExpectedDelegatorScore( + d3StakeBaseBefore19, + scorePerStakeCur19, + d3LastSettledBefore19, + ); + + /* ---------- perform withdrawal request --------------------------- */ + await contracts.staking + .connect(accounts.delegator3) + .requestWithdrawal(node1Id, TEN_K); // TEN_K = 10 000 TRAC + + /* ---------- AFTER snapshot --------------------------------------- */ + const d3StakeBaseAfter19 = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d3Key); + const nodeStakeAfter19 = + await contracts.stakingStorage.getNodeStake(node1Id); + + const d3ScoreAfter19 = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + currentEpoch19, + node1Id, + d3Key, + ); + const d3LastSettledAfter19 = + await contracts.randomSamplingStorage.getDelegatorLastSettledNodeEpochScorePerStake( + currentEpoch19, + node1Id, + d3Key, + ); + + const [withdrawAmount19] = + await contracts.stakingStorage.getDelegatorWithdrawalRequest( + node1Id, + d3Key, + ); + + /* ---------- Assertions ------------------------------------------- */ + expect(withdrawAmount19).to.equal( + TEN_K, + 'withdrawal request amount mismatch', + ); + + expect(nodeStakeAfter19).to.equal( + nodeStakeBefore19 - TEN_K, + 'node total stake should fall by 10 000 TRAC', + ); + expect(d3StakeBaseAfter19).to.equal( + d3StakeBaseBefore19 - TEN_K, + 'delegator base stake should fall by 10 000 TRAC', + ); + + expect(d3ScoreAfter19).to.equal( + d3ScoreBefore19 + expectedScoreInc19, + 'delegator score must be lazily settled before stake change', + ); + expect(d3LastSettledAfter19).to.equal( + scorePerStakeCur19, + 'lastSettled index must be bumped to current nodeScorePerStake', + ); + + /* ---------- Console summary -------------------------------------- */ + console.log( + ` ✅ withdrawal request stored (${ethers.formatUnits(withdrawAmount19, 18)} TRAC)`, + ); + console.log( + ` ✅ node stake ${ethers.formatUnits(nodeStakeBefore19, 18)} → ${ethers.formatUnits(nodeStakeAfter19, 18)} TRAC`, + ); + console.log( + ` ✅ D3 stakeBase ${ethers.formatUnits(d3StakeBaseBefore19, 18)} → ${ethers.formatUnits(d3StakeBaseAfter19, 18)} TRAC`, + ); + console.log( + ` ✅ D3 epoch-score ${d3ScoreBefore19} → ${d3ScoreAfter19} (settled +${expectedScoreInc19})`, + ); + + /********************************************************************** + * STEP 20 – Jump to epoch-5 ➜ finalise withdrawal of 10 000 TRAC + **********************************************************************/ + console.log( + '\n⏭️ STEP 20: Node 1 Submit Proof for epoch-4, Jump to epoch-5 so epoch-4 is finalised and D3 finalises withdrawal', + ); + + await advanceToNextProofingPeriod(contracts); + + // 2. take a stake snapshot (needed by the helper that double-checks maths) + const stakeSnapshot = await contracts.stakingStorage.getNodeStake(node1Id); + + // 3. have node-1 submit one more proof for *epoch-4* + await submitProofAndVerifyScore( + node1Id, + accounts.node1, + contracts, + currentEpoch19, // <- epoch-4 + stakeSnapshot, + ); + + /* 1️⃣ → epoch-5 */ + const ttn = await contracts.chronos.timeUntilNextEpoch(); + await time.increase(ttn + 1n); // epoch 5 + + await createKnowledgeCollection( + accounts.kcCreator, + accounts.node1, + Number(node1Id), + receivingNodes, + receivingNodesIdentityIds, + { KnowledgeCollection: contracts.kc, Token: contracts.token }, + merkleRoot, + 'finalise-epoch4', + 1, // holders + chunkSize * 2, // byteSize - use multiple of chunkSize for proper chunk generation + 1, // replicas + toTRAC(1), // + ); + + expect(await contracts.epochStorage.lastFinalizedEpoch(1)).to.equal( + 4n, + 'Epoch-4 should now be finalised', + ); + + const epoch5 = await contracts.chronos.getCurrentEpoch(); // == 5 + console.log(` ✅ Now in epoch ${epoch5} (epoch-4 finalised)`); + expect(epoch5).to.equal(5n); + + const epoc4 = 4n; + + const netNodeRewards = await contracts.stakingKPI.getNetNodeRewards( + node1Id, + epoc4, + ); + const allDelegatorsRewards = + (await contracts.stakingKPI.getDelegatorReward( + node1Id, + epoc4, + accounts.delegator1.address, + )) + + (await contracts.stakingKPI.getDelegatorReward( + node1Id, + epoc4, + accounts.delegator2.address, + )) + + (await contracts.stakingKPI.getDelegatorReward( + node1Id, + epoc4, + accounts.delegator3.address, + )); + + await epochRewardsPoolPrecisionLoss( + contracts, + epoc4, + netNodeRewards, + allDelegatorsRewards, + ); + + /* 3️⃣ Make sure the withdrawal delay elapsed */ + const [pending20, , releaseTs20] = + await contracts.stakingStorage.getDelegatorWithdrawalRequest( + node1Id, + d3Key, + ); + + expect(pending20).to.equal(TEN_K, 'pending amount mismatch'); + + const now20 = BigInt(await time.latest()); + if (now20 < releaseTs20) await time.increase(releaseTs20 - now20 + 1n); + + /* 4️⃣ BEFORE snapshot */ + const balBefore20 = await contracts.token.balanceOf(accounts.delegator3); + const nodeStakeBefore20 = + await contracts.stakingStorage.getNodeStake(node1Id); + + /* 5️⃣ Finalise withdrawal */ + await contracts.staking + .connect(accounts.delegator3) + .finalizeWithdrawal(node1Id); + + /* 6️⃣ AFTER snapshot & asserts */ + const balAfter20 = await contracts.token.balanceOf(accounts.delegator3); + const nodeStakeAfter20 = + await contracts.stakingStorage.getNodeStake(node1Id); + const [reqAfter20] = + await contracts.stakingStorage.getDelegatorWithdrawalRequest( + node1Id, + d3Key, + ); + + expect(balAfter20 - balBefore20).to.equal(TEN_K, 'wallet diff'); + expect(nodeStakeAfter20).to.equal( + nodeStakeBefore20, + 'node stake invariant', + ); + expect(reqAfter20).to.equal(0n, 'request must be cleared'); + + console.log( + ` 🪙 +${ethers.formatUnits(TEN_K, 18)} TRAC to Delegator3 – withdrawal finalised`, + ); + + /********************************************************************** + * STEP 21 – Delegator 1 tries to stake extra 5 000 TRAC (★ must revert) + **********************************************************************/ + console.log( + '\n⛔ STEP 21: Delegator1 attempts to stake 5 000 TRAC – should revert', + ); + + /* ---------- context info ---------------------------------------- */ + const currentEpoch21 = await contracts.chronos.getCurrentEpoch(); // == epoch5 + const d1LastClaimed21 = await contracts.delegatorsInfo.getLastClaimedEpoch( + node1Id, + accounts.delegator1.address, + ); + console.log( + ` ℹ️ currentEpoch = ${currentEpoch21}, D1.lastClaimedEpoch = ${d1LastClaimed21}`, + ); + + // D1 has NOT yet claimed epoch 3 (and 4) → stake change must fail + + /* ---------- BEFORE snapshot ------------------------------------- */ + const d1StakeBaseBefore21 = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d1Key); + const nodeStakeBefore21 = + await contracts.stakingStorage.getNodeStake(node1Id); + + /* ---------- token approval -------------------------------------- */ + await contracts.token + .connect(accounts.delegator1) + .approve(await contracts.staking.getAddress(), toTRAC18(5_000)); + + /* ---------- stake tx (expect revert) ---------------------------- */ + await expect( + contracts.staking + .connect(accounts.delegator1) + .stake(node1Id, toTRAC18(5_000)), + ).to.be.revertedWith( + 'Must claim the previous epoch rewards before changing stake', + ); + + console.log( + ' ✅ Revert received – Delegator1 must first claim epoch 4 rewards', + ); + + /* ---------- AFTER snapshot -------------------------------------- */ + const d1StakeBaseAfter21 = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d1Key); + const nodeStakeAfter21 = + await contracts.stakingStorage.getNodeStake(node1Id); + + /* ---------- invariants ----------------------------------------- */ + expect(d1StakeBaseAfter21, 'D1.stakeBase should stay unchanged').to.equal( + d1StakeBaseBefore21, + ); + expect(nodeStakeAfter21, 'Node total stake should stay unchanged').to.equal( + nodeStakeBefore21, + ); + + /* ---------- console summary ------------------------------------ */ + console.log( + ` ❌ Stake blocked – D1 must claim rewards first`, + `\n ✅ D1.stakeBase remains ${ethers.formatUnits(d1StakeBaseAfter21, 18)} TRAC`, + `\n ✅ Node1.totalStake remains ${ethers.formatUnits(nodeStakeAfter21, 18)} TRAC\n`, + ); + }); + + /* ------------------------------------------------------------------ + * STEP A (Claim, Redelegate, Proof) + * ------------------------------------------------------------------ */ + it('Redelegate steps – Step A (D1 claims, redelegates N1->N2, then N1 submits proof)', async function () { + /* ------------------------------------------------------------------ + * 1. PRE-CONDITION: CLAIM PENDING REWARDS + * ------------------------------------------------------------------ */ + console.log( + '\n⏳ STEP A.1: Delegator1 claiming pending rewards for epoch 4...', + ); + + // From previous tests, we know epoch 4 is the last finalized one, + // and D1's last claim was for epoch 2. So, epochs 3 and 4 are pending. + + await contracts.staking + .connect(accounts.delegator1) + .claimDelegatorRewards(node1Id, 4n, accounts.delegator1.address); + + const d1LastClaimed = await contracts.delegatorsInfo.getLastClaimedEpoch( + node1Id, + accounts.delegator1.address, + ); + expect(d1LastClaimed).to.be.gte( + 4n, + 'Delegator1 should have claimed all pending rewards up to epoch 4', + ); + console.log( + ` ✅ Pending rewards claimed. D1 last claimed epoch is now ${d1LastClaimed}.`, + ); + + /* ------------------------------------------------------------------ + * 2. REDELEGATE N1 -> N2 (with checks and logs) + * ------------------------------------------------------------------ */ + console.log( + '\n✈️ STEP A.2: Delegator1 redelegating from Node1 to Node2...', + ); + + // Snapshot BEFORE + const stakeToMove = await contracts.stakingStorage.getDelegatorStakeBase( + node1Id, + d1Key, + ); + const n1StakeBefore = await contracts.stakingStorage.getNodeStake(node1Id); + const n2StakeBefore = await contracts.stakingStorage.getNodeStake( + nodeIds.node2Id, + ); + console.log( + ` [BEFORE] N1.total=${ethers.formatUnits( + n1StakeBefore, + 18, + )} | N2.total=${ethers.formatUnits( + n2StakeBefore, + 18, + )} | D1.stake=${ethers.formatUnits(stakeToMove, 18)}`, + ); + + // Perform Redelegate + await contracts.staking + .connect(accounts.delegator1) + .redelegate(node1Id, nodeIds.node2Id, stakeToMove); + + // Snapshot AFTER + const n1StakeAfter = await contracts.stakingStorage.getNodeStake(node1Id); + const n2StakeAfter = await contracts.stakingStorage.getNodeStake( + nodeIds.node2Id, + ); + const d1BaseN1 = await contracts.stakingStorage.getDelegatorStakeBase( + node1Id, + d1Key, + ); + const d1BaseN2 = await contracts.stakingStorage.getDelegatorStakeBase( + nodeIds.node2Id, + d1Key, + ); + const d1StillOnN1 = await contracts.delegatorsInfo.isNodeDelegator( + node1Id, + accounts.delegator1.address, + ); + const d1OnN2 = await contracts.delegatorsInfo.isNodeDelegator( + nodeIds.node2Id, + accounts.delegator1.address, + ); + + console.log( + ` [AFTER] N1.total=${ethers.formatUnits( + n1StakeAfter, + 18, + )} | N2.total=${ethers.formatUnits( + n2StakeAfter, + 18, + )} | D1.base(N1)=${d1BaseN1} | D1.base(N2)=${d1BaseN2}`, + ); + + // Assertions + expect(d1BaseN1).to.equal(0n, 'D1 should have 0 stake on N1'); + expect(d1BaseN2).to.equal(stakeToMove, 'Stake should be moved to N2'); + expect(n1StakeAfter).to.equal(n1StakeBefore - stakeToMove); + expect(n2StakeAfter).to.equal(n2StakeBefore + stakeToMove); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(d1StillOnN1).to.be.false; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(d1OnN2).to.be.true; + + // Log the crucial state for debugging Step B + const lastStakeHeldEpochN1 = + await contracts.delegatorsInfo.getLastStakeHeldEpoch( + node1Id, + accounts.delegator1.address, + ); + console.log( + ` [DEBUG] D1 on N1: isDelegator=${d1StillOnN1}, lastStakeHeldEpoch=${lastStakeHeldEpochN1}`, + ); + + console.log(' ✅ Redelegation successful.'); + + /* ------------------------------------------------------------------ + * 3. NODE 1 SUBMITS PROOF + * ------------------------------------------------------------------ */ + console.log('\n🔬 STEP A.3: Node1 submitting proof for current epoch...'); + const curEpoch = await contracts.chronos.getCurrentEpoch(); // Should be epoch 5 + expect(curEpoch).to.equal(5n); + + await advanceToNextProofingPeriod(contracts); + + await ensureNodeHasChunksThisEpoch( + node1Id, + accounts.node1, + contracts, + accounts, + receivingNodes, + receivingNodesIdentityIds, + chunkSize, + ); + + const n1StakeNow = await contracts.stakingStorage.getNodeStake(node1Id); + await submitProofAndVerifyScore( + node1Id, + accounts.node1, + contracts, + curEpoch, + n1StakeNow, + ); + console.log(' ✅ Node1 proof submitted.'); + + console.log( + ` [DEBUG2] D1 on N1: isDelegator=${d1StillOnN1}, lastStakeHeldEpoch=${lastStakeHeldEpochN1}`, + ); + + /* ------------------------------------------------------------------ + * 4. ADVANCE TO NEXT EPOCH + * ------------------------------------------------------------------ */ + console.log('\n⏭️ STEP A.4: Advancing to the next epoch...'); + const ttn5 = await contracts.chronos.timeUntilNextEpoch(); + await time.increase(ttn5 + 1n); // → epoch-6 + const epoch6 = await contracts.chronos.getCurrentEpoch(); + + await createKnowledgeCollection( + accounts.kcCreator, + accounts.node2, + Number(nodeIds.node2Id), + receivingNodes, + receivingNodesIdentityIds, + { KnowledgeCollection: contracts.kc, Token: contracts.token }, + merkleRoot, + 'test-op-id-node2-proof-stepA4', + 10, + chunkSize * 8, // byteSize - use multiple of chunkSize for proper chunk generation + 10, + toTRAC(1000), + ); + + /* Verify epoch-5 is now finalised so its rewards can be claimed */ + expect( + await contracts.epochStorage.lastFinalizedEpoch(1), + 'epoch-5 should now be finalised', + ).to.equal(5n); + + expect(epoch6).to.equal(6n); + console.log(` ✅ Advanced to epoch ${epoch6}.`); + }); + + /* ------------------------------------------------------------------ + * STEP B – redelegate all stake N2 → N1 + * ------------------------------------------------------------------ */ + it('Redelegate steps – Step B (N2 → N1)', async function () { + /* ──────────────── 1. PREPARATION & INITIAL STATE ───────────────── */ + const epoch = await contracts.chronos.getCurrentEpoch(); + console.log(`\n\n--- STEP B: Redelegate N2 -> N1 (Epoch ${epoch}) ---`); + + const d1isDelegatorN2_before = + await contracts.delegatorsInfo.isNodeDelegator( + nodeIds.node2Id, + accounts.delegator1.address, + ); + const d1LastStakeHeldN2_before = + await contracts.delegatorsInfo.getLastStakeHeldEpoch( + nodeIds.node2Id, + accounts.delegator1.address, + ); + console.log( + `🔎 [B.1] Initial D1 on N2: isDelegator=${d1isDelegatorN2_before}, lastStakeHeldEpoch=${d1LastStakeHeldN2_before}`, + ); + + const d1BaseN2_before = + await contracts.stakingStorage.getDelegatorStakeBase( + nodeIds.node2Id, + d1Key, + ); + expect( + d1BaseN2_before, + 'D1 must have stake on N2 to start Step B', + ).to.be.gt(0n); + + /* ──────────────── 2. NODE-2 SUBMITS PROOF ───────── */ + console.log(`🔬 [B.2] Node2 submitting proof...`); + + await advanceToNextProofingPeriod(contracts); + + await ensureNodeHasChunksThisEpoch( + nodeIds.node2Id, + accounts.node2, + contracts, + accounts, + receivingNodes, + receivingNodesIdentityIds, + chunkSize, + ); + + const n2Stake_beforeProof = await contracts.stakingStorage.getNodeStake( + nodeIds.node2Id, + ); + await submitProofAndVerifyScore( + nodeIds.node2Id, + accounts.node2, + contracts, + epoch, + n2Stake_beforeProof, + ); + console.log(` ✅ Node2 proof submitted.`); + + /* ──────────────── 3. REDELEGATE N2 → N1 ─────────── */ + console.log(`✈️ [B.3] D1 redelegating all stake from N2 to N1...`); + const n1Stake_beforeRedelegate = + await contracts.stakingStorage.getNodeStake(node1Id); + await contracts.staking + .connect(accounts.delegator1) + .redelegate(nodeIds.node2Id, node1Id, d1BaseN2_before); + console.log(' ✅ Redelegation transaction sent.'); + + /* ──────────────── 4. POST-SNAPSHOT & ASSERTIONS ──────────────── */ + console.log(`🔎 [B.4] Final State & Assertions...`); + + const [ + d1BaseN2_after, + d1BaseN1_after, + n2Stake_after, + n1Stake_after, + stillDelegatorOnN2, + lastStakeHeldEpochN2, + ] = await Promise.all([ + contracts.stakingStorage.getDelegatorStakeBase(nodeIds.node2Id, d1Key), + contracts.stakingStorage.getDelegatorStakeBase(node1Id, d1Key), + contracts.stakingStorage.getNodeStake(nodeIds.node2Id), + contracts.stakingStorage.getNodeStake(node1Id), + contracts.delegatorsInfo.isNodeDelegator( + nodeIds.node2Id, + accounts.delegator1.address, + ), + contracts.delegatorsInfo.getLastStakeHeldEpoch( + nodeIds.node2Id, + accounts.delegator1.address, + ), + ]); + + console.log( + ` - Final D1 on N2: isDelegator=${stillDelegatorOnN2}, lastStakeHeldEpoch=${lastStakeHeldEpochN2}`, + ); + + expect(d1BaseN2_after, 'D1 stake on N2 should now be zero').to.equal(0n); + expect(d1BaseN1_after, 'Stake must fully move to N1').to.equal( + d1BaseN2_before, + ); + expect(n2Stake_after).to.equal( + n2Stake_beforeProof - d1BaseN2_before, + 'N2 total stake should decrease by the redelegated amount', + ); + expect(n1Stake_after).to.equal( + n1Stake_beforeRedelegate + d1BaseN2_before, + 'N1 total stake should increase by the redelegated amount', + ); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(stillDelegatorOnN2, 'D1 must remain delegator on N2').to.be.true; + expect( + lastStakeHeldEpochN2, + 'lastStakeHeldEpoch mismatch, should be set to current epoch', + ).to.equal(epoch); + }); + + /** + * STEP C – Move to the next epoch, explicitly call + * _validateDelegatorEpochClaims twice (N1 ✓, N2 ✗), + * then try the real redelegate which must revert. + */ + it('STEP C – validate twice, cancelWithdrawal, then failed redelegate', async function () { + /* ────────────────────────────────────────────────────────────── + * 1️⃣ Advance exactly one epoch forward + * (make the test independent of the absolute epoch number) + * ────────────────────────────────────────────────────────────── */ + const beforeEpoch = await contracts.chronos.getCurrentEpoch(); + const ttn = await contracts.chronos.timeUntilNextEpoch(); + await time.increase(ttn + 1n); // → +1 epoch + const afterEpoch = await contracts.chronos.getCurrentEpoch(); + + expect(afterEpoch).to.equal( + beforeEpoch + 1n, + 'Epoch did not advance by exactly one', + ); + console.log(`\n🚦 STEP C: now in epoch ${afterEpoch}`); + + /* ---------------------------------------------------------------- + * 1-b) Finalise the *previous* epoch by creating a tiny KC + * (prevents "epoch not finalised" surprises in later claims) + * ---------------------------------------------------------------- */ + await createKnowledgeCollection( + accounts.kcCreator, + accounts.node1, // any node is fine – we use N1 + Number(node1Id), + receivingNodes, + receivingNodesIdentityIds, + { KnowledgeCollection: contracts.kc, Token: contracts.token }, + merkleRoot, + 'finalise-stepC', + 1, // holders + chunkSize * 2, // byteSize - use multiple of chunkSize for proper chunk generation + 1, // replicas + toTRAC(1), // 1 TRAC fee – enough to finalise + ); + + expect( + await contracts.epochStorage.lastFinalizedEpoch(1), + 'Previous epoch should now be finalised', + ).to.be.gte(afterEpoch - 1n); + + /* ---------------------------------------------------------------- + * Helper – current Delegator-1 stake on N1 (used later) + * ---------------------------------------------------------------- */ + const stakeN1_start = await contracts.stakingStorage.getDelegatorStakeBase( + node1Id, + d1Key, + ); + + /* ────────────────────────────────────────────────────────────── + * 2️⃣ Dry-run the internal validator through callStatic + * ────────────────────────────────────────────────────────────── */ + console.log('\n🔍 Manual _validateDelegatorEpochClaims checks…'); + + // 2-a) N1 – should **pass** + await expect( + contracts.staking + .connect(accounts.delegator1) + .requestWithdrawal.staticCall(node1Id, 1n), // 1 wei is enough + ).to.not.be.reverted; + console.log(' ✅ Validation on N1 passed'); + + // Make a real 1-wei withdrawal so we can cancel it immediately + await contracts.staking + .connect(accounts.delegator1) + .requestWithdrawal(node1Id, 1n); + await contracts.staking + .connect(accounts.delegator1) + .cancelWithdrawal(node1Id); + console.log(' ↩️ requestWithdrawal + cancelWithdrawal on N1 succeeded'); + + // 2-b) N2 – must **revert** + await expect( + contracts.staking + .connect(accounts.delegator1) + .requestWithdrawal.staticCall(nodeIds.node2Id, 1n), + ).to.be.revertedWith( + 'Must claim rewards up to the lastStakeHeldEpoch before changing stake', + ); + console.log(' ✅ Validation on N2 reverted as expected'); + + /* ────────────────────────────────────────────────────────────── + * 3️⃣ Attempt a real redelegate N1 ➜ N2 – must revert + * ────────────────────────────────────────────────────────────── */ + const halfStake = stakeN1_start / 2n; + console.log( + `\n↪️ Attempting to redelegate ${ethers.formatUnits(halfStake, 18)} TRAC N1 ➜ N2`, + ); + + await expect( + contracts.staking + .connect(accounts.delegator1) + .redelegate(node1Id, nodeIds.node2Id, halfStake), + ).to.be.revertedWith( + 'Must claim rewards up to the lastStakeHeldEpoch before changing stake', + ); + console.log(' ✅ Redelegate reverted – pending N2 rewards not claimed'); + + /* ────────────────────────────────────────────────────────────── + * 4️⃣ Sanity-check – stake amounts must be unchanged + * ────────────────────────────────────────────────────────────── */ + const stakeN1_end = await contracts.stakingStorage.getDelegatorStakeBase( + node1Id, + d1Key, + ); + const stakeN2_end = await contracts.stakingStorage.getDelegatorStakeBase( + nodeIds.node2Id, + d1Key, + ); + + expect(stakeN1_end).to.equal( + stakeN1_start, + 'Stake on N1 must remain unchanged', + ); + expect(stakeN2_end).to.equal(0n, 'Stake on N2 must remain zero'); + + console.log( + ` ✅ State unchanged → N1: ${ethers.formatUnits(stakeN1_end, 18)} TRAC | ` + + `N2: ${ethers.formatUnits(stakeN2_end, 18)} TRAC`, + ); + console.log(`\n🚦 STEP C: now in epoch ${afterEpoch}`); + }); + + /****************************************************************************************** + * STEP D – two un-claimed epochs, claim one, redelegate half, check rolling + /* ------------------------------------------------------------------ + * STEP D – epoch-8: claim epoch-6 on N2 (→ goes to rollingRewards), + * redelegate half of live stake N1 → N2, verify state + * ------------------------------------------------------------------ */ + it('STEP D – claim one on N2, redelegate half, check rolling', async function () { + const delegator = accounts.delegator1; + const fmt = (x: bigint) => ethers.formatUnits(x, 18); + + /* ── 0. Move to epoch-8 and finalise epoch-7 ────────────────────── */ + await time.increase((await contracts.chronos.timeUntilNextEpoch()) + 1n); // → 8 + const epoch8 = await contracts.chronos.getCurrentEpoch(); + + await createKnowledgeCollection( + accounts.kcCreator, + accounts.node1, + Number(node1Id), + receivingNodes, + receivingNodesIdentityIds, + { KnowledgeCollection: contracts.kc, Token: contracts.token }, + merkleRoot, + 'finalise-ep7', + 1, + chunkSize * 2, // byteSize - use multiple of chunkSize for proper chunk generation + 1, + toTRAC(1), + ); + expect(await contracts.epochStorage.lastFinalizedEpoch(1)).to.be.gte(7n); + + console.log( + '\n────────────── STEP D – STATE BEFORE ACTIONS ──────────────', + ); + console.log(`[D-0] Current epoch: ${epoch8}`); + + /* ── 1. Quick sanity check for claimable epochs ────────────────── */ + const lastClaimedN1 = await contracts.delegatorsInfo.getLastClaimedEpoch( + node1Id, + delegator.address, + ); // 6 + const lastClaimedN2 = await contracts.delegatorsInfo.getLastClaimedEpoch( + nodeIds.node2Id, + delegator.address, + ); // 5 + const lastStakeHeldN2 = + await contracts.delegatorsInfo.getLastStakeHeldEpoch( + nodeIds.node2Id, + delegator.address, + ); // 6 + + console.log(`[D-1] N1.lastClaimed = ${lastClaimedN1}`); + console.log(`[D-1] N2.lastClaimed = ${lastClaimedN2}`); + console.log(`[D-1] N2.lastStakeHeldEpoch = ${lastStakeHeldN2}`); + + // exactly one claimable epoch on N2 → epoch-6 + expect(lastClaimedN2 + 1n).to.equal(lastStakeHeldN2); + expect(epoch8 - lastClaimedN2).to.equal(3n); // epochs 6-8 + + /* ── 2. Claim epoch-6 on N2 (gap = 2 ⇒ reward → rollingRewards) ── */ + const [baseN2_before, rollingN2_before, nodeScore6, delegScore6, pool6] = + await Promise.all([ + contracts.stakingStorage.getDelegatorStakeBase(nodeIds.node2Id, d1Key), + contracts.delegatorsInfo.getDelegatorRollingRewards( + nodeIds.node2Id, + delegator.address, + ), + contracts.randomSamplingStorage.getNodeEpochScore(6n, nodeIds.node2Id), + contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + 6n, + nodeIds.node2Id, + d1Key, + ), + contracts.stakingKPI.getNetNodeRewards(nodeIds.node2Id, 6n), + ]); + const expectedReward6 = + nodeScore6 === 0n ? 0n : (delegScore6 * pool6) / nodeScore6; + + console.log('\n[D-2] BEFORE claim epoch-6 on N2'); + console.log(` baseN2 : ${fmt(baseN2_before)} TRAC`); + console.log(` rollingN2 : ${fmt(rollingN2_before)} TRAC`); + console.log(` expectedReward: ${fmt(expectedReward6)} TRAC`); + + await contracts.staking + .connect(delegator) + .claimDelegatorRewards(nodeIds.node2Id, 6n, delegator.address); + + const [baseN2_after, rollingN2_after, lastClaimedN2_after] = + await Promise.all([ + contracts.stakingStorage.getDelegatorStakeBase(nodeIds.node2Id, d1Key), + contracts.delegatorsInfo.getDelegatorRollingRewards( + nodeIds.node2Id, + delegator.address, + ), + contracts.delegatorsInfo.getLastClaimedEpoch( + nodeIds.node2Id, + delegator.address, + ), + ]); + + console.log('\n[D-2] AFTER claim epoch-6 on N2'); + console.log(` baseN2 : ${fmt(baseN2_after)} TRAC`); + console.log(` rollingN2 : ${fmt(rollingN2_after)} TRAC`); + console.log(` lastClaimedN2 : ${lastClaimedN2_after}`); + + // reward should sit in rollingRewards, stake stays unchanged + expect(baseN2_after).to.equal(baseN2_before, 'base stake unchanged'); + expect(rollingN2_after - rollingN2_before).to.equal( + expectedReward6, + 'rolling diff', + ); + expect(lastClaimedN2_after).to.equal(6n); + + /* ── 3. Redelegate half of live stake N1 → N2 ─────────────────── */ + const baseN1_before = await contracts.stakingStorage.getDelegatorStakeBase( + node1Id, + d1Key, + ); + const halfStake = baseN1_before / 2n; + + const [n1Total_before, n2Total_before] = await Promise.all([ + contracts.stakingStorage.getNodeStake(node1Id), + contracts.stakingStorage.getNodeStake(nodeIds.node2Id), + ]); + + console.log('\n[D-3] BEFORE redelegate'); + console.log(` baseN1 : ${fmt(baseN1_before)} TRAC`); + console.log(` baseN2 : ${fmt(baseN2_after)} TRAC`); + console.log(` halfStake : ${fmt(halfStake)} TRAC`); + + await contracts.staking + .connect(delegator) + .redelegate(node1Id, nodeIds.node2Id, halfStake); + + /* ── 4. Post-redelegate assertions & logs ──────────────────────── */ + const [ + baseN1_after, + baseN2_final, + n1Total_after, + n2Total_after, + rollingN1_final, + rollingN2_final, + ] = await Promise.all([ + contracts.stakingStorage.getDelegatorStakeBase(node1Id, d1Key), + contracts.stakingStorage.getDelegatorStakeBase(nodeIds.node2Id, d1Key), + contracts.stakingStorage.getNodeStake(node1Id), + contracts.stakingStorage.getNodeStake(nodeIds.node2Id), + contracts.delegatorsInfo.getDelegatorRollingRewards( + node1Id, + delegator.address, + ), + contracts.delegatorsInfo.getDelegatorRollingRewards( + nodeIds.node2Id, + delegator.address, + ), + ]); + + console.log('\n[D-4] AFTER redelegate'); + console.log(` baseN1 : ${fmt(baseN1_after)} TRAC`); + console.log(` baseN2 : ${fmt(baseN2_final)} TRAC`); + console.log( + ` N1 total stake: ${fmt(n1Total_before)} ➜ ${fmt(n1Total_after)} TRAC`, + ); + console.log( + ` N2 total stake: ${fmt(n2Total_before)} ➜ ${fmt(n2Total_after)} TRAC`, + ); + console.log(` rollingN1 : ${fmt(rollingN1_final)} TRAC`); + console.log(` rollingN2 : ${fmt(rollingN2_final)} TRAC\n`); + + // stake balances + expect(baseN1_after).to.equal(baseN1_before - halfStake); + expect(baseN2_final).to.equal(baseN2_after + halfStake); + expect(n1Total_after).to.equal(n1Total_before - halfStake); + expect(n2Total_after).to.equal(n2Total_before + halfStake); + + // rollingRewards must stay the same after redelegate + expect(rollingN2_final).to.equal( + rollingN2_after, + 'rolling on N2 unchanged', + ); + expect(rollingN1_final).to.equal(0n, 'rolling on N1 remains zero'); + + console.log( + ` ✔ Redelegate OK – N1:${fmt(baseN1_after)} | N2:${fmt(baseN2_final)} TRAC`, + ); + }); +}); + +describe(`Delegator Scoring`, function () { + let accounts: TestAccounts; + let contracts: TestContracts; + let nodeIds: { node1Id: bigint; node2Id: bigint }; + let node1Id: bigint; + let d1Key: string, d2Key: string; + let epoch1: bigint; // eslint-disable-line @typescript-eslint/no-unused-vars + let receivingNodes: { + operational: SignerWithAddress; + admin: SignerWithAddress; + }[]; + let receivingNodesIdentityIds: number[]; + + beforeEach(async function () { + // Setup test environment + const setup = await setupTestEnvironment(); + accounts = setup.accounts; + contracts = setup.contracts; + nodeIds = setup.nodeIds; + node1Id = nodeIds.node1Id; + + epoch1 = await contracts.chronos.getCurrentEpoch(); + + // Create delegator keys for state verification + d1Key = ethers.keccak256( + ethers.solidityPacked(['address'], [accounts.delegator1.address]), + ); + d2Key = ethers.keccak256( + ethers.solidityPacked(['address'], [accounts.delegator2.address]), + ); + + // Setup receiving nodes for KC creation + receivingNodes = [ + accounts.receiver1, + accounts.receiver2, + accounts.receiver3, + ]; + receivingNodesIdentityIds = []; + for (const recNode of receivingNodes) { + const { identityId } = await createProfile(contracts.profile, recNode); + receivingNodesIdentityIds.push(Number(identityId)); + } + + // Initialize ask system properly to prevent division by zero + // Stake some tokens to node1 to make it eligible for ask setting + await contracts.token + .connect(accounts.node1.operational) + .approve(await contracts.staking.getAddress(), toTRAC(100)); + await contracts.staking + .connect(accounts.node1.operational) + .stake(node1Id, toTRAC(100)); + + // Set ask price to establish bounds + const nodeAsk = ethers.parseUnits('0.1', 18); + await contracts.profile + .connect(accounts.node1.operational) + .updateAsk(node1Id, nodeAsk); + await contracts.ask.connect(accounts.owner).recalculateActiveSet(); + + // Create knowledge collection for reward pool + const kcTokenAmount = toTRAC(10_000); + const numberOfEpochs = 5; + await createKnowledgeCollection( + accounts.kcCreator, + accounts.node1, + Number(node1Id), + receivingNodes, + receivingNodesIdentityIds, + { KnowledgeCollection: contracts.kc, Token: contracts.token }, + merkleRoot, + 'delegator-scoring-test', + 10, + 1000, + numberOfEpochs, + kcTokenAmount, + ); + }); + + describe('Suite 1: Basic Delegator Scoring', function () { + it('1A - First-time stake (no node score yet)', async function () { + console.log('\n📊 TEST 1A: First-time stake (no node score yet)'); + + // Fresh epoch, no proofs yet + const currentEpoch = await contracts.chronos.getCurrentEpoch(); + console.log(` ℹ️ Current epoch: ${currentEpoch}`); + + // Verify no node score exists yet + const nodeScoreBefore = + await contracts.randomSamplingStorage.getNodeEpochScore( + currentEpoch, + node1Id, + ); + expect(nodeScoreBefore).to.equal( + 0n, + 'Node should have no score initially', + ); + + // Delegator1 stakes 100 TRAC + const stakeAmount = toTRAC(100); + await contracts.token + .connect(accounts.delegator1) + .approve(await contracts.staking.getAddress(), stakeAmount); + await contracts.staking + .connect(accounts.delegator1) + .stake(node1Id, stakeAmount); + + // **DELEGATOR SCORING ASSERTIONS** + const delegatorScore = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + currentEpoch, + node1Id, + d1Key, + ); + expect(delegatorScore).to.equal( + 0n, + '**epochNodeDelegatorScore should be 0 (no proofs yet)**', + ); + + const lastSettledIndex = + await contracts.randomSamplingStorage.getDelegatorLastSettledNodeEpochScorePerStake( + currentEpoch, + node1Id, + d1Key, + ); + expect(lastSettledIndex).to.equal( + 0n, + 'Last settled index should be 0 initially', + ); + + // Verify stake amounts (account for setup stake from node1.operational + delegator stake) + const delegatorStakeBase = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d1Key); + const nodeStake = await contracts.stakingStorage.getNodeStake(node1Id); + const totalStake = await contracts.stakingStorage.getTotalStake(); + const setupStake = toTRAC(100); // from beforeEach setup + const expectedNodeStake = setupStake + stakeAmount; + + expect(delegatorStakeBase).to.equal( + stakeAmount, + 'stakeBase should equal delegator stake amount', + ); + expect(nodeStake).to.equal( + expectedNodeStake, + 'node stake should equal setup stake + delegator stake', + ); + expect(totalStake).to.equal( + expectedNodeStake, + 'total stake should equal node stake', + ); + + console.log(` ✅ Delegator score: ${delegatorScore} (expected: 0)`); + console.log( + ` ✅ Stake base: ${ethers.formatUnits(delegatorStakeBase, 18)} TRAC`, + ); + console.log( + ` ✅ Node stake: ${ethers.formatUnits(nodeStake, 18)} TRAC`, + ); + }); + + it('1B - Proof, then same delegator stakes more', async function () { + console.log('\n🔬 TEST 1B: Proof, then same delegator stakes more'); + + const currentEpoch = await contracts.chronos.getCurrentEpoch(); + + // Step 1: Initial stake + const initialStake = toTRAC(100); + await contracts.token + .connect(accounts.delegator1) + .approve(await contracts.staking.getAddress(), initialStake); + await contracts.staking + .connect(accounts.delegator1) + .stake(node1Id, initialStake); + + // Step 2: Node submits proof - Record index₀ + await contracts.randomSampling.updateAndGetActiveProofPeriodStartBlock(); + const setupStake = toTRAC(100); // from beforeEach setup + const totalStakeForScore = setupStake + initialStake; + const { nodeScorePerStake: index0 } = await submitProofAndVerifyScore( + node1Id, + accounts.node1, + contracts, + currentEpoch, + totalStakeForScore, + ); + console.log(` 📋 Recorded index₀: ${index0}`); + + // Step 3: Delegator stakes more - Record index₁ + const additionalStake = toTRAC(50); + await contracts.token + .connect(accounts.delegator1) + .approve(await contracts.staking.getAddress(), additionalStake); + + await contracts.staking + .connect(accounts.delegator1) + .stake(node1Id, additionalStake); + + const index1 = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + currentEpoch, + node1Id, + ); + console.log(` 📋 Recorded nodeScorePerStake: ${index1}`); + + // **DELEGATOR SCORING ASSERTIONS** + const SCALE18 = ethers.parseUnits('1', 18); + const expectedDeltaScore = + (initialStake * (index1 - BigInt(0))) / SCALE18; // index₀ was 0 when delegator first staked + const actualDelegatorScore = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + currentEpoch, + node1Id, + d1Key, + ); + + console.log( + ` 🧮 Expected Δscore: 100 TRAC × (${index1} - 0) / 1e18 = ${expectedDeltaScore}`, + ); + console.log(` 🧮 Actual delegator score: ${actualDelegatorScore}`); + + expect(actualDelegatorScore).to.equal( + expectedDeltaScore, + '**Δscore should equal 100 · (index₁−index₀)/1e18**', + ); + + // Last-settled index should be updated to current index + const lastSettledIndex = + await contracts.randomSamplingStorage.getDelegatorLastSettledNodeEpochScorePerStake( + currentEpoch, + node1Id, + d1Key, + ); + expect(lastSettledIndex).to.equal( + index1, + '**Last-settled index should equal index₁**', + ); + + // Stake base should be 150 TRAC + const finalStakeBase = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d1Key); + expect(finalStakeBase).to.equal( + initialStake + additionalStake, + '**stakeBase should be 150 TRAC**', + ); + + console.log( + ` ✅ Delegator score correctly calculated: ${actualDelegatorScore}`, + ); + console.log(` ✅ Last settled index: ${lastSettledIndex}`); + console.log( + ` ✅ Final stake base: ${ethers.formatUnits(finalStakeBase, 18)} TRAC`, + ); + }); + + it('1C - Partial withdrawal mid-epoch', async function () { + console.log('\n📤 TEST 1C: Partial withdrawal mid-epoch'); + + const currentEpoch = await contracts.chronos.getCurrentEpoch(); + + // Setup from 1B: stake 100, proof, stake +50 + const initialStake = toTRAC(100); + await contracts.token + .connect(accounts.delegator1) + .approve(await contracts.staking.getAddress(), initialStake); + await contracts.staking + .connect(accounts.delegator1) + .stake(node1Id, initialStake); + + await contracts.randomSampling.updateAndGetActiveProofPeriodStartBlock(); + const setupStake = toTRAC(100); // from beforeEach setup + await submitProofAndVerifyScore( + node1Id, + accounts.node1, + contracts, + currentEpoch, + setupStake + initialStake, + ); + + const additionalStake = toTRAC(50); + await contracts.token + .connect(accounts.delegator1) + .approve(await contracts.staking.getAddress(), additionalStake); + await contracts.staking + .connect(accounts.delegator1) + .stake(node1Id, additionalStake); + + // Record state before withdrawal + const delegatorScoreBefore = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + currentEpoch, + node1Id, + d1Key, + ); + const lastSettledIndexBefore = + await contracts.randomSamplingStorage.getDelegatorLastSettledNodeEpochScorePerStake( + currentEpoch, + node1Id, + d1Key, + ); + const currentIndex = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + currentEpoch, + node1Id, + ); + + console.log(` 📊 Score before withdrawal: ${delegatorScoreBefore}`); + console.log( + ` 📊 Last settled index before: ${lastSettledIndexBefore}`, + ); + console.log(` 📊 Current index: ${currentIndex}`); + + // Partial withdrawal of 25 TRAC + const withdrawalAmount = toTRAC(25); + await contracts.staking + .connect(accounts.delegator1) + .requestWithdrawal(node1Id, withdrawalAmount); + + // **DELEGATOR SCORING ASSERTIONS** + const delegatorScoreAfter = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + currentEpoch, + node1Id, + d1Key, + ); + const lastSettledIndexAfter = + await contracts.randomSamplingStorage.getDelegatorLastSettledNodeEpochScorePerStake( + currentEpoch, + node1Id, + d1Key, + ); + const currentIndexAfter = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + currentEpoch, + node1Id, + ); + + // Score should be unchanged from before withdrawal + expect(delegatorScoreAfter).to.equal( + delegatorScoreBefore, + '**Delegator score should be unchanged**', + ); + + // Last-settled index should be updated to current index + expect(lastSettledIndexAfter).to.equal( + currentIndexAfter, + '**Last-settled index should equal current index**', + ); + + // Stake base should be 125 TRAC (150 - 25) + const finalStakeBase = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d1Key); + expect(finalStakeBase).to.equal( + toTRAC(125), + '**stakeBase should be 125 TRAC**', + ); + + console.log(` ✅ Delegator score unchanged: ${delegatorScoreAfter}`); + console.log( + ` ✅ Last settled index updated: ${lastSettledIndexAfter}`, + ); + console.log( + ` ✅ Final stake base: ${ethers.formatUnits(finalStakeBase, 18)} TRAC`, + ); + }); + + it('1D - Single-epoch reward claim & auto-restake', async function () { + console.log('\n💰 TEST 1D: Single-epoch reward claim & auto-restake'); + + const startEpoch = await contracts.chronos.getCurrentEpoch(); + + // Setup: stake and earn score in startEpoch + const initialStake = toTRAC(100); + await contracts.token + .connect(accounts.delegator1) + .approve(await contracts.staking.getAddress(), initialStake); + await contracts.staking + .connect(accounts.delegator1) + .stake(node1Id, initialStake); + + await contracts.randomSampling.updateAndGetActiveProofPeriodStartBlock(); + const setupStake = toTRAC(100); // from beforeEach setup + await submitProofAndVerifyScore( + node1Id, + accounts.node1, + contracts, + startEpoch, + setupStake + initialStake, + ); + + // Trigger score settlement by doing a minimal stake operation + await contracts.token + .connect(accounts.delegator1) + .approve(await contracts.staking.getAddress(), toTRAC(1)); + await contracts.staking + .connect(accounts.delegator1) + .stake(node1Id, toTRAC(1)); + + const delegatorScoreInStartEpoch = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + startEpoch, + node1Id, + d1Key, + ); + console.log( + ` 📊 Delegator score in epoch ${startEpoch}: ${delegatorScoreInStartEpoch}`, + ); + + // Advance to epoch E+1 + const timeUntilNext = await contracts.chronos.timeUntilNextEpoch(); + await time.increase(timeUntilNext + 1n); + const nextEpoch = await contracts.chronos.getCurrentEpoch(); + expect(nextEpoch).to.equal(startEpoch + 1n); + + // Node proof in new epoch to finalize previous epoch + await createKnowledgeCollection( + accounts.kcCreator, + accounts.node1, + Number(node1Id), + receivingNodes, + receivingNodesIdentityIds, + { KnowledgeCollection: contracts.kc, Token: contracts.token }, + merkleRoot, + 'finalize-epoch', + 1, + 1000, + 1, + toTRAC(100), + ); + + // Verify epoch is finalized + expect(await contracts.epochStorage.lastFinalizedEpoch(1)).to.be.gte( + startEpoch, + ); + + // Get data before claim + const stakeBaseBefore = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d1Key); + const nodeScore = await contracts.randomSamplingStorage.getNodeEpochScore( + startEpoch, + node1Id, + ); + const netNodeRewards = await contracts.stakingKPI.getNetNodeRewards( + node1Id, + startEpoch, + ); + + // Calculate expected reward + const expectedReward = + nodeScore === 0n + ? 0n + : (delegatorScoreInStartEpoch * netNodeRewards) / nodeScore; + console.log( + ` 🧮 Expected reward: (${delegatorScoreInStartEpoch} × ${ethers.formatUnits(netNodeRewards, 18)}) / ${nodeScore} = ${ethers.formatUnits(expectedReward, 18)} TRAC`, + ); + + // Claim rewards + await contracts.staking + .connect(accounts.delegator1) + .claimDelegatorRewards( + node1Id, + startEpoch, + accounts.delegator1.address, + ); + + // **DELEGATOR SCORING ASSERTIONS** + const stakeBaseAfter = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d1Key); + const actualReward = stakeBaseAfter - stakeBaseBefore; + + expect(actualReward).to.equal( + expectedReward, + '**Reward should equal delegatorScore × netNodeRewards / nodeScore**', + ); + + // Check that epochNodeDelegatorScore is now consumed (this is implicit as it's used in reward calculation) + const delegatorScoreAfterClaim = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + startEpoch, + node1Id, + d1Key, + ); + // Score should still be there (it's not reset, just used for calculation) + expect(delegatorScoreAfterClaim).to.equal( + delegatorScoreInStartEpoch, + '**epochNodeDelegatorScore should remain for future reference**', + ); + + // Rolling rewards should be reset (0) since gap is only 1 epoch (auto-restake) + const rollingRewards = + await contracts.delegatorsInfo.getDelegatorRollingRewards( + node1Id, + accounts.delegator1.address, + ); + expect(rollingRewards).to.equal( + 0n, + '**rollingRewards should be reset to 0 (auto-restake)**', + ); + + console.log( + ` ✅ Actual reward: ${ethers.formatUnits(actualReward, 18)} TRAC`, + ); + console.log( + ` ✅ Stake base increased: ${ethers.formatUnits(stakeBaseBefore, 18)} → ${ethers.formatUnits(stakeBaseAfter, 18)} TRAC`, + ); + console.log(` ✅ Rolling rewards reset: ${rollingRewards}`); + }); + + it('1E - New delegator joins after index>0', async function () { + console.log('\n👤 TEST 1E: New delegator joins after index>0'); + + const currentEpoch = await contracts.chronos.getCurrentEpoch(); + + // Setup: existing delegator stakes and node submits proof + const initialStake = toTRAC(100); + await contracts.token + .connect(accounts.delegator1) + .approve(await contracts.staking.getAddress(), initialStake); + await contracts.staking + .connect(accounts.delegator1) + .stake(node1Id, initialStake); + + await contracts.randomSampling.updateAndGetActiveProofPeriodStartBlock(); + const setupStake = toTRAC(100); // from beforeEach setup + await submitProofAndVerifyScore( + node1Id, + accounts.node1, + contracts, + currentEpoch, + setupStake + initialStake, + ); + + // Trigger score settlement by doing a minimal stake operation for existing delegator + await contracts.token + .connect(accounts.delegator1) + .approve(await contracts.staking.getAddress(), toTRAC(1)); + await contracts.staking + .connect(accounts.delegator1) + .stake(node1Id, toTRAC(1)); + + // Verify index > 0 after proof + const indexAfterProof = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + currentEpoch, + node1Id, + ); + expect(indexAfterProof).to.be.gt(0n, 'Index should be > 0 after proof'); + console.log(` 📊 Index after proof: ${indexAfterProof}`); + + // New delegator (delegator2) joins + const newDelegatorStake = toTRAC(60); + await contracts.token + .connect(accounts.delegator2) + .approve(await contracts.staking.getAddress(), newDelegatorStake); + await contracts.staking + .connect(accounts.delegator2) + .stake(node1Id, newDelegatorStake); + + // **DELEGATOR SCORING ASSERTIONS** + const newDelegatorScore = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + currentEpoch, + node1Id, + d2Key, + ); + expect(newDelegatorScore).to.equal( + 0n, + '**epochNodeDelegatorScore should be 0 for new delegator**', + ); + + // Last-settled index should be bumped to current value + const newDelegatorLastSettled = + await contracts.randomSamplingStorage.getDelegatorLastSettledNodeEpochScorePerStake( + currentEpoch, + node1Id, + d2Key, + ); + const currentIndex = + await contracts.randomSamplingStorage.getNodeEpochScorePerStake( + currentEpoch, + node1Id, + ); + expect(newDelegatorLastSettled).to.equal( + currentIndex, + '**Last-settled index should be bumped to current value**', + ); + + // Verify stake amounts + const newDelegatorStakeBase = + await contracts.stakingStorage.getDelegatorStakeBase(node1Id, d2Key); + expect(newDelegatorStakeBase).to.equal( + newDelegatorStake, + 'New delegator stake base should equal stake amount', + ); + + // Verify existing delegator is unaffected + const existingDelegatorScore = + await contracts.randomSamplingStorage.getEpochNodeDelegatorScore( + currentEpoch, + node1Id, + d1Key, + ); + expect(existingDelegatorScore).to.be.gt( + 0n, + 'Existing delegator should still have score', + ); + + console.log( + ` ✅ New delegator score: ${newDelegatorScore} (expected: 0)`, + ); + console.log( + ` ✅ New delegator last settled index: ${newDelegatorLastSettled}`, + ); + console.log(` ✅ Current index: ${currentIndex}`); + console.log( + ` ✅ New delegator stake base: ${ethers.formatUnits(newDelegatorStakeBase, 18)} TRAC`, + ); + console.log( + ` ✅ Existing delegator score unchanged: ${existingDelegatorScore}`, + ); + }); + }); +});