diff --git a/contracts/infrastructure/PlatformVoter.sol b/contracts/infrastructure/PlatformVoter.sol index fa02f31..a67845f 100644 --- a/contracts/infrastructure/PlatformVoter.sol +++ b/contracts/infrastructure/PlatformVoter.sol @@ -18,7 +18,7 @@ contract PlatformVoter is ControllableV3, IPlatformVoter { // ************************************************************* /// @dev Version of this contract. Adjust manually on each code modification. - string public constant PLATFORM_VOTER_VERSION = "1.0.1"; + string public constant PLATFORM_VOTER_VERSION = "1.0.2"; /// @dev Denominator for different ratios. It is default for the whole platform. uint public constant RATIO_DENOMINATOR = 100_000; /// @dev Delay between votes. @@ -255,8 +255,14 @@ contract PlatformVoter is ControllableV3, IPlatformVoter { if (found) { require(v.timestamp + VOTE_DELAY < block.timestamp, "delay"); _removeVote(tokenId, v._type, v.target, v.weight, v.weightedValue); - // with descent loop we remove one by one last elements + + if (i != length) { + _votes[i - 1] = _votes[length - 1]; + } + _votes.pop(); + length--; + emit VoteReset( tokenId, uint(v._type), @@ -296,4 +302,49 @@ contract PlatformVoter is ControllableV3, IPlatformVoter { } } + /////////////////////////////////////////////////////////////// + // EMERGENCY ACTIONS + // If something went wrong governance can fix weights. + /////////////////////////////////////////////////////////////// + + /// @dev In case if something went wrong with vote calculation governance can remove the vote manually for a user + /// If removeWeights is false then it will only remove vote from the list without changing weights. + /// This will lead to "staked" weights forever. Use `emergencyAdjustWeights()` to fix it. + function emergencyResetVote(uint tokenId, uint index, bool removeWeights) external { + require(msg.sender == IController(controller()).governance(), "!gov"); + Vote[] storage _votes = votes[tokenId]; + + Vote memory v = _votes[index]; + if (removeWeights) { + _removeVote(tokenId, v._type, v.target, v.weight, v.weightedValue); + } + + _votes[index] = _votes[_votes.length - 1]; + _votes.pop(); + + emit VoteReset( + tokenId, + uint(v._type), + v.target, + v.weight, + v.weightedValue, + v.timestamp + ); + } + + /// @dev Before calling this function need to calculate simulation where all votes removed for all user and check the remaining weights/values. + /// The difference should be counted for the new values and passed to this function. + /// Do not call this function without properly check that users will able to reset votes! + /// If any user has invalid votes need to call `emergencyResetVote()` firstly and simulate full reset for all users. + function emergencyAdjustWeights(AttributeType _type, address target, uint weights, uint values) external { + require(msg.sender == IController(controller()).governance(), "!gov"); + + attributeWeights[_type][target] = weights; + attributeValues[_type][target] = values; + + uint newValue = weights == 0 ? 0 : values / weights; + require(newValue <= RATIO_DENOMINATOR, '!ratio'); + _setAttribute(_type, newValue, target); + } + } diff --git a/scripts/gov/poke-platform-voter.ts b/scripts/gov/poke-platform-voter.ts index 2d9b360..950f30c 100644 --- a/scripts/gov/poke-platform-voter.ts +++ b/scripts/gov/poke-platform-voter.ts @@ -2,35 +2,39 @@ import {ethers} from "hardhat"; import {Addresses} from "../addresses/addresses"; import {PlatformVoter__factory} from "../../typechain"; import {RunHelper} from "../utils/RunHelper"; +import {txParams} from "../deploy/DeployContract"; // tslint:disable-next-line:no-var-requires const {request, gql} = require('graphql-request') +// tslint:disable-next-line:no-var-requires +const hre = require("hardhat"); + async function main() { const [signer] = await ethers.getSigners(); const core = Addresses.getCore(); const voter = PlatformVoter__factory.connect(core.platformVoter, signer); - const data = await request('https://api.thegraph.com/subgraphs/name/tetu-io/tetu-v2', gql` - query { - platformVoterEntities { - votes(first: 1000) { - desiredValue - date - newValue - percent - target - voteType - veWeightedValue - vePower - veNFT { - veNFTId - } - } - } - } - `); + const data = await request('https://api.thegraph.com/subgraphs/name/tetu-io/tetu-v2', gql` + query { + platformVoterEntities { + votes(first: 1000) { + desiredValue + date + newValue + percent + target + voteType + veWeightedValue + vePower + veNFT { + veNFTId + } + } + } + } + `); const votes = data.platformVoterEntities[0].votes; @@ -58,7 +62,11 @@ async function main() { for (const veId of vePokes) { console.log('poke ve: ', veId); - // await RunHelper.runAndWait(() => voter.poke(veId)); + if(veId === 14) { + continue; + } + const params = await txParams(hre, ethers.provider); + await RunHelper.runAndWait(() => voter.poke(veId, {...params})); } } diff --git a/test/infrastructure/PlatformVoterTest.ts b/test/infrastructure/PlatformVoterTest.ts index 7e86ee4..047b70b 100644 --- a/test/infrastructure/PlatformVoterTest.ts +++ b/test/infrastructure/PlatformVoterTest.ts @@ -159,6 +159,108 @@ describe("Platform voter tests", function () { expect(await forwarder.toInvestFundRatio()).eq(0); }); + it("reset multiple votes test", async function () { + await platformVoter.vote(1, 1, 100, Misc.ZERO_ADDRESS); + await platformVoter.vote(1, 2, 100, Misc.ZERO_ADDRESS); + await platformVoter.vote(1, 3, 100, platformVoter.address); + await platformVoter.vote(1, 3, 100, ve.address); + + expect(await platformVoter.veVotesLength(1)).eq(4); + + expect(await forwarder.toInvestFundRatio()).eq(100); + expect(await forwarder.toGaugesRatio()).eq(100); + + const v0 = await platformVoter.votes(1, 0); + const v1 = await platformVoter.votes(1, 1); + const v2 = await platformVoter.votes(1, 2); + const v3 = await platformVoter.votes(1, 3); + expect(v0._type).eq(1); + expect(v1._type).eq(2); + expect(v2._type).eq(3); + expect(v3._type).eq(3); + expect(v2.target).eq(platformVoter.address); + expect(v3.target).eq(ve.address); + + await TimeUtils.advanceBlocksOnTs(WEEK); + + await platformVoter.reset(1, [2, 3], [Misc.ZERO_ADDRESS, platformVoter.address]); + + expect(await platformVoter.veVotesLength(1)).eq(2); + + const v0New = await platformVoter.votes(1, 0); + const v1New = await platformVoter.votes(1, 1); + expect(v0New._type).eq(1); + expect(v1New._type).eq(3); + expect(v1New.target).eq(ve.address); + }); + + + it("emergency reset vote test", async function () { + await expect(platformVoter.connect(owner3).emergencyResetVote(1, 2, true)).revertedWith('!gov'); + + expect(await forwarder.toInvestFundRatio()).eq(0); + expect(await forwarder.toGaugesRatio()).eq(0); + + await platformVoter.vote(1, 1, 100, Misc.ZERO_ADDRESS); + await platformVoter.vote(1, 2, 100, Misc.ZERO_ADDRESS); + await platformVoter.vote(1, 3, 100, platformVoter.address); + await platformVoter.vote(1, 3, 100, ve.address); + + expect(await platformVoter.veVotesLength(1)).eq(4); + + expect(await forwarder.toInvestFundRatio()).eq(100); + expect(await forwarder.toGaugesRatio()).eq(100); + + const vv1 = [ + await platformVoter.votes(1, 0), + await platformVoter.votes(1, 1), + await platformVoter.votes(1, 2), + await platformVoter.votes(1, 3) + ]; + expect(vv1[0]._type).eq(1); + expect(vv1[1]._type).eq(2); + expect(vv1[2]._type).eq(3); + expect(vv1[3]._type).eq(3); + expect(vv1[2].target).eq(platformVoter.address); + expect(vv1[3].target).eq(ve.address); + + await platformVoter.emergencyResetVote(1, 2, true); + + expect(await platformVoter.veVotesLength(1)).eq(3); + + const vv2 = [ + await platformVoter.votes(1, 0), + await platformVoter.votes(1, 1), + await platformVoter.votes(1, 2), + ]; + expect(vv2[0]._type).eq(1); + expect(vv2[1]._type).eq(2); + expect(vv2[2]._type).eq(3); + expect(vv2[2].target).eq(ve.address); + + await platformVoter.emergencyResetVote(1, 1, false); + + expect(await platformVoter.veVotesLength(1)).eq(2); + + const vv3 = [ + await platformVoter.votes(1, 0), + await platformVoter.votes(1, 1), + ]; + expect(vv3[0]._type).eq(1); + expect(vv3[1]._type).eq(3); + expect(vv3[1].target).eq(ve.address); + + expect(await forwarder.toInvestFundRatio()).eq(100); + expect(await forwarder.toGaugesRatio()).eq(100); + + }); + + it("emergency Adjust Weights test", async function () { + await platformVoter.emergencyAdjustWeights(1, Misc.ZERO_ADDRESS, 100, 100); + await expect(platformVoter.connect(owner3).emergencyAdjustWeights(1, Misc.ZERO_ADDRESS, 100, 1)).revertedWith('!gov'); + await expect(platformVoter.emergencyAdjustWeights(1, Misc.ZERO_ADDRESS, 1, 1000_000)).revertedWith('!ratio'); + }); + it("reset vote with zero value test", async function () { await platformVoter.vote(1, 1, 0, Misc.ZERO_ADDRESS); await TimeUtils.advanceBlocksOnTs(WEEK); @@ -228,8 +330,15 @@ describe("Platform voter tests", function () { // vote for not strategy should not revert await platformVoter.vote(1, 3, 50000, platformVoter.address); + expect(await platformVoter.veVotesLength(1)).eq(2); await TimeUtils.advanceBlocksOnTs(WEEK * 8); await platformVoter.poke(1); + expect(await platformVoter.veVotesLength(1)).eq(2); + + // poke for ended ve should not revert + await TimeUtils.advanceBlocksOnTs(WEEK * 52); + await platformVoter.poke(1); + expect(await platformVoter.veVotesLength(1)).eq(0); }); it("re vote test", async function () {