diff --git a/contracts/delegation/DelegationController.sol b/contracts/delegation/DelegationController.sol index 498d190e0..20e54778c 100644 --- a/contracts/delegation/DelegationController.sol +++ b/contracts/delegation/DelegationController.sol @@ -26,16 +26,21 @@ import "@openzeppelin/contracts/math/SafeMath.sol"; import "../Permissions.sol"; import "../SkaleToken.sol"; import "../utils/MathUtils.sol"; +import "../utils/FractionUtils.sol"; import "./DelegationPeriodManager.sol"; import "./Punisher.sol"; import "./TokenLaunchLocker.sol"; import "./TokenState.sol"; import "./ValidatorService.sol"; +import "./PartialDifferences.sol"; contract DelegationController is Permissions, ILocker { using MathUtils for uint; + using PartialDifferences for PartialDifferences.Sequence; + using PartialDifferences for PartialDifferences.Value; + using FractionUtils for FractionUtils.Fraction; enum State { PROPOSED, @@ -58,36 +63,8 @@ contract DelegationController is Permissions, ILocker { string info; } - struct PartialDifferences { - // month => diff - mapping (uint => uint) addDiff; - // month => diff - mapping (uint => uint) subtractDiff; - // month => value - mapping (uint => uint) value; - - uint firstUnprocessedMonth; - uint lastChangedMonth; - } - - struct PartialDifferencesValue { - // month => diff - mapping (uint => uint) addDiff; - // month => diff - mapping (uint => uint) subtractDiff; - - uint value; - uint firstUnprocessedMonth; - uint lastChangedMonth; - } - - struct Fraction { - uint numerator; - uint denominator; - } - struct SlashingLogEvent { - Fraction reducingCoefficient; + FractionUtils.Fraction reducingCoefficient; uint nextMonth; } @@ -103,7 +80,7 @@ contract DelegationController is Permissions, ILocker { } struct SlashingEvent { - Fraction reducingCoefficient; + FractionUtils.Fraction reducingCoefficient; uint validatorId; uint month; } @@ -154,19 +131,19 @@ contract DelegationController is Permissions, ILocker { mapping(uint => DelegationExtras) private _delegationExtras; // validatorId => sequence - mapping (uint => PartialDifferencesValue) private _delegatedToValidator; + mapping (uint => PartialDifferences.Value) private _delegatedToValidator; // validatorId => sequence - mapping (uint => PartialDifferences) private _effectiveDelegatedToValidator; + mapping (uint => PartialDifferences.Sequence) private _effectiveDelegatedToValidator; // validatorId => slashing log mapping (uint => SlashingLog) private _slashesOfValidator; // holder => sequence - mapping (address => PartialDifferencesValue) private _delegatedByHolder; + mapping (address => PartialDifferences.Value) private _delegatedByHolder; // holder => validatorId => sequence - mapping (address => mapping (uint => PartialDifferencesValue)) private _delegatedByHolderToValidator; + mapping (address => mapping (uint => PartialDifferences.Value)) private _delegatedByHolderToValidator; // holder => validatorId => sequence - mapping (address => mapping (uint => PartialDifferences)) private _effectiveDelegatedByHolderToValidator; + mapping (address => mapping (uint => PartialDifferences.Sequence)) private _effectiveDelegatedByHolderToValidator; SlashingEvent[] private _slashes; // holder => index in _slashes; @@ -199,7 +176,7 @@ contract DelegationController is Permissions, ILocker { allow("Distributor") returns (uint effectiveDelegated) { SlashingSignal[] memory slashingSignals = processSlashesWithoutSignals(holder); - effectiveDelegated = getAndUpdateValue(_effectiveDelegatedByHolderToValidator[holder][validatorId], month); + effectiveDelegated = _effectiveDelegatedByHolderToValidator[holder][validatorId].getAndUpdateValue(month); sendSlashingSignals(slashingSignals); } @@ -281,7 +258,9 @@ contract DelegationController is Permissions, ILocker { uint currentMonth = timeHelpers.getCurrentMonth(); delegations[delegationId].started = currentMonth.add(1); - _delegationExtras[delegationId].lastSlashingMonthBeforeDelegation = _slashesOfValidator[delegations[delegationId].validatorId].lastMonth; + if (_slashesOfValidator[delegations[delegationId].validatorId].lastMonth > 0) { + _delegationExtras[delegationId].lastSlashingMonthBeforeDelegation = _slashesOfValidator[delegations[delegationId].validatorId].lastMonth; + } addToDelegatedToValidator( delegations[delegationId].validatorId, @@ -375,8 +354,8 @@ contract DelegationController is Permissions, ILocker { function confiscate(uint validatorId, uint amount) external allow("Punisher") { uint currentMonth = getCurrentMonth(); - Fraction memory coefficient = reduce(_delegatedToValidator[validatorId], amount, currentMonth); - reduce(_effectiveDelegatedToValidator[validatorId], coefficient, currentMonth); + FractionUtils.Fraction memory coefficient = _delegatedToValidator[validatorId].reduce(amount, currentMonth); + _effectiveDelegatedToValidator[validatorId].reduce(coefficient, currentMonth); putToSlashingLog(_slashesOfValidator[validatorId], coefficient, currentMonth); _slashes.push(SlashingEvent({reducingCoefficient: coefficient, validatorId: validatorId, month: currentMonth})); } @@ -388,7 +367,7 @@ contract DelegationController is Permissions, ILocker { function getAndUpdateEffectiveDelegatedToValidator(uint validatorId, uint month) external allow("Distributor") returns (uint) { - return getAndUpdateValue(_effectiveDelegatedToValidator[validatorId], month); + return _effectiveDelegatedToValidator[validatorId].getAndUpdateValue(month); } function getDelegationsByValidatorLength(uint validatorId) external view returns (uint) { @@ -404,7 +383,7 @@ contract DelegationController is Permissions, ILocker { } function getAndUpdateDelegatedToValidator(uint validatorId, uint month) public allow("ValidatorService") returns (uint) { - return getAndUpdateValue(_delegatedToValidator[validatorId], month); + return _delegatedToValidator[validatorId].getAndUpdateValue(month); } function getState(uint delegationId) public view checkDelegationExists(delegationId) returns (State state) { @@ -511,31 +490,31 @@ contract DelegationController is Permissions, ILocker { } function addToDelegatedToValidator(uint validatorId, uint amount, uint month) internal { - add(_delegatedToValidator[validatorId], amount, month); + _delegatedToValidator[validatorId].add(amount, month); } function addToEffectiveDelegatedToValidator(uint validatorId, uint effectiveAmount, uint month) internal { - add(_effectiveDelegatedToValidator[validatorId], effectiveAmount, month); + _effectiveDelegatedToValidator[validatorId].add(effectiveAmount, month); } function addToDelegatedByHolder(address holder, uint amount, uint month) internal { - add(_delegatedByHolder[holder], amount, month); + _delegatedByHolder[holder].add(amount, month); } function addToDelegatedByHolderToValidator( address holder, uint validatorId, uint amount, uint month) internal { - add(_delegatedByHolderToValidator[holder][validatorId], amount, month); + _delegatedByHolderToValidator[holder][validatorId].add(amount, month); } function removeFromDelegatedByHolder(address holder, uint amount, uint month) internal { - subtract(_delegatedByHolder[holder], amount, month); + _delegatedByHolder[holder].subtract(amount, month); } function removeFromDelegatedByHolderToValidator( address holder, uint validatorId, uint amount, uint month) internal { - subtract(_delegatedByHolderToValidator[holder][validatorId], amount, month); + _delegatedByHolderToValidator[holder][validatorId].subtract(amount, month); } function addToEffectiveDelegatedByHolderToValidator( @@ -545,7 +524,7 @@ contract DelegationController is Permissions, ILocker { uint month) internal { - add(_effectiveDelegatedByHolderToValidator[holder][validatorId], effectiveAmount, month); + _effectiveDelegatedByHolderToValidator[holder][validatorId].add(effectiveAmount, month); } function removeFromEffectiveDelegatedByHolderToValidator( @@ -555,17 +534,17 @@ contract DelegationController is Permissions, ILocker { uint month) internal { - subtract(_effectiveDelegatedByHolderToValidator[holder][validatorId], effectiveAmount, month); + _effectiveDelegatedByHolderToValidator[holder][validatorId].subtract(effectiveAmount, month); } function getAndUpdateDelegatedByHolder(address holder) internal returns (uint) { uint currentMonth = getCurrentMonth(); processAllSlashes(holder); - return getAndUpdateValue(_delegatedByHolder[holder], currentMonth); + return _delegatedByHolder[holder].getAndUpdateValue(currentMonth); } function getAndUpdateDelegatedByHolderToValidator(address holder, uint validatorId, uint month) internal returns (uint) { - return getAndUpdateValue(_delegatedByHolderToValidator[holder][validatorId], month); + return _delegatedByHolderToValidator[holder][validatorId].getAndUpdateValue(month); } function addToLockedInPendingDelegations(address holder, uint amount) internal returns (uint) { @@ -618,15 +597,11 @@ contract DelegationController is Permissions, ILocker { } function removeFromDelegatedToValidator(uint validatorId, uint amount, uint month) internal { - subtract(_delegatedToValidator[validatorId], amount, month); + _delegatedToValidator[validatorId].subtract(amount, month); } function removeFromEffectiveDelegatedToValidator(uint validatorId, uint effectiveAmount, uint month) internal { - subtract(_effectiveDelegatedToValidator[validatorId], effectiveAmount, month); - } - - function init(PartialDifferences storage sequence) internal { - sequence.firstUnprocessedMonth = 0; + _effectiveDelegatedToValidator[validatorId].subtract(effectiveAmount, month); } function calculateDelegationAmountAfterSlashing(uint delegationId) internal view returns (uint) { @@ -647,232 +622,7 @@ contract DelegationController is Permissions, ILocker { return amount; } - function add(PartialDifferences storage sequence, uint diff, uint month) internal { - require(sequence.firstUnprocessedMonth <= month, "Cannot add to the past"); - if (sequence.firstUnprocessedMonth == 0) { - sequence.firstUnprocessedMonth = month; - } - sequence.addDiff[month] = sequence.addDiff[month].add(diff); - sequence.lastChangedMonth = month; - } - - function subtract(PartialDifferences storage sequence, uint diff, uint month) internal { - require(sequence.firstUnprocessedMonth <= month, "Cannot subtract from the past"); - if (sequence.firstUnprocessedMonth == 0) { - sequence.firstUnprocessedMonth = month; - } - sequence.subtractDiff[month] = sequence.subtractDiff[month].add(diff); - sequence.lastChangedMonth = month; - } - - function getAndUpdateValue(PartialDifferences storage sequence, uint month) internal returns (uint) { - if (sequence.firstUnprocessedMonth == 0) { - return 0; - } - - if (sequence.firstUnprocessedMonth <= month) { - for (uint i = sequence.firstUnprocessedMonth; i <= month; ++i) { - sequence.value[i] = sequence.value[i - 1].add(sequence.addDiff[i]).boundedSub(sequence.subtractDiff[i]); - delete sequence.addDiff[i]; - delete sequence.subtractDiff[i]; - } - sequence.firstUnprocessedMonth = month.add(1); - } - - return sequence.value[month]; - } - - function add(PartialDifferencesValue storage sequence, uint diff, uint month) internal { - require(sequence.firstUnprocessedMonth <= month, "Cannot add to the past"); - if (sequence.firstUnprocessedMonth == 0) { - sequence.firstUnprocessedMonth = month; - sequence.lastChangedMonth = month; - } - if (month > sequence.lastChangedMonth) { - sequence.lastChangedMonth = month; - } - - if (month >= sequence.firstUnprocessedMonth) { - sequence.addDiff[month] = sequence.addDiff[month].add(diff); - } else { - sequence.value = sequence.value.add(diff); - } - } - - function subtract(PartialDifferencesValue storage sequence, uint diff, uint month) internal { - require(sequence.firstUnprocessedMonth <= month.add(1), "Cannot subtract from the past"); - if (sequence.firstUnprocessedMonth == 0) { - sequence.firstUnprocessedMonth = month; - sequence.lastChangedMonth = month; - } - if (month > sequence.lastChangedMonth) { - sequence.lastChangedMonth = month; - } - - if (month >= sequence.firstUnprocessedMonth) { - sequence.subtractDiff[month] = sequence.subtractDiff[month].add(diff); - } else { - sequence.value = sequence.value.boundedSub(diff); - } - } - - function getAndUpdateValue(PartialDifferencesValue storage sequence, uint month) internal returns (uint) { - require(month.add(1) >= sequence.firstUnprocessedMonth, "Cannot calculate value in the past"); - if (sequence.firstUnprocessedMonth == 0) { - return 0; - } - - if (sequence.firstUnprocessedMonth <= month) { - for (uint i = sequence.firstUnprocessedMonth; i <= month; ++i) { - sequence.value = sequence.value.add(sequence.addDiff[i]).boundedSub(sequence.subtractDiff[i]); - delete sequence.addDiff[i]; - delete sequence.subtractDiff[i]; - } - sequence.firstUnprocessedMonth = month.add(1); - } - - return sequence.value; - } - - function reduce(PartialDifferencesValue storage sequence, uint amount, uint month) internal returns (Fraction memory) { - require(month.add(1) >= sequence.firstUnprocessedMonth, "Cannot reduce value in the past"); - if (sequence.firstUnprocessedMonth == 0) { - return createFraction(0); - } - uint value = getAndUpdateValue(sequence, month); - if (value.approximatelyEqual(0)) { - return createFraction(0); - } - - uint _amount = amount; - if (value < amount) { - _amount = value; - } - - Fraction memory reducingCoefficient = createFraction(value.boundedSub(_amount), value); - reduce(sequence, reducingCoefficient, month); - return reducingCoefficient; - } - - function reduce(PartialDifferencesValue storage sequence, Fraction memory reducingCoefficient, uint month) internal { - reduce( - sequence, - sequence, - reducingCoefficient, - month, - false); - } - - function reduce( - PartialDifferencesValue storage sequence, - PartialDifferencesValue storage sumSequence, - Fraction memory reducingCoefficient, - uint month) internal - { - reduce( - sequence, - sumSequence, - reducingCoefficient, - month, - true); - } - - function reduce( - PartialDifferencesValue storage sequence, - PartialDifferencesValue storage sumSequence, - Fraction memory reducingCoefficient, - uint month, - bool hasSumSequence) internal - { - require(month.add(1) >= sequence.firstUnprocessedMonth, "Cannot reduce value in the past"); - if (hasSumSequence) { - require(month.add(1) >= sumSequence.firstUnprocessedMonth, "Cannot reduce value in the past"); - } - require(reducingCoefficient.numerator <= reducingCoefficient.denominator, "Increasing of values is not implemented"); - if (sequence.firstUnprocessedMonth == 0) { - return; - } - uint value = getAndUpdateValue(sequence, month); - if (value.approximatelyEqual(0)) { - return; - } - - uint newValue = sequence.value.mul(reducingCoefficient.numerator).div(reducingCoefficient.denominator); - if (hasSumSequence) { - subtract(sumSequence, sequence.value.boundedSub(newValue), month); - } - sequence.value = newValue; - - for (uint i = month.add(1); i <= sequence.lastChangedMonth; ++i) { - uint newDiff = sequence.subtractDiff[i].mul(reducingCoefficient.numerator).div(reducingCoefficient.denominator); - if (hasSumSequence) { - sumSequence.subtractDiff[i] = sumSequence.subtractDiff[i].boundedSub(sequence.subtractDiff[i].boundedSub(newDiff)); - } - sequence.subtractDiff[i] = newDiff; - } - } - - function reduce( - PartialDifferences storage sequence, - Fraction memory reducingCoefficient, - uint month) internal - { - require(month.add(1) >= sequence.firstUnprocessedMonth, "Can't reduce value in the past"); - require(reducingCoefficient.numerator <= reducingCoefficient.denominator, "Increasing of values is not implemented"); - if (sequence.firstUnprocessedMonth == 0) { - return; - } - uint value = getAndUpdateValue(sequence, month); - if (value.approximatelyEqual(0)) { - return; - } - - sequence.value[month] = sequence.value[month].mul(reducingCoefficient.numerator).div(reducingCoefficient.denominator); - - for (uint i = month.add(1); i <= sequence.lastChangedMonth; ++i) { - sequence.subtractDiff[i] = sequence.subtractDiff[i].mul(reducingCoefficient.numerator).div(reducingCoefficient.denominator); - } - } - - function createFraction(uint numerator, uint denominator) internal pure returns (Fraction memory) { - require(denominator > 0, "Division by zero"); - Fraction memory fraction = Fraction({numerator: numerator, denominator: denominator}); - reduceFraction(fraction); - return fraction; - } - - function createFraction(uint value) internal pure returns (Fraction memory) { - return createFraction(value, 1); - } - - function reduceFraction(Fraction memory fraction) internal pure { - uint _gcd = gcd(fraction.numerator, fraction.denominator); - fraction.numerator = fraction.numerator.div(_gcd); - fraction.denominator = fraction.denominator.div(_gcd); - } - - function multiplyFraction(Fraction memory a, Fraction memory b) internal pure returns (Fraction memory) { - return createFraction(a.numerator.mul(b.numerator), a.denominator.mul(b.denominator)); - } - - function gcd(uint _a, uint _b) internal pure returns (uint) { - uint a = _a; - uint b = _b; - if (b > a) { - (a, b) = swap(a, b); - } - while (b > 0) { - a = a.mod(b); - (a, b) = swap (a, b); - } - return a; - } - - function swap(uint a, uint b) internal pure returns (uint, uint) { - return (b, a); - } - - function putToSlashingLog(SlashingLog storage log, Fraction memory coefficient, uint month) internal { + function putToSlashingLog(SlashingLog storage log, FractionUtils.Fraction memory coefficient, uint month) internal { if (log.firstMonth == 0) { log.firstMonth = month; log.lastMonth = month; @@ -881,7 +631,7 @@ contract DelegationController is Permissions, ILocker { } else { require(log.lastMonth <= month, "Cannot put slashing event in the past"); if (log.lastMonth == month) { - log.slashes[month].reducingCoefficient = multiplyFraction(log.slashes[month].reducingCoefficient, coefficient); + log.slashes[month].reducingCoefficient = log.slashes[month].reducingCoefficient.multiplyFraction(coefficient); } else { log.slashes[month].reducingCoefficient = coefficient; log.slashes[month].nextMonth = 0; @@ -905,13 +655,11 @@ contract DelegationController is Permissions, ILocker { uint month = _slashes[index].month; uint oldValue = getAndUpdateDelegatedByHolderToValidator(holder, validatorId, month); if (oldValue.muchGreater(0)) { - reduce( - _delegatedByHolderToValidator[holder][validatorId], + _delegatedByHolderToValidator[holder][validatorId].reduce( _delegatedByHolder[holder], _slashes[index].reducingCoefficient, month); - reduce( - _effectiveDelegatedByHolderToValidator[holder][validatorId], + _effectiveDelegatedByHolderToValidator[holder][validatorId].reduce( _slashes[index].reducingCoefficient, month); slashingSignals[index.sub(begin)].holder = holder; diff --git a/contracts/delegation/PartialDifferences.sol b/contracts/delegation/PartialDifferences.sol new file mode 100644 index 000000000..5bd4339e4 --- /dev/null +++ b/contracts/delegation/PartialDifferences.sol @@ -0,0 +1,281 @@ +/* + PartialDifferences.sol - SKALE Manager + Copyright (C) 2018-Present SKALE Labs + @author Dmytro Stebaiev + + SKALE Manager is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SKALE Manager is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with SKALE Manager. If not, see . +*/ + +pragma solidity 0.5.16; + +import "../utils/MathUtils.sol"; +import "../utils/FractionUtils.sol"; + + +library PartialDifferences { + using SafeMath for uint; + using MathUtils for uint; + + struct Sequence { + // month => diff + mapping (uint => uint) addDiff; + // month => diff + mapping (uint => uint) subtractDiff; + // month => value + mapping (uint => uint) value; + + uint firstUnprocessedMonth; + uint lastChangedMonth; + } + + struct Value { + // month => diff + mapping (uint => uint) addDiff; + // month => diff + mapping (uint => uint) subtractDiff; + + uint value; + uint firstUnprocessedMonth; + uint lastChangedMonth; + } + + // functions for sequence + + function add(Sequence storage sequence, uint diff, uint month) internal { + require(sequence.firstUnprocessedMonth <= month, "Cannot add to the past"); + if (sequence.firstUnprocessedMonth == 0) { + sequence.firstUnprocessedMonth = month; + } + sequence.addDiff[month] = sequence.addDiff[month].add(diff); + if (sequence.lastChangedMonth != month) { + sequence.lastChangedMonth = month; + } + } + + function subtract(Sequence storage sequence, uint diff, uint month) internal { + require(sequence.firstUnprocessedMonth <= month, "Cannot subtract from the past"); + if (sequence.firstUnprocessedMonth == 0) { + sequence.firstUnprocessedMonth = month; + } + sequence.subtractDiff[month] = sequence.subtractDiff[month].add(diff); + if (sequence.lastChangedMonth != month) { + sequence.lastChangedMonth = month; + } + } + + function getAndUpdateValue(Sequence storage sequence, uint month) internal returns (uint) { + if (sequence.firstUnprocessedMonth == 0) { + return 0; + } + + if (sequence.firstUnprocessedMonth <= month) { + for (uint i = sequence.firstUnprocessedMonth; i <= month; ++i) { + uint nextValue = sequence.value[i - 1].add(sequence.addDiff[i]).boundedSub(sequence.subtractDiff[i]); + if (sequence.value[i] != nextValue) { + sequence.value[i] = nextValue; + } + if (sequence.addDiff[i] > 0) { + delete sequence.addDiff[i]; + } + if (sequence.subtractDiff[i] > 0) { + delete sequence.subtractDiff[i]; + } + } + sequence.firstUnprocessedMonth = month.add(1); + } + + return sequence.value[month]; + } + + function reduce( + Sequence storage sequence, + FractionUtils.Fraction memory reducingCoefficient, + uint month) internal + { + require(month.add(1) >= sequence.firstUnprocessedMonth, "Can't reduce value in the past"); + require(reducingCoefficient.numerator <= reducingCoefficient.denominator, "Increasing of values is not implemented"); + if (sequence.firstUnprocessedMonth == 0) { + return; + } + uint value = getAndUpdateValue(sequence, month); + if (value.approximatelyEqual(0)) { + return; + } + + sequence.value[month] = sequence.value[month].mul(reducingCoefficient.numerator).div(reducingCoefficient.denominator); + + for (uint i = month.add(1); i <= sequence.lastChangedMonth; ++i) { + sequence.subtractDiff[i] = sequence.subtractDiff[i].mul(reducingCoefficient.numerator).div(reducingCoefficient.denominator); + } + } + + // functions for value + + function add(Value storage sequence, uint diff, uint month) internal { + require(sequence.firstUnprocessedMonth <= month, "Cannot add to the past"); + if (sequence.firstUnprocessedMonth == 0) { + sequence.firstUnprocessedMonth = month; + sequence.lastChangedMonth = month; + } + if (month > sequence.lastChangedMonth) { + sequence.lastChangedMonth = month; + } + + if (month >= sequence.firstUnprocessedMonth) { + sequence.addDiff[month] = sequence.addDiff[month].add(diff); + } else { + sequence.value = sequence.value.add(diff); + } + } + + function subtract(Value storage sequence, uint diff, uint month) internal { + require(sequence.firstUnprocessedMonth <= month.add(1), "Cannot subtract from the past"); + if (sequence.firstUnprocessedMonth == 0) { + sequence.firstUnprocessedMonth = month; + sequence.lastChangedMonth = month; + } + if (month > sequence.lastChangedMonth) { + sequence.lastChangedMonth = month; + } + + if (month >= sequence.firstUnprocessedMonth) { + sequence.subtractDiff[month] = sequence.subtractDiff[month].add(diff); + } else { + sequence.value = sequence.value.boundedSub(diff); + } + } + + function getAndUpdateValue(Value storage sequence, uint month) internal returns (uint) { + require(month.add(1) >= sequence.firstUnprocessedMonth, "Cannot calculate value in the past"); + if (sequence.firstUnprocessedMonth == 0) { + return 0; + } + + if (sequence.firstUnprocessedMonth <= month) { + for (uint i = sequence.firstUnprocessedMonth; i <= month; ++i) { + uint newValue = sequence.value.add(sequence.addDiff[i]).boundedSub(sequence.subtractDiff[i]); + if (sequence.value != newValue) { + sequence.value = newValue; + } + if (sequence.addDiff[i] > 0) { + delete sequence.addDiff[i]; + } + if (sequence.subtractDiff[i] > 0) { + delete sequence.subtractDiff[i]; + } + } + sequence.firstUnprocessedMonth = month.add(1); + } + + return sequence.value; + } + + function reduce(Value storage sequence, uint amount, uint month) internal returns (FractionUtils.Fraction memory) { + require(month.add(1) >= sequence.firstUnprocessedMonth, "Cannot reduce value in the past"); + if (sequence.firstUnprocessedMonth == 0) { + return FractionUtils.createFraction(0); + } + uint value = getAndUpdateValue(sequence, month); + if (value.approximatelyEqual(0)) { + return FractionUtils.createFraction(0); + } + + uint _amount = amount; + if (value < amount) { + _amount = value; + } + + FractionUtils.Fraction memory reducingCoefficient = FractionUtils.createFraction(value.boundedSub(_amount), value); + reduce(sequence, reducingCoefficient, month); + return reducingCoefficient; + } + + function reduce(Value storage sequence, FractionUtils.Fraction memory reducingCoefficient, uint month) internal { + reduce( + sequence, + sequence, + reducingCoefficient, + month, + false); + } + + function reduce( + Value storage sequence, + Value storage sumSequence, + FractionUtils.Fraction memory reducingCoefficient, + uint month) internal + { + reduce( + sequence, + sumSequence, + reducingCoefficient, + month, + true); + } + + function reduce( + Value storage sequence, + Value storage sumSequence, + FractionUtils.Fraction memory reducingCoefficient, + uint month, + bool hasSumSequence) internal + { + require(month.add(1) >= sequence.firstUnprocessedMonth, "Cannot reduce value in the past"); + if (hasSumSequence) { + require(month.add(1) >= sumSequence.firstUnprocessedMonth, "Cannot reduce value in the past"); + } + require(reducingCoefficient.numerator <= reducingCoefficient.denominator, "Increasing of values is not implemented"); + if (sequence.firstUnprocessedMonth == 0) { + return; + } + uint value = getAndUpdateValue(sequence, month); + if (value.approximatelyEqual(0)) { + return; + } + + uint newValue = sequence.value.mul(reducingCoefficient.numerator).div(reducingCoefficient.denominator); + if (hasSumSequence) { + subtract(sumSequence, sequence.value.boundedSub(newValue), month); + } + sequence.value = newValue; + + for (uint i = month.add(1); i <= sequence.lastChangedMonth; ++i) { + uint newDiff = sequence.subtractDiff[i].mul(reducingCoefficient.numerator).div(reducingCoefficient.denominator); + if (hasSumSequence) { + sumSequence.subtractDiff[i] = sumSequence.subtractDiff[i].boundedSub(sequence.subtractDiff[i].boundedSub(newDiff)); + } + sequence.subtractDiff[i] = newDiff; + } + } + + function clear(Value storage sequence) internal { + for (uint i = sequence.firstUnprocessedMonth; i <= sequence.lastChangedMonth; ++i) { + if (sequence.addDiff[i] > 0) { + delete sequence.addDiff[i]; + } + if (sequence.subtractDiff[i] > 0) { + delete sequence.subtractDiff[i]; + } + } + if (sequence.value > 0) { + delete sequence.value; + } + if (sequence.firstUnprocessedMonth > 0) { + delete sequence.firstUnprocessedMonth; + } + if (sequence.lastChangedMonth > 0) { + delete sequence.lastChangedMonth; + } + } +} \ No newline at end of file diff --git a/contracts/delegation/TokenLaunchLocker.sol b/contracts/delegation/TokenLaunchLocker.sol index f5da72614..d4f1d957e 100644 --- a/contracts/delegation/TokenLaunchLocker.sol +++ b/contracts/delegation/TokenLaunchLocker.sol @@ -28,10 +28,12 @@ import "../utils/MathUtils.sol"; import "./DelegationController.sol"; import "./TimeHelpers.sol"; +import "./PartialDifferences.sol"; contract TokenLaunchLocker is Permissions, ILocker { using MathUtils for uint; + using PartialDifferences for PartialDifferences.Value; event Unlocked( address holder, @@ -43,17 +45,6 @@ contract TokenLaunchLocker is Permissions, ILocker { uint amount ); - struct PartialDifferencesValue { - // month => diff - mapping (uint => uint) addDiff; - // month => diff - mapping (uint => uint) subtractDiff; - - uint value; - uint firstUnprocessedMonth; - uint lastChangedMonth; - } - struct DelegatedAmountAndMonth { uint delegated; uint month; @@ -63,7 +54,7 @@ contract TokenLaunchLocker is Permissions, ILocker { mapping (address => uint) private _locked; // holder => tokens - mapping (address => PartialDifferencesValue) private _delegatedAmount; + mapping (address => PartialDifferences.Value) private _delegatedAmount; mapping (address => DelegatedAmountAndMonth) private _totalDelegatedAmount; @@ -142,15 +133,15 @@ contract TokenLaunchLocker is Permissions, ILocker { // private function getAndUpdateDelegatedAmount(address holder, uint currentMonth) internal returns (uint) { - return getAndUpdateValue(_delegatedAmount[holder], currentMonth); + return _delegatedAmount[holder].getAndUpdateValue(currentMonth); } function addToDelegatedAmount(address holder, uint amount, uint month) internal { - add(_delegatedAmount[holder], amount, month); + _delegatedAmount[holder].add(amount, month); } function removeFromDelegatedAmount(address holder, uint amount, uint month) internal { - subtract(_delegatedAmount[holder], amount, month); + _delegatedAmount[holder].subtract(amount, month); } function addToTotalDelegatedAmount(address holder, uint amount, uint month) internal { @@ -174,73 +165,11 @@ contract TokenLaunchLocker is Permissions, ILocker { } function deleteDelegatedAmount(address holder) internal { - deletePartialDifferencesValue(_delegatedAmount[holder]); + _delegatedAmount[holder].clear(); } function deleteTotalDelegatedAmount(address holder) internal { delete _totalDelegatedAmount[holder].delegated; delete _totalDelegatedAmount[holder].month; } - - function add(PartialDifferencesValue storage sequence, uint diff, uint month) internal { - require(sequence.firstUnprocessedMonth <= month, "Cannot add to the past"); - if (sequence.firstUnprocessedMonth == 0) { - sequence.firstUnprocessedMonth = month; - sequence.lastChangedMonth = month; - } - if (month > sequence.lastChangedMonth) { - sequence.lastChangedMonth = month; - } - - if (month >= sequence.firstUnprocessedMonth) { - sequence.addDiff[month] = sequence.addDiff[month].add(diff); - } else { - sequence.value = sequence.value.add(diff); - } - } - - function subtract(PartialDifferencesValue storage sequence, uint diff, uint month) internal { - require(sequence.firstUnprocessedMonth <= month.add(1), "Cannot subtract from the past"); - if (sequence.firstUnprocessedMonth == 0) { - sequence.firstUnprocessedMonth = month; - sequence.lastChangedMonth = month; - } - if (month > sequence.lastChangedMonth) { - sequence.lastChangedMonth = month; - } - - if (month >= sequence.firstUnprocessedMonth) { - sequence.subtractDiff[month] = sequence.subtractDiff[month].add(diff); - } else { - sequence.value = sequence.value.boundedSub(diff); - } - } - - function getAndUpdateValue(PartialDifferencesValue storage sequence, uint month) internal returns (uint) { - require(month.add(1) >= sequence.firstUnprocessedMonth, "Cannot calculate value in the past"); - if (sequence.firstUnprocessedMonth == 0) { - return 0; - } - - if (sequence.firstUnprocessedMonth <= month) { - for (uint i = sequence.firstUnprocessedMonth; i <= month; ++i) { - sequence.value = sequence.value.add(sequence.addDiff[i]).boundedSub(sequence.subtractDiff[i]); - delete sequence.addDiff[i]; - delete sequence.subtractDiff[i]; - } - sequence.firstUnprocessedMonth = month.add(1); - } - - return sequence.value; - } - - function deletePartialDifferencesValue(PartialDifferencesValue storage sequence) internal { - for (uint i = sequence.firstUnprocessedMonth; i <= sequence.lastChangedMonth; ++i) { - delete sequence.addDiff[i]; - delete sequence.subtractDiff[i]; - } - delete sequence.value; - delete sequence.firstUnprocessedMonth; - delete sequence.lastChangedMonth; - } } \ No newline at end of file diff --git a/contracts/delegation/TokenLaunchManager.sol b/contracts/delegation/TokenLaunchManager.sol index 1ed49c247..875ca1d70 100644 --- a/contracts/delegation/TokenLaunchManager.sol +++ b/contracts/delegation/TokenLaunchManager.sol @@ -64,7 +64,7 @@ contract TokenLaunchManager is Permissions, IERC777Recipient { function retrieve() external { require(approved[_msgSender()] > 0, "Transfer is not approved"); uint value = approved[_msgSender()]; - approved[_msgSender()] = 0; + delete approved[_msgSender()]; require(IERC20(contractManager.getContract("SkaleToken")).transfer(_msgSender(), value), "Error of token sending"); TokenLaunchLocker(contractManager.getContract("TokenLaunchLocker")).lock(_msgSender(), value); emit TokensRetrieved(_msgSender(), value); diff --git a/contracts/delegation/ValidatorService.sol b/contracts/delegation/ValidatorService.sol index 4617bb12b..3467a9536 100644 --- a/contracts/delegation/ValidatorService.sol +++ b/contracts/delegation/ValidatorService.sol @@ -115,11 +115,13 @@ contract ValidatorService is Permissions { } function enableValidator(uint validatorId) external checkValidatorExists(validatorId) onlyOwner { + require(!trustedValidators[validatorId], "Validator is already enabled"); trustedValidators[validatorId] = true; emit ValidatorWasEnabled(validatorId); } function disableValidator(uint validatorId) external checkValidatorExists(validatorId) onlyOwner { + require(trustedValidators[validatorId], "Validator is already disabled"); trustedValidators[validatorId] = false; emit ValidatorWasDisabled(validatorId); } @@ -156,7 +158,7 @@ contract ValidatorService is Permissions { getValidator(validatorId).requestedAddress == msg.sender, "The validator address cannot be changed because it is not the actual owner" ); - validators[validatorId].requestedAddress = address(0); + delete validators[validatorId].requestedAddress; setValidatorAddress(validatorId, msg.sender); emit ValidatorAddressChanged(validatorId, validators[validatorId].validatorAddress); diff --git a/contracts/test/PartialDifferencesTester.sol b/contracts/test/PartialDifferencesTester.sol new file mode 100644 index 000000000..b85e03465 --- /dev/null +++ b/contracts/test/PartialDifferencesTester.sol @@ -0,0 +1,67 @@ +/* + PartialDifferencesTester.sol - SKALE Manager + Copyright (C) 2018-Present SKALE Labs + @author Dmytro Stebaiev + + SKALE Manager is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SKALE Manager is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with SKALE Manager. If not, see . +*/ + +pragma solidity 0.5.16; + +import "../delegation/PartialDifferences.sol"; + + +contract PartialDifferencesTester { + using PartialDifferences for PartialDifferences.Sequence; + using PartialDifferences for PartialDifferences.Value; + + PartialDifferences.Sequence[] sequences; + PartialDifferences.Value[] values; + + function createSequence() external returns (uint id) { + id = sequences.length; + ++sequences.length; + } + + function latestSequence() external view returns (uint id) { + require(sequences.length > 0, "There are no sequences"); + return sequences.length - 1; + } + + function addToSequence(uint sequence, uint diff, uint month) external { + require(sequence < sequences.length, "Sequence does not exist"); + sequences[sequence].add(diff, month); + } + + function subtractFromSequence(uint sequence, uint diff, uint month) external { + require(sequence < sequences.length, "Sequence does not exist"); + sequences[sequence].subtract(diff, month); + } + + function getAndUpdateSequenceItem(uint sequence, uint month) external returns (uint) { + require(sequence < sequences.length, "Sequence does not exist"); + return sequences[sequence].getAndUpdateValue(month); + } + + function reduceSequence( + uint sequence, + uint a, + uint b, + uint month) external + { + require(sequence < sequences.length, "Sequence does not exist"); + FractionUtils.Fraction memory reducingCoefficient = FractionUtils.createFraction(a, b); + return sequences[sequence].reduce(reducingCoefficient, month); + } +} \ No newline at end of file diff --git a/contracts/utils/FractionUtils.sol b/contracts/utils/FractionUtils.sol new file mode 100644 index 000000000..91bc77110 --- /dev/null +++ b/contracts/utils/FractionUtils.sol @@ -0,0 +1,70 @@ +/* + FractionUtils.sol - SKALE Manager + Copyright (C) 2018-Present SKALE Labs + @author Dmytro Stebaiev + + SKALE Manager is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SKALE Manager is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with SKALE Manager. If not, see . +*/ + +pragma solidity 0.5.16; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + + +library FractionUtils { + using SafeMath for uint; + + struct Fraction { + uint numerator; + uint denominator; + } + + function createFraction(uint numerator, uint denominator) internal pure returns (Fraction memory) { + require(denominator > 0, "Division by zero"); + Fraction memory fraction = Fraction({numerator: numerator, denominator: denominator}); + reduceFraction(fraction); + return fraction; + } + + function createFraction(uint value) internal pure returns (Fraction memory) { + return createFraction(value, 1); + } + + function reduceFraction(Fraction memory fraction) internal pure { + uint _gcd = gcd(fraction.numerator, fraction.denominator); + fraction.numerator = fraction.numerator.div(_gcd); + fraction.denominator = fraction.denominator.div(_gcd); + } + + function multiplyFraction(Fraction memory a, Fraction memory b) internal pure returns (Fraction memory) { + return createFraction(a.numerator.mul(b.numerator), a.denominator.mul(b.denominator)); + } + + function gcd(uint _a, uint _b) internal pure returns (uint) { + uint a = _a; + uint b = _b; + if (b > a) { + (a, b) = swap(a, b); + } + while (b > 0) { + a = a.mod(b); + (a, b) = swap (a, b); + } + return a; + } + + function swap(uint a, uint b) internal pure returns (uint, uint) { + return (b, a); + } +} \ No newline at end of file diff --git a/test/delegation/PartialDifferences.ts b/test/delegation/PartialDifferences.ts new file mode 100644 index 000000000..f90dd9805 --- /dev/null +++ b/test/delegation/PartialDifferences.ts @@ -0,0 +1,55 @@ +import { deployContractManager } from "../tools/deploy/contractManager"; +import { deployPartialDifferencesTester } from "../tools/deploy/test/partialDifferencesTester"; +import { PartialDifferencesTesterInstance } from "../../types/truffle-contracts"; +import * as chai from "chai"; +import * as chaiAsPromised from "chai-as-promised"; + +chai.should(); +chai.use(chaiAsPromised); + +contract("PartialDifferences", ([owner]) => { + let contractManager; + let partialDifferencesTester: PartialDifferencesTesterInstance; + before(async () => { + contractManager = await deployContractManager(); + partialDifferencesTester = await deployPartialDifferencesTester(contractManager); + }) + + it("should calculate sequences correctly", async () => { + await partialDifferencesTester.createSequence(); + let sequence = await partialDifferencesTester.latestSequence(); + + (await partialDifferencesTester.getAndUpdateSequenceItem.call(sequence, 1)).toNumber().should.be.equal(0); + await partialDifferencesTester.reduceSequence(sequence, 1, 2, 2); + + await partialDifferencesTester.addToSequence(sequence, 5e7, 1); + await partialDifferencesTester.subtractFromSequence(sequence, 3e7, 3); + await partialDifferencesTester.addToSequence(sequence, 1e7, 4); + await partialDifferencesTester.subtractFromSequence(sequence, 5e7, 5); + await partialDifferencesTester.addToSequence(sequence, 1e7, 4); + await partialDifferencesTester.addToSequence(sequence, 1e7, 4); + + (await partialDifferencesTester.getAndUpdateSequenceItem.call(sequence, 1)).toNumber().should.be.equal(5e7); + (await partialDifferencesTester.getAndUpdateSequenceItem.call(sequence, 2)).toNumber().should.be.equal(5e7); + (await partialDifferencesTester.getAndUpdateSequenceItem.call(sequence, 3)).toNumber().should.be.equal(2e7); + (await partialDifferencesTester.getAndUpdateSequenceItem.call(sequence, 4)).toNumber().should.be.equal(5e7); + (await partialDifferencesTester.getAndUpdateSequenceItem.call(sequence, 5)).toNumber().should.be.equal(0); + + await partialDifferencesTester.reduceSequence(sequence, 1, 2, 2); + + (await partialDifferencesTester.getAndUpdateSequenceItem.call(sequence, 1)).toNumber().should.be.equal(5e7); + (await partialDifferencesTester.getAndUpdateSequenceItem.call(sequence, 2)).toNumber().should.be.equal(25e6); + (await partialDifferencesTester.getAndUpdateSequenceItem.call(sequence, 3)).toNumber().should.be.equal(1e7); + (await partialDifferencesTester.getAndUpdateSequenceItem.call(sequence, 4)).toNumber().should.be.equal(4e7); + (await partialDifferencesTester.getAndUpdateSequenceItem.call(sequence, 5)).toNumber().should.be.equal(0); + + await partialDifferencesTester.createSequence(); + sequence = await partialDifferencesTester.latestSequence(); + await partialDifferencesTester.subtractFromSequence(sequence, 1, 1); + await partialDifferencesTester.addToSequence(sequence, 1, 1); + await partialDifferencesTester.getAndUpdateSequenceItem(sequence, 1); + await partialDifferencesTester.reduceSequence(sequence, 1, 2, 1); + (await partialDifferencesTester.getAndUpdateSequenceItem.call(sequence, 1)).toNumber().should.be.equal(0); + + }); +}); \ No newline at end of file diff --git a/test/delegation/ValidatorService.ts b/test/delegation/ValidatorService.ts index 1b51901bd..1470b6329 100644 --- a/test/delegation/ValidatorService.ts +++ b/test/delegation/ValidatorService.ts @@ -295,7 +295,13 @@ contract("ValidatorService", ([owner, holder, validator1, validator2, validator3 it("should allow to disable validator from whitelist", async () => { await validatorService.disableValidator(validatorId, {from: validator1}) .should.be.eventually.rejectedWith("Ownable: caller is not the owner"); + await validatorService.disableValidator(validatorId, {from: owner}) + .should.be.eventually.rejectedWith("Validator is already disabled"); + + await validatorService.enableValidator(validatorId, {from: owner}); + await validatorService.trustedValidators(validatorId).should.eventually.be.true; await validatorService.disableValidator(validatorId, {from: owner}); + await validatorService.trustedValidators(validatorId).should.eventually.be.false; }); it("should not allow to send delegation request if validator isn't authorized", async () => { diff --git a/test/tools/deploy/test/partialDifferencesTester.ts b/test/tools/deploy/test/partialDifferencesTester.ts new file mode 100644 index 000000000..e5ec6fd58 --- /dev/null +++ b/test/tools/deploy/test/partialDifferencesTester.ts @@ -0,0 +1,10 @@ +import { ContractManagerInstance, PartialDifferencesTesterInstance } from "../../../../types/truffle-contracts"; +import { deployWithConstructorFunctionFactory } from "../factory"; + +const deployPartialDifferencesTester: (contractManager: ContractManagerInstance) => Promise + = deployWithConstructorFunctionFactory("PartialDifferencesTester", + async (contractManager: ContractManagerInstance) => { + return undefined; + }); + +export { deployPartialDifferencesTester };