diff --git a/contracts/general/Ownable.sol b/contracts/general/Ownable.sol index 813572b..6ac2dbb 100644 --- a/contracts/general/Ownable.sol +++ b/contracts/general/Ownable.sol @@ -6,14 +6,19 @@ pragma solidity ^0.6.6; * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". - * - * @dev Completely default OpenZeppelin. + * + * @dev We've added a second owner to share control of the timelocked owner contract. */ contract Ownable { address private _owner; address private _pendingOwner; + + // Second allows a DAO to share control. + address private _secondOwner; + address private _pendingSecond; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + event SecondOwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender @@ -22,7 +27,9 @@ contract Ownable { function initializeOwnable() internal { require(_owner == address(0), "already initialized"); _owner = msg.sender; + _secondOwner = msg.sender; emit OwnershipTransferred(address(0), msg.sender); + emit SecondOwnershipTransferred(address(0), msg.sender); } @@ -33,6 +40,13 @@ contract Ownable { return _owner; } + /** + * @return the address of the owner. + */ + function secondOwner() public view returns (address) { + return _secondOwner; + } + /** * @dev Throws if called by any account other than the owner. */ @@ -40,12 +54,22 @@ contract Ownable { require(isOwner(), "msg.sender is not owner"); _; } + + modifier onlyFirstOwner() { + require(msg.sender == _owner, "msg.sender is not owner"); + _; + } + + modifier onlySecondOwner() { + require(msg.sender == _secondOwner, "msg.sender is not owner"); + _; + } /** * @return true if `msg.sender` is the owner of the contract. */ function isOwner() public view returns (bool) { - return msg.sender == _owner; + return msg.sender == _owner || msg.sender == _secondOwner; } @@ -53,7 +77,7 @@ contract Ownable { * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ - function transferOwnership(address newOwner) public onlyOwner { + function transferOwnership(address newOwner) public onlyFirstOwner { _pendingOwner = newOwner; } @@ -73,6 +97,30 @@ contract Ownable { _owner = newOwner; } + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function transferSecondOwnership(address newOwner) public onlySecondOwner { + _pendingSecond = newOwner; + } + + function receiveSecondOwnership() public { + require(msg.sender == _pendingSecond, "only pending owner can call this function"); + _transferSecondOwnership(_pendingSecond); + _pendingSecond = address(0); + } + + /** + * @dev Transfers control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function _transferSecondOwnership(address newOwner) internal { + require(newOwner != address(0)); + emit SecondOwnershipTransferred(_secondOwner, newOwner); + _secondOwner = newOwner; + } + uint256[50] private __gap; } diff --git a/contracts/staking/FarmController.sol b/contracts/staking/FarmController.sol index 3628cef..e365177 100644 --- a/contracts/staking/FarmController.sol +++ b/contracts/staking/FarmController.sol @@ -17,7 +17,7 @@ contract FarmController is Ownable { address public constant borrowerReward = 0x1337DEF172152f2fF82d9545Fd6f79fE38dF15ce; - uint256 public constant INITIAL_DISTRIBUTED = 1638082800; + uint256 public constant INITIAL_DISTRIBUTED = 1638687600; IRewardDistributionRecipientTokenOnly[] public farms; mapping(address => address) public lpFarm; @@ -84,7 +84,7 @@ contract FarmController is Ownable { } function initializeRewardDistribution() external onlyOwner { - require(lastRewardDistributed > 0, "initialized"); + require(lastRewardDistributed == 0, "initialized"); lastRewardDistributed = INITIAL_DISTRIBUTED; } @@ -96,21 +96,30 @@ contract FarmController is Ownable { for(uint256 i = 0; i 0) { + rewardToken.approve(address(farm), amount); + farm.notifyRewardAmount(amount); + } } uint256 cacheStakerReward = stakerRewards; - IRewardDistributionRecipientTokenOnly stakerFarm = IRewardDistributionRecipientTokenOnly(stakerReward); - rewardToken.approve(address(stakerFarm), cacheStakerReward); - stakerFarm.notifyRewardAmount(cacheStakerReward); + if(cacheStakerReward > 0) { + IRewardDistributionRecipientTokenOnly stakerFarm = IRewardDistributionRecipientTokenOnly(stakerReward); + rewardToken.approve(address(stakerFarm), cacheStakerReward); + stakerFarm.notifyRewardAmount(cacheStakerReward); + } uint256 cacheBorrowerReward = borrowerRewards; - IRewardDistributionRecipientTokenOnly borrowerFarm = IRewardDistributionRecipientTokenOnly(borrowerReward); - rewardToken.approve(address(borrowerFarm), cacheBorrowerReward); - stakerFarm.notifyRewardAmount(cacheBorrowerReward); + if(cacheBorrowerReward > 0) { + IRewardDistributionRecipientTokenOnly borrowerFarm = IRewardDistributionRecipientTokenOnly(borrowerReward); + rewardToken.approve(address(borrowerFarm), cacheBorrowerReward); + borrowerFarm.notifyRewardAmount(cacheBorrowerReward); + } - lastRewardDistributed += 7 days; + // this will make sure lastRewardDistributed to set in order + while(block.timestamp - lastRewardDistributed > 7 days) { + lastRewardDistributed += 7 days; + } } // should transfer rewardToken prior to calling this contract diff --git a/test/core/RewardManagerV2_mainnet.test.ts b/test/core/RewardManagerV2_mainnet.test.ts index 3f07464..3d77897 100644 --- a/test/core/RewardManagerV2_mainnet.test.ts +++ b/test/core/RewardManagerV2_mainnet.test.ts @@ -120,7 +120,7 @@ describe("RewardManagerV2 Mainnet Fork", function () { expect(userInfo.amount).to.equal(coverPrice); }); - it.only("should withdraw from rewardV2 when withdrawNft", async function () { + it("should withdraw from rewardV2 when withdrawNft", async function () { // Allow user const userAddress = "0x09fa38eba245bb68354b8950fa2fe71f02863393"; const protocol = "0x0000000000000000000000000000000000000001"; diff --git a/test/fork.test.ts b/test/fork.test.ts new file mode 100644 index 0000000..a07dfc7 --- /dev/null +++ b/test/fork.test.ts @@ -0,0 +1,59 @@ +import { network, ethers } from "hardhat"; +import { providers, Contract, Signer, BigNumber, utils } from "ethers"; +import { expect } from "chai"; +import { mine, increase, getTimestamp } from './utils'; + +const multisig_mainnet = "0x1f28ed9d4792a567dad779235c2b766ab84d8e33"; +const controller_mainnet = "0x1337DEF159da6F97dB7c4D0E257dc689837b9E70"; +const staker_mainnet = "0x1337DEF1B1Ae35314b40e5A4b70e216A499b0E37"; +const borrower_mainnet = "0x1337DEF172152f2fF82d9545Fd6f79fE38dF15ce"; +const token_mainnet = "0x1337def16f9b486faed0293eb623dc8395dfe46a"; +let multisig : Signer; + +let farmController : Contract; +let stakerFarm : Contract; +let borrowerFarm : Contract; +let token: Contract; + +describe.only('fork', function(){ + beforeEach(async function(){ + await network.provider.request({ + method: "hardhat_impersonateAccount", + params: [multisig_mainnet] + }); + multisig = await ethers.provider.getSigner(multisig_mainnet); + const proxy = await ethers.getContractAt("OwnedUpgradeabilityProxy", controller_mainnet); + const NewTemplate = await ethers.getContractFactory("FarmController"); + const template = await NewTemplate.deploy(); + await proxy.connect(multisig).upgradeTo(template.address); + farmController = await ethers.getContractAt("FarmController", proxy.address); + await farmController.connect(multisig).initializeRewardDistribution(); + stakerFarm = await ethers.getContractAt("UtilizationFarm", staker_mainnet); + borrowerFarm = await ethers.getContractAt("UtilizationFarm", borrower_mainnet); + token = await ethers.getContractAt("contracts/interfaces/IERC20.sol:IERC20", token_mainnet); + await stakerFarm.connect(multisig).setRewardDistribution(proxy.address); + await borrowerFarm.connect(multisig).setRewardDistribution(proxy.address); + }); + + it.only('changeProtocol - stakeManual', async function() { + console.log((await getTimestamp()).toString()); + await increase( 3* 86400 + 3 * 60 * 60); + await mine(); + console.log((await getTimestamp()).toString()); + await farmController.connect(multisig).setRewards("10000", "100", "1"); + await token.connect(multisig).transfer(farmController.address, "10101"); + const beforeBalance = { + staker: await token.balanceOf(staker_mainnet), + borrower : await token.balanceOf(borrower_mainnet) + } + await farmController.flushRewards(); + const afterBalance = { + staker: await token.balanceOf(staker_mainnet), + borrower : await token.balanceOf(borrower_mainnet) + } + + expect(afterBalance.staker).to.equal(beforeBalance.staker.add(100)); + expect(afterBalance.borrower).to.equal(beforeBalance.borrower.add(1)); + console.log((await farmController.lastRewardDistributed()).toString()); + }); +});