diff --git a/contracts/colonyNetwork/ColonyNetwork.sol b/contracts/colonyNetwork/ColonyNetwork.sol index 6d0b2cc8d0..c0d3f31d5d 100644 --- a/contracts/colonyNetwork/ColonyNetwork.sol +++ b/contracts/colonyNetwork/ColonyNetwork.sol @@ -108,121 +108,6 @@ contract ColonyNetwork is BasicMetaTransaction, ColonyNetworkStorage, Multicall return colonies[_id]; } - function addSkill( - uint _parentSkillId - ) public stoppable skillExists(_parentSkillId) allowedToAddSkill returns (uint256) { - require(_parentSkillId > 0, "colony-network-invalid-parent-skill"); - - Skill storage parentSkill = skills[_parentSkillId]; - require(!parentSkill.DEPRECATED_globalSkill, "colony-network-no-global-skills"); - - skillCount += 1; - Skill memory s; - - s.nParents = parentSkill.nParents + 1; - skills[skillCount] = s; - - uint parentSkillId = _parentSkillId; - bool notAtRoot = true; - uint powerOfTwo = 1; - uint treeWalkingCounter = 1; - - // Walk through the tree parent skills up to the root - while (notAtRoot) { - // Add the new skill to each parent children - parentSkill.children.push(skillCount); - parentSkill.nChildren += 1; - - // When we are at an integer power of two steps away from the newly added skill (leaf) node, - // add the current parent skill to the new skill's parents array - if (treeWalkingCounter == powerOfTwo) { - // slither-disable-next-line controlled-array-length - skills[skillCount].parents.push(parentSkillId); - powerOfTwo = powerOfTwo * 2; - } - - // Check if we've reached the root of the tree yet (it has no parents) - // Otherwise get the next parent - if (parentSkill.nParents == 0) { - notAtRoot = false; - } else { - parentSkillId = parentSkill.parents[0]; - parentSkill = skills[parentSkill.parents[0]]; - } - - treeWalkingCounter += 1; - } - - emit SkillAdded(skillCount, _parentSkillId); - return skillCount; - } - - function getParentSkillId(uint _skillId, uint _parentSkillIndex) public view returns (uint256) { - return ascendSkillTree(_skillId, _parentSkillIndex + 1); - } - - function getChildSkillId(uint _skillId, uint _childSkillIndex) public view returns (uint256) { - if (_childSkillIndex == UINT256_MAX) { - return _skillId; - } else { - Skill storage skill = skills[_skillId]; - require( - _childSkillIndex < skill.children.length, - "colony-network-out-of-range-child-skill-index" - ); - return skill.children[_childSkillIndex]; - } - } - - function deprecateSkill( - uint256 _skillId, - bool _deprecated - ) public stoppable allowedToAddSkill returns (bool) { - require( - skills[_skillId].nParents == 0, - "colony-network-deprecate-local-skills-temporarily-disabled" - ); - bool changed = skills[_skillId].deprecated != _deprecated; - skills[_skillId].deprecated = _deprecated; - return changed; - } - - /// @notice @deprecated - function deprecateSkill(uint256 _skillId) public stoppable { - deprecateSkill(_skillId, true); - } - - function initialiseRootLocalSkill() public stoppable calledByColony returns (uint256) { - skillCount++; - return skillCount; - } - - function appendReputationUpdateLog( - address _user, - int _amount, - uint _skillId - ) public stoppable calledByColony skillExists(_skillId) { - if (_amount == 0 || _user == address(0x0)) { - // We short-circut amount=0 as it has no effect to save gas, and we ignore Address Zero because it will - // mess up the tracking of the total amount of reputation in a colony, as that's the key that it's - // stored under in the patricia/merkle tree. Colonies can still pay tokens out to it if they want, - // it just won't earn reputation. - return; - } - - uint128 nParents = skills[_skillId].nParents; - // We only update child skill reputation if the update is negative, otherwise just set nChildren to 0 to save gas - uint128 nChildren = _amount < 0 ? skills[_skillId].nChildren : 0; - IReputationMiningCycle(inactiveReputationMiningCycle).appendReputationUpdateLog( - _user, - _amount, - _skillId, - msgSender(), - nParents, - nChildren - ); - } - function checkNotAdditionalProtectedVariable(uint256 _slot) public view { // solhint-disable-line no-empty-blocks } @@ -263,19 +148,4 @@ contract ColonyNetwork is BasicMetaTransaction, ColonyNetworkStorage, Multicall protectSlot(slot); metatransactionNonces[_user] += 1; } - - function ascendSkillTree(uint _skillId, uint _parentSkillNumber) internal view returns (uint256) { - if (_parentSkillNumber == 0) { - return _skillId; - } - - Skill storage skill = skills[_skillId]; - for (uint256 i; i < skill.parents.length; i++) { - if (2 ** (i + 1) > _parentSkillNumber) { - uint _newSkillId = skill.parents[i]; - uint _newParentSkillNumber = _parentSkillNumber - 2 ** i; - return ascendSkillTree(_newSkillId, _newParentSkillNumber); - } - } - } } diff --git a/contracts/colonyNetwork/ColonyNetworkSkills.sol b/contracts/colonyNetwork/ColonyNetworkSkills.sol new file mode 100644 index 0000000000..83fcf751d7 --- /dev/null +++ b/contracts/colonyNetwork/ColonyNetworkSkills.sol @@ -0,0 +1,161 @@ +/* + This file is part of The Colony Network. + + The Colony Network is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The Colony Network 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with The Colony Network. If not, see . +*/ + +pragma solidity 0.8.23; +pragma experimental "ABIEncoderV2"; + +import "./../reputationMiningCycle/IReputationMiningCycle.sol"; +import "./../common/Multicall.sol"; +import "./ColonyNetworkStorage.sol"; + +contract ColonyNetworkSkills is ColonyNetworkStorage, Multicall { + // Skills + + function addSkill( + uint _parentSkillId + ) public stoppable skillExists(_parentSkillId) allowedToAddSkill returns (uint256) { + require(_parentSkillId > 0, "colony-network-invalid-parent-skill"); + + Skill storage parentSkill = skills[_parentSkillId]; + require(!parentSkill.DEPRECATED_globalSkill, "colony-network-no-global-skills"); + + skillCount += 1; + Skill memory s; + + s.nParents = parentSkill.nParents + 1; + skills[skillCount] = s; + + uint parentSkillId = _parentSkillId; + bool notAtRoot = true; + uint powerOfTwo = 1; + uint treeWalkingCounter = 1; + + // Walk through the tree parent skills up to the root + while (notAtRoot) { + // Add the new skill to each parent children + parentSkill.children.push(skillCount); + parentSkill.nChildren += 1; + + // When we are at an integer power of two steps away from the newly added skill (leaf) node, + // add the current parent skill to the new skill's parents array + if (treeWalkingCounter == powerOfTwo) { + // slither-disable-next-line controlled-array-length + skills[skillCount].parents.push(parentSkillId); + powerOfTwo = powerOfTwo * 2; + } + + // Check if we've reached the root of the tree yet (it has no parents) + // Otherwise get the next parent + if (parentSkill.nParents == 0) { + notAtRoot = false; + } else { + parentSkillId = parentSkill.parents[0]; + parentSkill = skills[parentSkill.parents[0]]; + } + + treeWalkingCounter += 1; + } + + emit SkillAdded(skillCount, _parentSkillId); + return skillCount; + } + + function deprecateSkill( + uint256 _skillId, + bool _deprecated + ) public stoppable allowedToAddSkill returns (bool) { + require( + skills[_skillId].nParents == 0, + "colony-network-deprecate-local-skills-temporarily-disabled" + ); + bool changed = skills[_skillId].deprecated != _deprecated; + skills[_skillId].deprecated = _deprecated; + return changed; + } + + /// @notice @deprecated + function deprecateSkill(uint256 _skillId) public stoppable { + deprecateSkill(_skillId, true); + } + + function initialiseRootLocalSkill() public stoppable calledByColony returns (uint256) { + skillCount++; + return skillCount; + } + + function appendReputationUpdateLog( + address _user, + int _amount, + uint _skillId + ) public stoppable calledByColony skillExists(_skillId) { + if (_amount == 0 || _user == address(0x0)) { + // We short-circut amount=0 as it has no effect to save gas, and we ignore Address Zero because it will + // mess up the tracking of the total amount of reputation in a colony, as that's the key that it's + // stored under in the patricia/merkle tree. Colonies can still pay tokens out to it if they want, + // it just won't earn reputation. + return; + } + + uint128 nParents = skills[_skillId].nParents; + // We only update child skill reputation if the update is negative, otherwise just set nChildren to 0 to save gas + uint128 nChildren = _amount < 0 ? skills[_skillId].nChildren : 0; + IReputationMiningCycle(inactiveReputationMiningCycle).appendReputationUpdateLog( + _user, + _amount, + _skillId, + msgSender(), + nParents, + nChildren + ); + } + + // View + + function getParentSkillId(uint _skillId, uint _parentSkillIndex) public view returns (uint256) { + return ascendSkillTree(_skillId, _parentSkillIndex + 1); + } + + function getChildSkillId(uint _skillId, uint _childSkillIndex) public view returns (uint256) { + if (_childSkillIndex == UINT256_MAX) { + return _skillId; + } else { + Skill storage skill = skills[_skillId]; + require( + _childSkillIndex < skill.children.length, + "colony-network-out-of-range-child-skill-index" + ); + return skill.children[_childSkillIndex]; + } + } + + // Internal + + function ascendSkillTree(uint _skillId, uint _parentSkillNumber) internal view returns (uint256) { + if (_parentSkillNumber == 0) { + return _skillId; + } + + Skill storage skill = skills[_skillId]; + for (uint256 i; i < skill.parents.length; i++) { + if (2 ** (i + 1) > _parentSkillNumber) { + uint _newSkillId = skill.parents[i]; + uint _newParentSkillNumber = _parentSkillNumber - 2 ** i; + return ascendSkillTree(_newSkillId, _newParentSkillNumber); + } + } + } +} diff --git a/helpers/upgradable-contracts.js b/helpers/upgradable-contracts.js index 19d227d3ce..a3757b1bbd 100644 --- a/helpers/upgradable-contracts.js +++ b/helpers/upgradable-contracts.js @@ -107,6 +107,7 @@ exports.setupUpgradableColonyNetwork = async function setupUpgradableColonyNetwo colonyNetworkAuction, colonyNetworkENS, colonyNetworkExtensions, + colonyNetworkSkills, contractRecovery, ) { const deployedImplementations = {}; @@ -115,6 +116,7 @@ exports.setupUpgradableColonyNetwork = async function setupUpgradableColonyNetwo deployedImplementations.ColonyNetworkMining = colonyNetworkMining.address; deployedImplementations.ColonyNetworkAuction = colonyNetworkAuction.address; deployedImplementations.ColonyNetworkENS = colonyNetworkENS.address; + deployedImplementations.ColonyNetworkSkills = colonyNetworkSkills.address; deployedImplementations.ColonyNetworkExtensions = colonyNetworkExtensions.address; deployedImplementations.ContractRecovery = contractRecovery.address; diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index 60d09fc5d8..49fb332aba 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -7,6 +7,7 @@ const ColonyNetworkMining = artifacts.require("./ColonyNetworkMining"); const ColonyNetworkAuction = artifacts.require("./ColonyNetworkAuction"); const ColonyNetworkENS = artifacts.require("./ColonyNetworkENS"); const ColonyNetworkExtensions = artifacts.require("./ColonyNetworkExtensions"); +const ColonyNetworkSkills = artifacts.require("./ColonyNetworkSkills"); const ReputationMiningCycle = artifacts.require("./ReputationMiningCycle"); const ReputationMiningCycleRespond = artifacts.require("./ReputationMiningCycleRespond"); const ReputationMiningCycleBinarySearch = artifacts.require("./ReputationMiningCycleBinarySearch"); @@ -28,6 +29,7 @@ module.exports = async function (deployer, network) { await deployer.deploy(ColonyNetworkAuction); await deployer.deploy(ColonyNetworkENS); await deployer.deploy(ColonyNetworkExtensions); + await deployer.deploy(ColonyNetworkSkills); await deployer.deploy(ReputationMiningCycle); await deployer.deploy(ReputationMiningCycleRespond); await deployer.deploy(ReputationMiningCycleBinarySearch); diff --git a/migrations/3_setup_colony_network.js b/migrations/3_setup_colony_network.js index 12355512ec..789244e143 100644 --- a/migrations/3_setup_colony_network.js +++ b/migrations/3_setup_colony_network.js @@ -11,6 +11,7 @@ const ColonyNetworkMining = artifacts.require("./ColonyNetworkMining"); const ColonyNetworkAuction = artifacts.require("./ColonyNetworkAuction"); const ColonyNetworkENS = artifacts.require("./ColonyNetworkENS"); const ColonyNetworkExtensions = artifacts.require("./ColonyNetworkExtensions"); +const ColonyNetworkSkills = artifacts.require("./ColonyNetworkSkills"); const EtherRouter = artifacts.require("./EtherRouter"); const Resolver = artifacts.require("./Resolver"); @@ -22,6 +23,7 @@ module.exports = async function (deployer) { const colonyNetworkAuction = await ColonyNetworkAuction.deployed(); const colonyNetworkENS = await ColonyNetworkENS.deployed(); const colonyNetworkExtensions = await ColonyNetworkExtensions.deployed(); + const colonyNetworkSkills = await ColonyNetworkSkills.deployed(); const etherRouter = await EtherRouter.deployed(); const resolver = await Resolver.deployed(); const contractRecovery = await ContractRecovery.deployed(); @@ -35,6 +37,7 @@ module.exports = async function (deployer) { colonyNetworkAuction, colonyNetworkENS, colonyNetworkExtensions, + colonyNetworkSkills, contractRecovery, ); diff --git a/test-smoke/colony-storage-consistent.js b/test-smoke/colony-storage-consistent.js index b29f5c4d53..409711b809 100644 --- a/test-smoke/colony-storage-consistent.js +++ b/test-smoke/colony-storage-consistent.js @@ -155,11 +155,11 @@ contract("Contract Storage", (accounts) => { console.log("miningCycleStateHash:", miningCycleStateHash); console.log("tokenLockingStateHash:", tokenLockingStateHash); - expect(colonyNetworkStateHash).to.equal("0xe2a19d28c1a68778bfe793623d1b9f71f43db3e98b46fef29f3ea1040968f26c"); - expect(colonyStateHash).to.equal("0x58b09676f8fb26ec467b5bb8ea3392b6da0db191acc5ee2f400a0940ee79f4ce"); - expect(metaColonyStateHash).to.equal("0xa09c107f9a66e313434ba2d6633e09c15fcb365db7678cf4dc4a19ca481a3954"); - expect(miningCycleStateHash).to.equal("0xfd18a690f69132bd95d32bf3a91cb2b60d0da16993cd60087bf8ccc1fa75b680"); - expect(tokenLockingStateHash).to.equal("0x0a66e763122dc805a1fcd36aa1f0cc40228ffa53ed050fec4ac78c70cad4d31a"); + expect(colonyNetworkStateHash).to.equal("0x7df06499d65ae6b6164fc768c7cfc89e0c7a56d5483a21a9a95cafa8eaaee719"); + expect(colonyStateHash).to.equal("0xcfcaeb63eba9378b73a4c62e5a4cb4674b4e301f73814776f16f717055a7c295"); + expect(metaColonyStateHash).to.equal("0xba451b41b29bc477b8b53f057b9252c6daedeab4ee917b5ba00d46f6d2919bfc"); + expect(miningCycleStateHash).to.equal("0xf453858c03397af01668d54e6031e5a5594cff94c8fdb618c44899cd24cb1856"); + expect(tokenLockingStateHash).to.equal("0xb0e53da184faa87011a47b92e99d95f22afcc2feba5aa009be659242df09df63"); }); }); });