diff --git a/contracts/Comptroller.sol b/contracts/Comptroller.sol index 9ba55ab11..13ddf7b79 100644 --- a/contracts/Comptroller.sol +++ b/contracts/Comptroller.sol @@ -1077,39 +1077,6 @@ contract Comptroller is ComptrollerV6Storage, ComptrollerInterface, ComptrollerE function _become(Unitroller unitroller) public { require(msg.sender == unitroller.admin(), "only unitroller admin can change brains"); require(unitroller._acceptImplementation() == 0, "change not authorized"); - - // TODO: Remove this post upgrade - Comptroller(address(unitroller))._upgradeSplitCompRewards(); - } - - function _upgradeSplitCompRewards() public { - require(msg.sender == comptrollerImplementation, "only brains can become itself"); - - uint32 blockNumber = safe32(getBlockNumber(), "block number exceeds 32 bits"); - - // compSpeeds -> compBorrowSpeeds & compSupplySpeeds t - for (uint i = 0; i < allMarkets.length; i ++) { - compBorrowSpeeds[address(allMarkets[i])] = compSupplySpeeds[address(allMarkets[i])] = compSpeeds[address(allMarkets[i])]; - delete compSpeeds[address(allMarkets[i])]; - - /* - * Ensure supply and borrow state indices are all set. If not set, update to default value - */ - CompMarketState storage supplyState = compSupplyState[address(allMarkets[i])]; - CompMarketState storage borrowState = compBorrowState[address(allMarkets[i])]; - - if (supplyState.index == 0) { - // Initialize supply state index with default value - supplyState.index = compInitialIndex; - supplyState.block = blockNumber; - } - - if (borrowState.index == 0) { - // Initialize borrow state index with default value - borrowState.index = compInitialIndex; - borrowState.block = blockNumber; - } - } } /** @@ -1214,7 +1181,7 @@ contract Comptroller is ComptrollerV6Storage, ComptrollerInterface, ComptrollerE // Update supplier's index to the current index since we are distributing accrued COMP compSupplierIndex[cToken][supplier] = supplyIndex; - if (supplierIndex == 0 && supplyIndex > compInitialIndex) { + if (supplierIndex == 0 && supplyIndex >= compInitialIndex) { // Covers the case where users supplied tokens before the market's supply state index was set. // Rewards the user with COMP accrued from the start of when supplier rewards were first // set for the market. @@ -1253,7 +1220,7 @@ contract Comptroller is ComptrollerV6Storage, ComptrollerInterface, ComptrollerE // Update borrowers's index to the current index since we are distributing accrued COMP compBorrowerIndex[cToken][borrower] = borrowIndex; - if (borrowerIndex == 0 && borrowIndex > compInitialIndex) { + if (borrowerIndex == 0 && borrowIndex >= compInitialIndex) { // Covers the case where users borrowed tokens before the market's borrow state index was set. // Rewards the user with COMP accrued from the start of when borrower rewards were first // set for the market. @@ -1348,6 +1315,18 @@ contract Comptroller is ComptrollerV6Storage, ComptrollerInterface, ComptrollerE * @return The amount of COMP which was NOT transferred to the user */ function grantCompInternal(address user, uint amount) internal returns (uint) { + for (uint i = 0; i < allMarkets.length; ++i) { + address market = address(allMarkets[i]); + + bool noOriginalSpeed = compBorrowSpeeds[market] == 0; + bool invalidSupply = noOriginalSpeed && compSupplierIndex[market][user] > 0; + bool invalidBorrow = noOriginalSpeed && compBorrowerIndex[market][user] > 0; + + if (invalidSupply || invalidBorrow) { + return amount; + } + } + Comp comp = Comp(getCompAddress()); uint compRemaining = comp.balanceOf(address(this)); if (amount > 0 && amount <= compRemaining) { diff --git a/scenario/src/Contract/Comptroller.ts b/scenario/src/Contract/Comptroller.ts index 410f30a35..385faad89 100644 --- a/scenario/src/Contract/Comptroller.ts +++ b/scenario/src/Contract/Comptroller.ts @@ -58,7 +58,8 @@ interface ComptrollerMethods { compSpeeds(string): Callable compSupplySpeeds(string): Callable compBorrowSpeeds(string): Callable - claimComp(string): Sendable + claimComp(holder: string): Sendable + claimComp(holder: string, cTokens: string[]): Sendable updateContributorRewards(account: string): Sendable _grantComp(account: string, encodedNumber): Sendable _setCompRate(encodedNumber): Sendable diff --git a/scenario/src/Event/ComptrollerEvent.ts b/scenario/src/Event/ComptrollerEvent.ts index ebdb461c9..e29e92b55 100644 --- a/scenario/src/Event/ComptrollerEvent.ts +++ b/scenario/src/Event/ComptrollerEvent.ts @@ -248,6 +248,18 @@ async function claimComp(world: World, from: string, comptroller: Comptroller, h return world; } +async function claimCompInMarkets(world: World, from: string, comptroller: Comptroller, holder: string, cTokens: CToken[]): Promise { + let invokation = await invoke(world, comptroller.methods.claimComp(holder, cTokens.map(c => c._address)), from, ComptrollerErrorReporter); + + world = addAction( + world, + `Comp claimed by ${holder} in markets ${cTokens.map(c => c.name)}`, + invokation + ); + + return world; +} + async function updateContributorRewards(world: World, from: string, comptroller: Comptroller, contributor: string): Promise { let invokation = await invoke(world, comptroller.methods.updateContributorRewards(contributor), from, ComptrollerErrorReporter); @@ -760,6 +772,20 @@ export function comptrollerCommands() { ], (world, from, {comptroller, holder}) => claimComp(world, from, comptroller, holder.val) ), + new Command<{comptroller: Comptroller, holder: AddressV, cTokens: CToken[]}>(` + #### ClaimCompInMarkets + + * "Comptroller ClaimComp ( ...)" - Claims comp + * E.g. "Comptroller ClaimCompInMarkets Geoff (cDAI cBAT) + `, + "ClaimCompInMarkets", + [ + new Arg("comptroller", getComptroller, {implicit: true}), + new Arg("holder", getAddressV), + new Arg("cTokens", getCTokenV, {mapped: true}) + ], + (world, from, {comptroller, holder, cTokens}) => claimCompInMarkets(world, from, comptroller, holder.val, cTokens) + ), new Command<{comptroller: Comptroller, contributor: AddressV}>(` #### UpdateContributorRewards diff --git a/spec/scenario/Flywheel/Flywheel.scen b/spec/scenario/Flywheel/Flywheel.scen index 2dcef6f87..56411863c 100644 --- a/spec/scenario/Flywheel/Flywheel.scen +++ b/spec/scenario/Flywheel/Flywheel.scen @@ -261,6 +261,10 @@ Test "New COMP speeds apply to both prior borrowers+suppliers and later borrower Assert Equal (Erc20 COMP TokenBalance Geoff) 1000 Assert Equal (Erc20 COMP TokenBalance Jared) 1000 Assert Equal (Erc20 COMP TokenBalance Torrey) 1000 + Assert Equal (Comptroller CompAccrued Coburn) 0 + Assert Equal (Comptroller CompAccrued Geoff) 0 + Assert Equal (Comptroller CompAccrued Jared) 0 + Assert Equal (Comptroller CompAccrued Torrey) 0 Assert Equal (Comptroller CompBorrowerIndex cYFI Coburn) (Comptroller CompBorrowerIndex cYFI Geoff) Assert Equal (Comptroller CompSupplierIndex cYFI Jared) (Comptroller CompSupplierIndex cYFI Torrey) @@ -301,5 +305,51 @@ Test "New COMP speeds apply to both prior borrowers+suppliers and later borrower Assert Equal (Erc20 COMP TokenBalance Geoff) 1000 Assert Equal (Erc20 COMP TokenBalance Jared) 1000 Assert Equal (Erc20 COMP TokenBalance Torrey) 1000 + Assert Equal (Comptroller CompAccrued Coburn) 0 + Assert Equal (Comptroller CompAccrued Geoff) 0 + Assert Equal (Comptroller CompAccrued Jared) 0 + Assert Equal (Comptroller CompAccrued Torrey) 0 + Assert Equal (Comptroller CompBorrowerIndex cYFI Coburn) (Comptroller CompBorrowerIndex cYFI Geoff) + Assert Equal (Comptroller CompSupplierIndex cYFI Jared) (Comptroller CompSupplierIndex cYFI Torrey) + +Test "Zero COMP speed markets don't accrue rewards w/ uninitialized borrower/supplier state indices" + -- Supplying + -- Torrey mints 100e18 before COMP speeds set + -- Jared mints 100e18 after COMP speeds set + -- Borrowing + -- Coburn borrows 10e18 before COMP speeds set + -- Geoff borrows 10e18 after COMP speeds set + FlywheelComptroller + InitUsage2 + -- New token with no rewards + NewCToken YFI cYFI + Support cYFI collateralFactor:0.5 + Prep Torrey 100e18 YFI cYFI + Mint Torrey 100e18 cYFI + Prep Jared 100e18 YFI cYFI + Borrow Coburn 10e18 cYFI + EnterMarkets Geoff cZRX + -- Set borrower and supplier state indices to 0 (uninitialized - before all market state indices were initialized properly) + Comptroller Send "setCompBorrowerIndex(address,address,uint256)" (Address cYFI) (Address Coburn) 0 + Comptroller Send "setCompSupplierIndex(address,address,uint256)" (Address cYFI) (Address Torrey) 0 + Mint Jared 100e18 cYFI + Borrow Geoff 10e18 cYFI + Assert Equal (Comptroller CompBorrowerIndex cYFI Coburn) 0 + Assert Equal (Comptroller CompBorrowerIndex cYFI Geoff) 1e36 + Assert Equal (Comptroller CompSupplierIndex cYFI Torrey) 0 + Assert Equal (Comptroller CompSupplierIndex cYFI Jared) 1e36 + FastForward 1000 blocks + Comptroller ClaimComp Jared + Comptroller ClaimComp Torrey + Comptroller ClaimComp Geoff + Comptroller ClaimComp Coburn + Assert Equal (Erc20 COMP TokenBalance Coburn) 0 + Assert Equal (Erc20 COMP TokenBalance Geoff) 0 + Assert Equal (Erc20 COMP TokenBalance Jared) 0 + Assert Equal (Erc20 COMP TokenBalance Torrey) 0 + Assert Equal (Comptroller CompAccrued Coburn) 0 + Assert Equal (Comptroller CompAccrued Geoff) 0 + Assert Equal (Comptroller CompAccrued Jared) 0 + Assert Equal (Comptroller CompAccrued Torrey) 0 Assert Equal (Comptroller CompBorrowerIndex cYFI Coburn) (Comptroller CompBorrowerIndex cYFI Geoff) Assert Equal (Comptroller CompSupplierIndex cYFI Jared) (Comptroller CompSupplierIndex cYFI Torrey) diff --git a/spec/sim/0064-fix-comp-accruals/hypothetical_mainnet_upgrade.scen b/spec/sim/0064-fix-comp-accruals/hypothetical_mainnet_upgrade.scen new file mode 100755 index 000000000..64d6797ae --- /dev/null +++ b/spec/sim/0064-fix-comp-accruals/hypothetical_mainnet_upgrade.scen @@ -0,0 +1,109 @@ +#!/usr/bin/env -S yarn repl -s + +PrintTransactionLogs + +-- Token holder addresses for mocking +Alias CompHolder "0x7587caefc8096f5f40acb83a09df031a018c66ec" +Alias CUSDCHolder "0xF977814e90dA44bFA03b6295A0616a897441aceC" -- Binance 8 +Alias DaiHolder "0x5f65f7b609678448494De4C87521CdF6cEf1e932" -- Gemini 4 + +Alias Timelock "0x6d903f6003cca6255D85CcA4D3B5E5146dC33925" + +-- Fork the block prior to proposal 62 executing +Web3Fork "https://mainnet-eth.compound.finance/@13322797" (CompHolder CUSDCHolder DaiHolder Timelock) +UseConfigs mainnet + +-- Disable USDC COMP rewards so that these rewards won't mess with our results +Send Timelock 1e18 +From Timelock (Comptroller SetCompSpeed cUSDC 0) + +-- Send USDC from CUSDCHolder to other accounts +From CUSDCHolder (Erc20 USDC Transfer Geoff 1000000e6) +From CUSDCHolder (Erc20 USDC Transfer Torrey 1000000e6) +From CUSDCHolder (Erc20 USDC Transfer Coburn 1000000e6) +From CUSDCHolder (Erc20 USDC Transfer Jared 1000000e6) + +-- Send DAI from DaiHolder to other accounts +From DaiHolder (Erc20 DAI Transfer Coburn 1000000e18) +From DaiHolder (Erc20 DAI Transfer Jared 1000000e18) + +-- Pre Proposal 62: Deposit collateral and borrow SUSHI +From Geoff (Erc20 USDC Approve (Address cUSDC) 1000000e6) +From Geoff (CToken cUSDC Mint 1000000e6) +From Geoff (Comptroller EnterMarkets (cUSDC)) +From Geoff (CToken cSUSHI Borrow 1000e18) + +From Torrey (Erc20 USDC Approve (Address cUSDC) 1000000e6) +From Torrey (CToken cUSDC Mint 1000000e6) +From Torrey (Comptroller EnterMarkets (cUSDC)) +From Torrey (CToken cSUSHI Borrow 1000e18) + +-- Execute proposal 62 +GovernorBravo GovernorBravo Proposal LastProposal Execute +MineBlock + +-- Claim COMP for Geoff +Comptroller ClaimComp Geoff + +-- Check Geoff COMP rewards - invalid accrual & COMP sent +Assert Equal (Erc20 COMP TokenBalance Geoff) (988792275103122749560) -- Huge amount (988 COMP) -> Bug +Assert Equal (Comptroller CompAccrued Geoff) (0) + +-- Cause Torrey to accrue COMP without claiming +From Torrey (CToken cSUSHI Borrow 1e18) -- Causes COMP to be distributed but not claimed + +-- Deploy latest Comptroller +ComptrollerImpl Deploy Standard NewComptroller + +-- Delegate and propose update (containing bug fix) +From CompHolder (Comp Delegate CompHolder) +From CompHolder (GovernorBravo GovernorBravo Propose "Upgrade Comptroller" [(Address Unitroller) (Address NewComptroller)] [0 0] ["_setPendingImplementation(address)" "_become(address)"] [[(Address NewComptroller)] [(Address Unitroller)]]) + +-- Fast forward, vote, queue, execute +MineBlock +AdvanceBlocks 14000 +From CompHolder (GovernorBravo GovernorBravo Proposal LastProposal Vote For) +AdvanceBlocks 20000 +GovernorBravo GovernorBravo Proposal LastProposal Queue +IncreaseTime 604910 +GovernorBravo GovernorBravo Proposal LastProposal Execute +MineBlock + +-- Claim COMP for Torrey +Comptroller ClaimComp Torrey + +-- Check Torrey COMP balance changes - invalid accrual & COMP not sent +Assert Equal (Erc20 COMP TokenBalance Torrey) (0) +Assert Equal (Comptroller CompAccrued Torrey) (988792086947769887785) -- Huge amount (988 COMP) -> Bug + +-- Post bug fix: Deposit collateral (DAI) and borrow SUSHI - block COMP sending +From Coburn (Erc20 DAI Approve (Address cDAI) 1000000e18) +From Coburn (CToken cDAI Mint 1000000e18) +From Coburn (Comptroller EnterMarkets (cDAI)) +From Coburn (CToken cSUSHI Borrow 1000e18) + +-- Post bug fix: Deposit collateral (DAI) and borrow BAT - don't block COMP sending +From Jared (Erc20 DAI Approve (Address cDAI) 1000000e18) +From Jared (CToken cDAI Mint 1000000e18) +From Jared (Comptroller EnterMarkets (cDAI)) +From Jared (CToken cBAT Borrow 1000e18) + +-- Accrue rewards (if any) +MineBlock +AdvanceBlocks 14000 + +-- Claim COMP for Coburn +Comptroller ClaimCompInMarkets Coburn (cDAI cSUSHI) + +-- Claim COMP for Jared +Comptroller ClaimCompInMarkets Jared (cDAI cBAT) + +-- Check Coburn COMP balance changes - valid accrual & COMP not sent (claim in affected market) +Assert Equal (Erc20 COMP TokenBalance Coburn) (0) +Assert Equal (Comptroller CompAccrued Coburn) (211455443766873733) -- 0.21 COMP accrued + +-- Check Jared COMP balance changes - valid accrual & COMP sent (no claim in affected market) +Assert Equal (Erc20 COMP TokenBalance Jared) (212379370589809042) -- 0.21 COMP claimed +Assert Equal (Comptroller CompAccrued Jared) (0) + +Print "Done" \ No newline at end of file