From 4eb67001cbe597dc7b1fb34ecf495d809c3308a8 Mon Sep 17 00:00:00 2001 From: NZT48 Date: Fri, 2 Feb 2024 16:31:36 +0100 Subject: [PATCH 1/3] Implement scaling down the score --- contracts/v2/scoring/LinearSum.sol | 6 ++++-- contracts/v2/utils/ScaleDownLibrary.sol | 9 +++++++++ test/v2/unit/CommitManagerV2.test.ts | 7 ++++++- test/v2/unit/LinearSum.test.ts | 7 ++++++- 4 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 contracts/v2/utils/ScaleDownLibrary.sol diff --git a/contracts/v2/scoring/LinearSum.sol b/contracts/v2/scoring/LinearSum.sol index e7c52a80..420ed79f 100644 --- a/contracts/v2/scoring/LinearSum.sol +++ b/contracts/v2/scoring/LinearSum.sol @@ -9,6 +9,7 @@ import {Indexable} from "../../v1/interface/Indexable.sol"; import {Initializable} from "../../v1/interface/Initializable.sol"; import {IProximityScoreFunctionsPair} from "../interface/IProximityScoreFunctionsPair.sol"; import {Named} from "../../v1/interface/Named.sol"; +import {ScaleDownLib} from "../utils/ScaleDownLibrary.sol"; contract LinearSum is IProximityScoreFunctionsPair, Indexable, Named, HubDependent, Initializable { event ParameterChanged(string parameterName, uint256 parameterValue); @@ -55,14 +56,15 @@ contract LinearSum is IProximityScoreFunctionsPair, Indexable, Named, HubDepende uint64 normalizedDistance = normalizeDistance(distance, maxDistance, maxNodesNumber); if (1e18 >= normalizedDistance) { - return uint40((1e18 - normalizedDistance) * w1 + normalizeStake(stake) * w2); + return + ScaleDownLib.toUint40((1e18 - normalizedDistance) * w1 + normalizeStake(stake) * w2, type(uint64).max); } else { uint64 proximityScore = (normalizedDistance - 1e18) * w1; uint64 stakeScore = normalizeStake(stake) * w2; if (stakeScore <= proximityScore) { return 0; } - return uint40(stakeScore - proximityScore); + return ScaleDownLib.toUint40(stakeScore - proximityScore, type(uint64).max); } } diff --git a/contracts/v2/utils/ScaleDownLibrary.sol b/contracts/v2/utils/ScaleDownLibrary.sol new file mode 100644 index 00000000..c19bc296 --- /dev/null +++ b/contracts/v2/utils/ScaleDownLibrary.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.16; + +library ScaleDownLib { + function toUint40(uint216 value, uint216 maxValue) internal pure returns (uint40) { + return uint40((value * type(uint40).max) / maxValue); + } +} diff --git a/test/v2/unit/CommitManagerV2.test.ts b/test/v2/unit/CommitManagerV2.test.ts index c98e28b9..945cae7f 100644 --- a/test/v2/unit/CommitManagerV2.test.ts +++ b/test/v2/unit/CommitManagerV2.test.ts @@ -160,6 +160,11 @@ describe('@v2 @unit CommitManagerV2 contract', function () { return directDistance.lt(wraparoundDistance) ? directDistance : wraparoundDistance; } + function toUint40(value: BigNumber, maxValue: BigNumber): BigNumber { + const result = value.mul(UINT40_MAX_BN).div(maxValue); + return result; + } + async function calculateScore( distance: BigNumber, stake: BigNumber, @@ -220,7 +225,7 @@ describe('@v2 @unit CommitManagerV2 contract', function () { } if (finalScore.gt(UINT40_MAX_BN)) { - finalScore = finalScore.mod(UINT40_MAX_BN.add(1)); + finalScore = toUint40(finalScore, UINT64_MAX_BN); } return finalScore; diff --git a/test/v2/unit/LinearSum.test.ts b/test/v2/unit/LinearSum.test.ts index de6f692d..5b2164eb 100644 --- a/test/v2/unit/LinearSum.test.ts +++ b/test/v2/unit/LinearSum.test.ts @@ -54,6 +54,11 @@ describe('@v2 @unit LinearSum', function () { return directDistance.lt(wraparoundDistance) ? directDistance : wraparoundDistance; } + function toUint40(value: BigNumber, maxValue: BigNumber): BigNumber { + const result = value.mul(UINT40_MAX_BN).div(maxValue); + return result; + } + async function calculateScore( distance: BigNumber, stake: BigNumber, @@ -114,7 +119,7 @@ describe('@v2 @unit LinearSum', function () { } if (finalScore.gt(UINT40_MAX_BN)) { - finalScore = finalScore.mod(UINT40_MAX_BN.add(1)); + finalScore = toUint40(finalScore, UINT64_MAX_BN); } return finalScore; From 9458ae72ffe3b81d5f2a07ffaaaaca0310b1419c Mon Sep 17 00:00:00 2001 From: NZT48 Date: Fri, 2 Feb 2024 16:51:37 +0100 Subject: [PATCH 2/3] Fix calculate score in tests --- test/v2/unit/CommitManagerV2.test.ts | 4 +--- test/v2/unit/LinearSum.test.ts | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/test/v2/unit/CommitManagerV2.test.ts b/test/v2/unit/CommitManagerV2.test.ts index 945cae7f..10e25195 100644 --- a/test/v2/unit/CommitManagerV2.test.ts +++ b/test/v2/unit/CommitManagerV2.test.ts @@ -224,9 +224,7 @@ describe('@v2 @unit CommitManagerV2 contract', function () { finalScore = BigNumber.from(0); } - if (finalScore.gt(UINT40_MAX_BN)) { - finalScore = toUint40(finalScore, UINT64_MAX_BN); - } + finalScore = toUint40(finalScore, UINT64_MAX_BN); return finalScore; } diff --git a/test/v2/unit/LinearSum.test.ts b/test/v2/unit/LinearSum.test.ts index 5b2164eb..cddeed53 100644 --- a/test/v2/unit/LinearSum.test.ts +++ b/test/v2/unit/LinearSum.test.ts @@ -117,10 +117,7 @@ describe('@v2 @unit LinearSum', function () { } else { finalScore = BigNumber.from(0); } - - if (finalScore.gt(UINT40_MAX_BN)) { - finalScore = toUint40(finalScore, UINT64_MAX_BN); - } + finalScore = toUint40(finalScore, UINT64_MAX_BN); return finalScore; } From 8a1b78347baf5e94f670e9f80b37a10bacc6a496 Mon Sep 17 00:00:00 2001 From: NZT48 Date: Fri, 2 Feb 2024 18:27:19 +0100 Subject: [PATCH 3/3] Implement value verification test for linear sum --- contracts/v2/scoring/LinearSum.sol | 4 +- test/v2/unit/LinearSum.test.ts | 145 +++++++++++++++++++++++++++-- 2 files changed, 138 insertions(+), 11 deletions(-) diff --git a/contracts/v2/scoring/LinearSum.sol b/contracts/v2/scoring/LinearSum.sol index 420ed79f..ad5736a7 100644 --- a/contracts/v2/scoring/LinearSum.sol +++ b/contracts/v2/scoring/LinearSum.sol @@ -57,14 +57,14 @@ contract LinearSum is IProximityScoreFunctionsPair, Indexable, Named, HubDepende if (1e18 >= normalizedDistance) { return - ScaleDownLib.toUint40((1e18 - normalizedDistance) * w1 + normalizeStake(stake) * w2, type(uint64).max); + ScaleDownLib.toUint40((1e18 - normalizedDistance) * w1 + normalizeStake(stake) * w2, (w1 + w2) * 1e18); } else { uint64 proximityScore = (normalizedDistance - 1e18) * w1; uint64 stakeScore = normalizeStake(stake) * w2; if (stakeScore <= proximityScore) { return 0; } - return ScaleDownLib.toUint40(stakeScore - proximityScore, type(uint64).max); + return ScaleDownLib.toUint40(stakeScore - proximityScore, (w1 + w2) * 1e18); } } diff --git a/test/v2/unit/LinearSum.test.ts b/test/v2/unit/LinearSum.test.ts index cddeed53..0f55a82c 100644 --- a/test/v2/unit/LinearSum.test.ts +++ b/test/v2/unit/LinearSum.test.ts @@ -7,6 +7,7 @@ import { BigNumber, BytesLike } from 'ethers'; import hre from 'hardhat'; import { LinearSum, ParametersStorage } from '../../../typechain'; +import { ZERO_BYTES32 } from '../../helpers/constants'; type LinearSumFixture = { accounts: SignerWithAddress[]; @@ -59,17 +60,15 @@ describe('@v2 @unit LinearSum', function () { return result; } - async function calculateScore( + async function calculateNormalizedDistance( distance: BigNumber, stake: BigNumber, maxNeighborhoodDistance: BigNumber, r2: number, nodesNumber: number, - minStake: BigNumber, - maxStake: BigNumber, ): Promise { const linearSumParams = await LinearSum.getParameters(); - const [distanceScaleFactor, stakeScaleFactor, w1, w2] = linearSumParams; + const [distanceScaleFactor] = linearSumParams; const idealMaxDistanceInNeighborhood = HASH_RING_SIZE.div(nodesNumber).mul(Math.ceil(r2 / 2)); @@ -95,10 +94,26 @@ describe('@v2 @unit LinearSum', function () { normalizedDistance = normalizedDistance.mod(UINT64_MAX_BN.add(1)); } - let normalizedStake = stakeScaleFactor.mul(stake.sub(minStake)).div(maxStake.sub(minStake)); - if (normalizedStake.gt(UINT64_MAX_BN)) { - normalizedStake = normalizedStake.mod(UINT64_MAX_BN.add(1)); - } + return normalizedDistance; + } + + async function calculateProximityScore( + distance: BigNumber, + stake: BigNumber, + maxNeighborhoodDistance: BigNumber, + r2: number, + nodesNumber: number, + ): Promise { + const linearSumParams = await LinearSum.getParameters(); + const [, , w1] = linearSumParams; + + const normalizedDistance = await calculateNormalizedDistance( + distance, + stake, + maxNeighborhoodDistance, + r2, + nodesNumber, + ); const oneEther = BigNumber.from('1000000000000000000'); @@ -107,9 +122,63 @@ describe('@v2 @unit LinearSum', function () { const proximityScore = isProximityScorePositive ? oneEther.sub(normalizedDistance).mul(w1) : normalizedDistance.sub(oneEther).mul(w1); + + return proximityScore; + } + + async function calculateStakeScore( + distance: BigNumber, + stake: BigNumber, + maxNeighborhoodDistance: BigNumber, + r2: number, + nodesNumber: number, + minStake: BigNumber, + maxStake: BigNumber, + ): Promise { + const linearSumParams = await LinearSum.getParameters(); + const [, stakeScaleFactor, , w2] = linearSumParams; + + let normalizedStake = stakeScaleFactor.mul(stake.sub(minStake)).div(maxStake.sub(minStake)); + if (normalizedStake.gt(UINT64_MAX_BN)) { + normalizedStake = normalizedStake.mod(UINT64_MAX_BN.add(1)); + } const stakeScore = normalizedStake.mul(w2); + return stakeScore; + } + + async function calculateScoreBeforeScaling( + distance: BigNumber, + stake: BigNumber, + maxNeighborhoodDistance: BigNumber, + r2: number, + nodesNumber: number, + minStake: BigNumber, + maxStake: BigNumber, + ): Promise { + const proximityScore = await calculateProximityScore(distance, stake, maxNeighborhoodDistance, r2, nodesNumber); + const normalizedDistance = await calculateNormalizedDistance( + distance, + stake, + maxNeighborhoodDistance, + r2, + nodesNumber, + ); + const stakeScore = await calculateStakeScore( + distance, + stake, + maxNeighborhoodDistance, + r2, + nodesNumber, + minStake, + maxStake, + ); + + const oneEther = BigNumber.from('1000000000000000000'); + + const isProximityScorePositive = oneEther.gte(normalizedDistance); let finalScore; + if (isProximityScorePositive) { finalScore = proximityScore.add(stakeScore); } else if (stakeScore.gte(proximityScore)) { @@ -117,7 +186,32 @@ describe('@v2 @unit LinearSum', function () { } else { finalScore = BigNumber.from(0); } - finalScore = toUint40(finalScore, UINT64_MAX_BN); + + return finalScore; + } + + async function calculateScore( + distance: BigNumber, + stake: BigNumber, + maxNeighborhoodDistance: BigNumber, + r2: number, + nodesNumber: number, + minStake: BigNumber, + maxStake: BigNumber, + ): Promise { + const linearSumParams = await LinearSum.getParameters(); + const [, , w1, w2] = linearSumParams; + const oneEther = BigNumber.from('1000000000000000000'); + const scoreWithoutScaling = await calculateScoreBeforeScaling( + distance, + stake, + maxNeighborhoodDistance, + r2, + nodesNumber, + minStake, + maxStake, + ); + const finalScore = toUint40(scoreWithoutScaling, oneEther.mul(w1 + w2)); return finalScore; } @@ -199,4 +293,37 @@ describe('@v2 @unit LinearSum', function () { } } }); + + it('Manual verification of score function', async function () { + const minStake = await ParametersStorage.minimumStake(); + const maxStake = await ParametersStorage.maximumStake(); + const peerHash = ZERO_BYTES32; + const keyHash = HASH_RING_SIZE.div(4).toHexString(); + const distance = calculateDistance(peerHash, keyHash); + expect(distance).to.be.equal(HASH_RING_SIZE.div(4)); + + const maxDistance = HASH_RING_SIZE.div(2); + const stake = minStake.mul(2); + + const proximityScore = await calculateProximityScore(distance, stake, maxDistance, 1, 1); + expect(proximityScore).to.be.equal(BigNumber.from('500000000000000000')); + + const stakeScore = await calculateStakeScore(distance, stake, maxDistance, 1, 1, minStake, maxStake); + // 50k / 1950k = 0.0256410256410256410256410256410256410256410256410256410256410256 + expect(stakeScore).to.be.equal(BigNumber.from('25641025641025641')); + + const scoreBeforeScaling = await calculateScoreBeforeScaling( + distance, + stake, + maxDistance, + 1, + 1, + minStake, + maxStake, + ); + expect(scoreBeforeScaling).to.be.equal(BigNumber.from('525641025641025641')); + + const score = await calculateScore(distance, stake, maxDistance, 1, 1, minStake, maxStake); + expect(score).to.be.equal(BigNumber.from('288974209863')); + }); });