diff --git a/contracts/delegation/DelegationController.sol b/contracts/delegation/DelegationController.sol index 5464fbc33..d484ff17c 100644 --- a/contracts/delegation/DelegationController.sol +++ b/contracts/delegation/DelegationController.sol @@ -270,7 +270,7 @@ contract DelegationController is Permissions, ILocker { SlashingSignal[] memory slashingSignals = processSlashesWithoutSignals(delegations[delegationId].holder); - uint currentMonth = timeHelpers.timestampToMonth(now); + uint currentMonth = timeHelpers.getCurrentMonth(); delegations[delegationId].started = currentMonth.add(1); _delegationExtras[delegationId].lastSlashingMonthBeforeDelegation = _slashesOfValidator[delegations[delegationId].validatorId].lastMonth; @@ -402,7 +402,7 @@ contract DelegationController is Permissions, ILocker { if (delegations[delegationId].started == 0) { if (delegations[delegationId].finished == 0) { TimeHelpers timeHelpers = TimeHelpers(contractManager.getContract("TimeHelpers")); - if (now < timeHelpers.getNextMonthStartFromDate(delegations[delegationId].created)) { + if (timeHelpers.getCurrentMonth() == timeHelpers.timestampToMonth(delegations[delegationId].created)) { return State.PROPOSED; } else { return State.REJECTED; @@ -412,13 +412,13 @@ contract DelegationController is Permissions, ILocker { } } else { TimeHelpers timeHelpers = TimeHelpers(contractManager.getContract("TimeHelpers")); - if (now < timeHelpers.monthToTimestamp(delegations[delegationId].started)) { + if (timeHelpers.getCurrentMonth() < delegations[delegationId].started) { return State.ACCEPTED; } else { if (delegations[delegationId].finished == 0) { return State.DELEGATED; } else { - if (now < timeHelpers.monthToTimestamp(delegations[delegationId].finished)) { + if (timeHelpers.getCurrentMonth() < delegations[delegationId].finished) { return State.UNDELEGATION_REQUESTED; } else { return State.COMPLETED; @@ -579,7 +579,7 @@ contract DelegationController is Permissions, ILocker { function getCurrentMonth() internal view returns (uint) { TimeHelpers timeHelpers = TimeHelpers(contractManager.getContract("TimeHelpers")); - return timeHelpers.timestampToMonth(now); + return timeHelpers.getCurrentMonth(); } function _getAndUpdateLockedAmount(address wallet) internal returns (uint) { diff --git a/contracts/delegation/TimeHelpers.sol b/contracts/delegation/TimeHelpers.sol index 2c53bdb48..639b61c6d 100644 --- a/contracts/delegation/TimeHelpers.sol +++ b/contracts/delegation/TimeHelpers.sol @@ -29,70 +29,12 @@ contract TimeHelpers { uint constant ZERO_YEAR = 2020; - function getNextMonthStart() external view returns (uint timestamp) { - return getNextMonthStartFromDate(now); - } - - function calculateDelegationEndTime( - uint requestTime, - uint delegationPeriod, - uint redelegationPeriod - ) - external - view - returns (uint timestamp) - { - uint year; - uint month; - (year, month, ) = BokkyPooBahsDateTimeLibrary.timestampToDate(requestTime); - - month = month.add(delegationPeriod).add(1); - if (month > 12) { - year = year.add(month.sub(1).div(12)); - month = month.sub(1).mod(12).add(1); - } - timestamp = BokkyPooBahsDateTimeLibrary.timestampFromDate(year, month, 1); - - if (now > timestamp) { - uint currentYear; - uint currentMonth; - (currentYear, currentMonth, ) = BokkyPooBahsDateTimeLibrary.timestampToDate(now); - currentMonth = currentMonth.add(currentYear.sub(year).mul(12)); - - month = month.add( - currentMonth.sub(month).div(redelegationPeriod).add(1).mul(redelegationPeriod)); - if (month > 12) { - year = year.add(month.sub(1).div(12)); - month = month.sub(1).mod(12).add(1); - } - timestamp = BokkyPooBahsDateTimeLibrary.timestampFromDate(year, month, 1); - } - } - function calculateProofOfUseLockEndTime(uint month, uint lockUpPeriodDays) external pure returns (uint timestamp) { timestamp = BokkyPooBahsDateTimeLibrary.addDays(monthToTimestamp(month), lockUpPeriodDays); } function addMonths(uint fromTimestamp, uint n) external pure returns (uint) { - uint year; - uint month; - uint day; - uint hour; - uint minute; - uint second; - (year, month, day, hour, minute, second) = BokkyPooBahsDateTimeLibrary.timestampToDateTime(fromTimestamp); - month = month.add(n); - if (month > 12) { - year = year.add(month.sub(1).div(12)); - month = month.sub(1).mod(12).add(1); - } - return BokkyPooBahsDateTimeLibrary.timestampFromDateTime( - year, - month, - day, - hour, - minute, - second); + return BokkyPooBahsDateTimeLibrary.addMonths(fromTimestamp, n); } function getCurrentMonth() external view returns (uint) { @@ -117,16 +59,4 @@ contract TimeHelpers { require(month > 0, "Timestamp is too far in the past"); return month; } - - function getNextMonthStartFromDate(uint dateTimestamp) public pure returns (uint timestamp) { - uint year; - uint month; - (year, month, ) = BokkyPooBahsDateTimeLibrary.timestampToDate(dateTimestamp); - month = month.add(1); - if (month > 12) { - year = year.add(1); - month = 1; - } - timestamp = BokkyPooBahsDateTimeLibrary.timestampFromDate(year, month, 1); - } } diff --git a/contracts/test/TimeHelpersWithDebug.sol b/contracts/test/TimeHelpersWithDebug.sol new file mode 100644 index 000000000..fbba58953 --- /dev/null +++ b/contracts/test/TimeHelpersWithDebug.sol @@ -0,0 +1,44 @@ +/* + TimeHellpersWithDebug.sol - SKALE Manager + Copyright (C) 2019-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-ethereum-package/contracts/ownership/Ownable.sol"; +import "@openzeppelin/upgrades/contracts/Initializable.sol"; + +import "../delegation/TimeHelpers.sol"; + + +contract TimeHelpersWithDebug is TimeHelpers, Ownable { + uint timeShift; + + function getCurrentMonth() external view returns (uint) { + return timestampToMonth(now.add(timeShift)); + } + + function skipTime(uint sec) external onlyOwner { + timeShift = timeShift.add(sec); + } + + function initialize() external initializer { + Ownable.initialize(msg.sender); + timeShift = 0; + } + +} \ No newline at end of file diff --git a/migrations/1_deploy_skale_manager_system.js b/migrations/1_deploy_skale_manager_system.js index ff6d194a4..a522755a4 100644 --- a/migrations/1_deploy_skale_manager_system.js +++ b/migrations/1_deploy_skale_manager_system.js @@ -24,6 +24,7 @@ let erc1820Contract = erc1820Params.contractAddress; let erc1820Sender = erc1820Params.senderAddress; let erc1820Bytecode = erc1820Params.bytecode; let erc1820Amount = "80000000000000000"; +const production = false; // TODO: change to true before launch async function deploy(deployer, networkName, accounts) { if (configFile.networks[networkName].host !== "" && configFile.networks[networkName].host !== undefined && configFile.networks[networkName].port !== "" && configFile.networks[networkName].port !== undefined) { @@ -96,6 +97,9 @@ async function deploy(deployer, networkName, accounts) { "SkaleManager", "Pricing" ] + if (!production) { + contracts.push("TimeHelpersWithDebug"); + } contractsData = []; for (const contract of contracts) { @@ -119,6 +123,8 @@ async function deploy(deployer, networkName, accounts) { console.log("contractManager address:", contract.address); } else if (["TimeHelpers", "Decryption", "ECDH"].includes(contractName)) { contract = await create(Object.assign({ contractAlias: contractName }, options)); + } else if (["TimeHelpersWithDebug"].includes(contractName)) { + contract = await create(Object.assign({ contractAlias: contractName, methodName: 'initialize', methodArgs: [] }, options)); } else { contract = await create(Object.assign({ contractAlias: contractName, methodName: 'initialize', methodArgs: [contractManager.address] }, options)); } @@ -132,7 +138,12 @@ async function deploy(deployer, networkName, accounts) { await contractManager.methods.setContractsAddress(contract, address).send({from: deployAccount}).then(function(res) { console.log("Contract", contract, "with address", address, "is registered in Contract Manager"); }); - } + } + if (!production) { + await contractManager.methods.setContractsAddress("TimeHelpers", deployed.get("TimeHelpersWithDebug").address).send({from: deployAccount}).then(function(res) { + console.log("TimeHelpersWithDebug was enabled"); + }); + } await deployer.deploy(SkaleToken, contractManager.address, [], {gas: gasLimit * gas_multiplier}); await contractManager.methods.setContractsAddress("SkaleToken", SkaleToken.address).send({from: deployAccount}).then(function(res) { diff --git a/test/delegation/DelegationController.ts b/test/delegation/DelegationController.ts index cde7aef1b..c6967f933 100644 --- a/test/delegation/DelegationController.ts +++ b/test/delegation/DelegationController.ts @@ -13,6 +13,7 @@ import { deployDelegationController } from "../utils/deploy/delegation/delegatio import { deployTokenState } from "../utils/deploy/delegation/tokenState"; import { deployValidatorService } from "../utils/deploy/delegation/validatorService"; import { deploySkaleToken } from "../utils/deploy/skaleToken"; +import { deployTimeHelpersWithDebug } from "../utils/deploy/test/timeHelpersWithDebug"; import { Delegation, State } from "../utils/types"; chai.should(); chai.use(chaiAsPromised); @@ -169,6 +170,24 @@ contract("DelegationController", ([owner, holder1, holder2, validator, validator .should.be.rejectedWith("No permissions to accept request"); }); + it("should allow for QA team to test delegation pipeline immediately", async () => { + const timeHelpersWithDebug = await deployTimeHelpersWithDebug(contractManager); + await contractManager.setContractsAddress("TimeHelpers", timeHelpersWithDebug.address); + + await delegationController.acceptPendingDelegation(delegationId, {from: validator}); + (await delegationController.getState(delegationId)).toNumber().should.be.equal(State.ACCEPTED); + + await timeHelpersWithDebug.skipTime(month); + (await delegationController.getState(delegationId)).toNumber().should.be.equal(State.DELEGATED); + + await delegationController.requestUndelegation(delegationId, {from: holder1}); + (await delegationController.getState(delegationId)).toNumber() + .should.be.equal(State.UNDELEGATION_REQUESTED); + + await timeHelpersWithDebug.skipTime(month * delegationPeriod); + (await delegationController.getState(delegationId)).toNumber().should.be.equal(State.COMPLETED); + }); + describe("when delegation is accepted", async () => { beforeEach(async () => { await delegationController.acceptPendingDelegation(delegationId, {from: validator}); diff --git a/test/utils/deploy/test/timeHelpersWithDebug.ts b/test/utils/deploy/test/timeHelpersWithDebug.ts new file mode 100644 index 000000000..8ace7559f --- /dev/null +++ b/test/utils/deploy/test/timeHelpersWithDebug.ts @@ -0,0 +1,14 @@ +import { ContractManagerInstance, TimeHelpersWithDebugInstance } from "../../../../types/truffle-contracts"; +import { deployFunctionFactory } from "../factory"; + +const deployTimeHelpersWithDebug: (contractManager: ContractManagerInstance) => Promise + = deployFunctionFactory("TimeHelpersWithDebug", + undefined, + async (contractManager: ContractManagerInstance) => { + const TimeHelpersWithDebug = artifacts.require("./TimeHelpersWithDebug"); + const instance = await TimeHelpersWithDebug.new(); + await instance.initialize(); + return instance; + }); + +export { deployTimeHelpersWithDebug };