From d84c5dd62799e197d0b2ee0137fab6808bc4df31 Mon Sep 17 00:00:00 2001 From: taek Date: Thu, 9 Dec 2021 18:22:11 +0900 Subject: [PATCH 1/3] fixed/tested --- contracts/general/Ownable.sol | 56 +++++++++++++++++++++-- contracts/staking/FarmController.sol | 2 +- hardhat.config.ts | 8 ++-- test/core/RewardManagerV2_mainnet.test.ts | 2 +- test/fork.test.ts | 52 +++++++++++++++++++++ 5 files changed, 110 insertions(+), 10 deletions(-) create mode 100644 test/fork.test.ts 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..c344051 100644 --- a/contracts/staking/FarmController.sol +++ b/contracts/staking/FarmController.sol @@ -108,7 +108,7 @@ contract FarmController is Ownable { uint256 cacheBorrowerReward = borrowerRewards; IRewardDistributionRecipientTokenOnly borrowerFarm = IRewardDistributionRecipientTokenOnly(borrowerReward); rewardToken.approve(address(borrowerFarm), cacheBorrowerReward); - stakerFarm.notifyRewardAmount(cacheBorrowerReward); + borrowerFarm.notifyRewardAmount(cacheBorrowerReward); lastRewardDistributed += 7 days; } diff --git a/hardhat.config.ts b/hardhat.config.ts index 7d29030..eb9ff2c 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -45,10 +45,10 @@ export default { }, allowUnlimitedContractSize: true, timeout: 1000000, - //forking: { - // url: "https://eth-mainnet.alchemyapi.io/v2/90dtUWHmLmwbYpvIeC53UpAICALKyoIu", - // blockNumber: 12049335 - //} + forking: { + url: "https://eth-mainnet.alchemyapi.io/v2/90dtUWHmLmwbYpvIeC53UpAICALKyoIu", + blockNumber: 13769255 + } }, coverage: { url: "http://localhost:8555", diff --git a/test/core/RewardManagerV2_mainnet.test.ts b/test/core/RewardManagerV2_mainnet.test.ts index 3f07464..c361638 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.skip("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..10e6e67 --- /dev/null +++ b/test/fork.test.ts @@ -0,0 +1,52 @@ +import { network, ethers } from "hardhat"; +import { providers, Contract, Signer, BigNumber, utils } from "ethers"; +import { expect } from "chai"; +import { 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); + 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() { + 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)); + }); +}); From b28c3b7bdde2a58c33786a9d21973db023875536 Mon Sep 17 00:00:00 2001 From: taek Date: Thu, 9 Dec 2021 18:22:54 +0900 Subject: [PATCH 2/3] removed only --- test/core/RewardManagerV2_mainnet.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/core/RewardManagerV2_mainnet.test.ts b/test/core/RewardManagerV2_mainnet.test.ts index c361638..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.skip("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"; From 5d66dc85ac08356e2fc74e8f9e83d9cd21d932d4 Mon Sep 17 00:00:00 2001 From: taek Date: Fri, 10 Dec 2021 19:37:58 +0900 Subject: [PATCH 3/3] added timestamp update mechanism --- contracts/staking/FarmController.sol | 31 ++++++++++++++++++---------- test/fork.test.ts | 9 +++++++- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/contracts/staking/FarmController.sol b/contracts/staking/FarmController.sol index c344051..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); - borrowerFarm.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/fork.test.ts b/test/fork.test.ts index 10e6e67..a07dfc7 100644 --- a/test/fork.test.ts +++ b/test/fork.test.ts @@ -1,7 +1,8 @@ import { network, ethers } from "hardhat"; import { providers, Contract, Signer, BigNumber, utils } from "ethers"; import { expect } from "chai"; -import { increase, getTimestamp } from './utils'; +import { mine, increase, getTimestamp } from './utils'; + const multisig_mainnet = "0x1f28ed9d4792a567dad779235c2b766ab84d8e33"; const controller_mainnet = "0x1337DEF159da6F97dB7c4D0E257dc689837b9E70"; const staker_mainnet = "0x1337DEF1B1Ae35314b40e5A4b70e216A499b0E37"; @@ -26,6 +27,7 @@ describe.only('fork', function(){ 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); @@ -34,6 +36,10 @@ describe.only('fork', function(){ }); 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 = { @@ -48,5 +54,6 @@ describe.only('fork', function(){ expect(afterBalance.staker).to.equal(beforeBalance.staker.add(100)); expect(afterBalance.borrower).to.equal(beforeBalance.borrower.add(1)); + console.log((await farmController.lastRewardDistributed()).toString()); }); });