Skip to content

Commit

Permalink
Merge pull request #221 from OriginTrail/bugfix/scale-down-score
Browse files Browse the repository at this point in the history
Scale down score to uint40
  • Loading branch information
br-41n authored Feb 2, 2024
2 parents 3b4cc22 + 8a1b783 commit 3170df6
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 16 deletions.
6 changes: 4 additions & 2 deletions contracts/v2/scoring/LinearSum.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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, (w1 + w2) * 1e18);
} 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, (w1 + w2) * 1e18);
}
}

Expand Down
9 changes: 9 additions & 0 deletions contracts/v2/utils/ScaleDownLibrary.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
9 changes: 6 additions & 3 deletions test/v2/unit/CommitManagerV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -219,9 +224,7 @@ describe('@v2 @unit CommitManagerV2 contract', function () {
finalScore = BigNumber.from(0);
}

if (finalScore.gt(UINT40_MAX_BN)) {
finalScore = finalScore.mod(UINT40_MAX_BN.add(1));
}
finalScore = toUint40(finalScore, UINT64_MAX_BN);

return finalScore;
}
Expand Down
151 changes: 140 additions & 11 deletions test/v2/unit/LinearSum.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down Expand Up @@ -54,17 +55,20 @@ describe('@v2 @unit LinearSum', function () {
return directDistance.lt(wraparoundDistance) ? directDistance : wraparoundDistance;
}

async function calculateScore(
function toUint40(value: BigNumber, maxValue: BigNumber): BigNumber {
const result = value.mul(UINT40_MAX_BN).div(maxValue);
return result;
}

async function calculateNormalizedDistance(
distance: BigNumber,
stake: BigNumber,
maxNeighborhoodDistance: BigNumber,
r2: number,
nodesNumber: number,
minStake: BigNumber,
maxStake: BigNumber,
): Promise<BigNumber> {
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));

Expand All @@ -90,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<BigNumber> {
const linearSumParams = await LinearSum.getParameters();
const [, , w1] = linearSumParams;

const normalizedDistance = await calculateNormalizedDistance(
distance,
stake,
maxNeighborhoodDistance,
r2,
nodesNumber,
);

const oneEther = BigNumber.from('1000000000000000000');

Expand All @@ -102,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<BigNumber> {
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<BigNumber> {
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)) {
Expand All @@ -113,9 +187,31 @@ describe('@v2 @unit LinearSum', function () {
finalScore = BigNumber.from(0);
}

if (finalScore.gt(UINT40_MAX_BN)) {
finalScore = finalScore.mod(UINT40_MAX_BN.add(1));
}
return finalScore;
}

async function calculateScore(
distance: BigNumber,
stake: BigNumber,
maxNeighborhoodDistance: BigNumber,
r2: number,
nodesNumber: number,
minStake: BigNumber,
maxStake: BigNumber,
): Promise<BigNumber> {
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;
}
Expand Down Expand Up @@ -197,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'));
});
});

0 comments on commit 3170df6

Please sign in to comment.