diff --git a/contracts/extensions/ReputationBootstrapper.sol b/contracts/extensions/ReputationBootstrapper.sol index 9b313ac603..17038ec639 100644 --- a/contracts/extensions/ReputationBootstrapper.sol +++ b/contracts/extensions/ReputationBootstrapper.sol @@ -48,6 +48,7 @@ contract ReputationBootstrapper is ColonyExtensionMeta { address public token; bool public giveTokens; + bool public isLocked; uint256 public decayPeriod; uint256 public decayNumerator; @@ -58,7 +59,17 @@ contract ReputationBootstrapper is ColonyExtensionMeta { // Modifiers modifier onlyRoot() { - require(colony.hasUserRole(msgSender(), 1, ColonyDataTypes.ColonyRole.Root), "reputation-bootsrapper-caller-not-root"); + require(colony.hasUserRole(msgSender(), 1, ColonyDataTypes.ColonyRole.Root), "reputation-bootstrapper-caller-not-root"); + _; + } + + modifier unlocked() { + require(!isLocked, "reputation-bootstrapper-locked"); + _; + } + + modifier locked() { + require(isLocked, "reputation-bootstrapper-unlocked"); _; } @@ -94,6 +105,7 @@ contract ReputationBootstrapper is ColonyExtensionMeta { /// @notice Called when deprecating (or undeprecating) the extension function deprecate(bool _deprecated) public override auth { deprecated = _deprecated; + isLocked = true; } /// @notice Called when uninstalling the extension @@ -106,12 +118,16 @@ contract ReputationBootstrapper is ColonyExtensionMeta { // Public - function setGiveTokens(bool _giveTokens) public onlyRoot { + function lockExtension() public onlyRoot unlocked { + isLocked = true; + } + + function setGiveTokens(bool _giveTokens) public onlyRoot unlocked { giveTokens = _giveTokens; } - function setGrants(bytes32[] memory _hashedSecrets, uint256[] memory _amounts) public onlyRoot notDeprecated { - require(_hashedSecrets.length == _amounts.length, "reputation-bootsrapper-invalid-arguments"); + function setGrants(bytes32[] memory _hashedSecrets, uint256[] memory _amounts) public onlyRoot unlocked { + require(_hashedSecrets.length == _amounts.length, "reputation-bootstrapper-invalid-arguments"); for (uint256 i; i < _hashedSecrets.length; i++) { require(_amounts[i] <= INT128_MAX, "reputation-bootstrapper-invalid-amount"); @@ -121,7 +137,7 @@ contract ReputationBootstrapper is ColonyExtensionMeta { } } - function claimGrant(uint256 _secret) public { + function claimGrant(uint256 _secret) public locked { bytes32 hashedSecret = keccak256(abi.encodePacked(_secret)); uint256 grantAmount = grants[hashedSecret].amount; uint256 tokenAmount = grants[hashedSecret].amount; diff --git a/test/extensions/reputation-bootstrapper.js b/test/extensions/reputation-bootstrapper.js index d2b0eb1631..d2cc9dc2dd 100644 --- a/test/extensions/reputation-bootstrapper.js +++ b/test/extensions/reputation-bootstrapper.js @@ -98,7 +98,7 @@ contract("Reputation Bootstrapper", (accounts) => { }); describe("managing the extension", async () => { - it("can setup repuation amounts", async () => { + it("can setup reputation amounts", async () => { await reputationBootstrapper.setGrants([soliditySha3(PIN1), soliditySha3(PIN2)], [WAD, WAD.muln(2)]); const grant = await reputationBootstrapper.grants(soliditySha3(PIN1)); @@ -106,19 +106,25 @@ contract("Reputation Bootstrapper", (accounts) => { }); it("cannot setup reputation amounts if not root", async () => { - await checkErrorRevert(reputationBootstrapper.setGrants([], [], { from: USER1 }), "reputation-bootsrapper-caller-not-root"); + await checkErrorRevert(reputationBootstrapper.setGrants([], [], { from: USER1 }), "reputation-bootstrapper-caller-not-root"); }); - it("cannot setup repuation amounts with mismatched arguments", async () => { - await checkErrorRevert(reputationBootstrapper.setGrants([], [WAD]), "reputation-bootsrapper-invalid-arguments"); + it("cannot setup reputation amounts with mismatched arguments", async () => { + await checkErrorRevert(reputationBootstrapper.setGrants([], [WAD]), "reputation-bootstrapper-invalid-arguments"); }); - it("cannot setup repuation amounts with invalid values", async () => { + it("cannot setup reputation amounts with invalid values", async () => { await checkErrorRevert(reputationBootstrapper.setGrants([soliditySha3(PIN1)], [INT128_MAX.addn(1)]), "reputation-bootstrapper-invalid-amount"); }); - it("can claim repuation amounts", async () => { + it("cannot setup reputation amounts when locked", async () => { + await reputationBootstrapper.lockExtension(); + await checkErrorRevert(reputationBootstrapper.setGrants([soliditySha3(PIN1)], [INT128_MAX.addn(1)]), "reputation-bootstrapper-locked"); + }); + + it("can claim reputation amounts", async () => { await reputationBootstrapper.setGrants([soliditySha3(PIN1), soliditySha3(PIN2)], [WAD, WAD.muln(2)]); + await reputationBootstrapper.lockExtension(); await reputationBootstrapper.claimGrant(PIN1, { from: USER1 }); @@ -137,10 +143,10 @@ contract("Reputation Bootstrapper", (accounts) => { it("can claim reputation amounts with a decay", async () => { await reputationBootstrapper.setGrants([soliditySha3(PIN1), soliditySha3(PIN2)], [WAD, WAD.muln(2)]); + await reputationBootstrapper.lockExtension(); // Reputation decays by half in 90 days await forwardTime(SECONDS_PER_DAY * 90, this); - await reputationBootstrapper.claimGrant(PIN1, { from: USER1 }); const inactiveCycleAddress = await colonyNetwork.getReputationMiningCycle(false); @@ -150,11 +156,12 @@ contract("Reputation Bootstrapper", (accounts) => { expect(updateLog.amount).to.eq.BN(WAD.divn(2).subn(406575)); // Numerical approximation }); - it("can claim repuation amounts and tokens, if set", async () => { + it("can claim reputation amounts and tokens, if set", async () => { await token.mint(reputationBootstrapper.address, WAD.muln(10)); await reputationBootstrapper.setGiveTokens(true); await reputationBootstrapper.setGrants([soliditySha3(PIN1), soliditySha3(PIN2)], [WAD, WAD.muln(2)]); + await reputationBootstrapper.lockExtension(); await reputationBootstrapper.claimGrant(PIN1, { from: USER1 }); @@ -162,7 +169,13 @@ contract("Reputation Bootstrapper", (accounts) => { expect(balance).to.eq.BN(WAD); }); + it("cannot set giveTokens once locked", async () => { + await reputationBootstrapper.lockExtension(); + await checkErrorRevert(reputationBootstrapper.setGiveTokens(false), "reputation-bootstrapper-locked"); + }); + it("cannot claim a nonexistent amount", async () => { + await reputationBootstrapper.lockExtension(); await checkErrorRevert(reputationBootstrapper.claimGrant(PIN1, { from: USER1 }), "reputation-bootstrapper-nothing-to-claim"); }); @@ -171,12 +184,14 @@ contract("Reputation Bootstrapper", (accounts) => { await reputationBootstrapper.setGiveTokens(true); await reputationBootstrapper.setGrants([soliditySha3(PIN1)], [WAD]); + await reputationBootstrapper.lockExtension(); await checkErrorRevert(reputationBootstrapper.claimGrant(PIN1, { from: USER1 }), "ds-token-insufficient-balance"); }); it("can claim reputation via metatransactions", async () => { await reputationBootstrapper.setGrants([soliditySha3(PIN1)], [WAD]); + await reputationBootstrapper.lockExtension(); const txData = await reputationBootstrapper.contract.methods.claimGrant(PIN1).encodeABI(); const { r, s, v } = await getMetaTransactionParameters(txData, USER0, reputationBootstrapper.address); @@ -192,5 +207,9 @@ contract("Reputation Bootstrapper", (accounts) => { expect(updateLog.amount).to.eq.BN(WAD); expect(updateLog.skillId).to.eq.BN(domain1.skillId); }); + + it("cannot claim reputation amounts when unlocked", async () => { + await checkErrorRevert(reputationBootstrapper.claimGrant(PIN1, { from: USER1 }), "reputation-bootstrapper-unlocked"); + }); }); });