From 5334ba9aaf75d0e22a7845003f37e0fc8c326084 Mon Sep 17 00:00:00 2001 From: alexcos20 Date: Mon, 24 Apr 2023 05:23:09 -0700 Subject: [PATCH 1/2] add contract --- contracts/ve/veFeeOwner.sol | 70 +++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 contracts/ve/veFeeOwner.sol diff --git a/contracts/ve/veFeeOwner.sol b/contracts/ve/veFeeOwner.sol new file mode 100644 index 00000000..27804478 --- /dev/null +++ b/contracts/ve/veFeeOwner.sol @@ -0,0 +1,70 @@ +// BigchainDB GmbH and Ocean Protocol contributors +// SPDX-License-Identifier: (Apache-2.0 AND MIT) + +pragma solidity 0.8.12; + +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + + +interface IveFeeDistributor{ + function token() external view returns(address); + function checkpoint_token() external; + function checkpoint_total_supply() external; + function commit_admin(address) external; + function apply_admin() external; + function toggle_allow_checkpoint_token() external; + function kill_me() external; + function recover_balance(address) external; +} + +contract veFeeDistributorOwner is Ownable{ + address private veFeeDistributorToken; + IveFeeDistributor private veFeeDistributorContract; + + + constructor( + address _veFeeDistributor + ) payable { + require( + _veFeeDistributor != address(0), + "_veFeeDistributor is zero address" + ); + veFeeDistributorContract=IveFeeDistributor(_veFeeDistributor); + veFeeDistributorToken=IveFeeDistributor(_veFeeDistributor).token(); + require( + veFeeDistributorToken != address(0), + "_veFeeDistributor token is zero address" + ); + } + + function checkpoint_token() external{ + veFeeDistributorContract.checkpoint_token(); + } + function checkpoint_total_supply() external{ + veFeeDistributorContract.checkpoint_total_supply(); + } + function checkpoint() external{ + veFeeDistributorContract.checkpoint_token(); + veFeeDistributorContract.checkpoint_total_supply(); + } + function commit_admin(address admin) external onlyOwner{ + veFeeDistributorContract.commit_admin(admin); + } + function apply_admin() external onlyOwner{ + veFeeDistributorContract.apply_admin(); + } + function toggle_allow_checkpoint_token() external onlyOwner{ + veFeeDistributorContract.toggle_allow_checkpoint_token(); + } + function kill_me() external onlyOwner{ + veFeeDistributorContract.kill_me(); + uint256 balance = IERC20(veFeeDistributorToken).balanceOf(address(this)); + SafeERC20.safeTransfer(IERC20(veFeeDistributorToken), owner(), balance); + } + function recover_balance(address token) external onlyOwner{ + veFeeDistributorContract.recover_balance(token); + uint256 balance = IERC20(token).balanceOf(address(this)); + SafeERC20.safeTransfer(IERC20(token), owner(), balance); + } +} \ No newline at end of file From ff118e3a699b8e809560930a076ebcd27883fece Mon Sep 17 00:00:00 2001 From: alexcos20 Date: Mon, 24 Apr 2023 23:05:59 -0700 Subject: [PATCH 2/2] add tests --- ...FeeOwner.sol => veFeeDistributorOwner.sol} | 32 ++-- test/unit/ve/veFeeOwner.test.js | 141 ++++++++++++++++++ 2 files changed, 159 insertions(+), 14 deletions(-) rename contracts/ve/{veFeeOwner.sol => veFeeDistributorOwner.sol} (63%) create mode 100644 test/unit/ve/veFeeOwner.test.js diff --git a/contracts/ve/veFeeOwner.sol b/contracts/ve/veFeeDistributorOwner.sol similarity index 63% rename from contracts/ve/veFeeOwner.sol rename to contracts/ve/veFeeDistributorOwner.sol index 27804478..85b42b74 100644 --- a/contracts/ve/veFeeOwner.sol +++ b/contracts/ve/veFeeDistributorOwner.sol @@ -19,10 +19,14 @@ interface IveFeeDistributor{ } contract veFeeDistributorOwner is Ownable{ - address private veFeeDistributorToken; - IveFeeDistributor private veFeeDistributorContract; - + address public immutable veFeeDistributorToken; + address public immutable veFeeDistributorContract; + event CommitAdmin(address admin); + event ApplyAdmin(address admin); + event ToggleAllowCheckpointToken(bool toogle_flag); + event CheckpointToken(uint256 time,uint256 tokens); + constructor( address _veFeeDistributor ) payable { @@ -30,40 +34,40 @@ contract veFeeDistributorOwner is Ownable{ _veFeeDistributor != address(0), "_veFeeDistributor is zero address" ); - veFeeDistributorContract=IveFeeDistributor(_veFeeDistributor); + veFeeDistributorContract=_veFeeDistributor; veFeeDistributorToken=IveFeeDistributor(_veFeeDistributor).token(); require( veFeeDistributorToken != address(0), "_veFeeDistributor token is zero address" ); } - + function checkpoint_token() external{ - veFeeDistributorContract.checkpoint_token(); + IveFeeDistributor(veFeeDistributorContract).checkpoint_token(); } function checkpoint_total_supply() external{ - veFeeDistributorContract.checkpoint_total_supply(); + IveFeeDistributor(veFeeDistributorContract).checkpoint_total_supply(); } function checkpoint() external{ - veFeeDistributorContract.checkpoint_token(); - veFeeDistributorContract.checkpoint_total_supply(); + IveFeeDistributor(veFeeDistributorContract).checkpoint_token(); + IveFeeDistributor(veFeeDistributorContract).checkpoint_total_supply(); } function commit_admin(address admin) external onlyOwner{ - veFeeDistributorContract.commit_admin(admin); + IveFeeDistributor(veFeeDistributorContract).commit_admin(admin); } function apply_admin() external onlyOwner{ - veFeeDistributorContract.apply_admin(); + IveFeeDistributor(veFeeDistributorContract).apply_admin(); } function toggle_allow_checkpoint_token() external onlyOwner{ - veFeeDistributorContract.toggle_allow_checkpoint_token(); + IveFeeDistributor(veFeeDistributorContract).toggle_allow_checkpoint_token(); } function kill_me() external onlyOwner{ - veFeeDistributorContract.kill_me(); + IveFeeDistributor(veFeeDistributorContract).kill_me(); uint256 balance = IERC20(veFeeDistributorToken).balanceOf(address(this)); SafeERC20.safeTransfer(IERC20(veFeeDistributorToken), owner(), balance); } function recover_balance(address token) external onlyOwner{ - veFeeDistributorContract.recover_balance(token); + IveFeeDistributor(veFeeDistributorContract).recover_balance(token); uint256 balance = IERC20(token).balanceOf(address(this)); SafeERC20.safeTransfer(IERC20(token), owner(), balance); } diff --git a/test/unit/ve/veFeeOwner.test.js b/test/unit/ve/veFeeOwner.test.js new file mode 100644 index 00000000..51d7ac20 --- /dev/null +++ b/test/unit/ve/veFeeOwner.test.js @@ -0,0 +1,141 @@ +/* eslint-env mocha */ +/* global artifacts, contract, web3, it, beforeEach */ +const hre = require("hardhat"); +const { assert, expect } = require("chai"); +const { expectRevert, expectEvent, BN } = require("@openzeppelin/test-helpers"); +const { impersonate } = require("../../helpers/impersonate"); +const constants = require("../../helpers/constants"); +const { web3 } = require("@openzeppelin/test-helpers/src/setup"); +const { sha256 } = require("@ethersproject/sha2"); +const {getEventFromTx} = require("../../helpers/utils"); +const { ZERO_ADDRESS } = require("@openzeppelin/test-helpers/src/constants"); +const ethers = hre.ethers; +const { ecsign } = require("ethereumjs-util"); + + +describe("veFeeOwner tests", () => { + let veFeeDistributor, + veFeeDistributorOwner, + veOcean, + oceanToken, + mockERC20Token, + owner, + alice, + bob + + it("#deploy veOcean, veFeeDistributor, veFeeOwner & Ocean", async () => { + [owner, alice,bob] = await ethers.getSigners(); + const OceanToken = await ethers.getContractFactory('MockOcean'); + oceanToken = await OceanToken.connect(owner).deploy(owner.address); + const MockERC20Token = await ethers.getContractFactory('MockERC20'); + mockERC20Token = await MockERC20Token.connect(owner).deploy(owner.address,'Mock','Mock'); + const VeOcean = await ethers.getContractFactory("veOCEAN"); + veOcean = await VeOcean.connect(owner).deploy(oceanToken.address,"veOCEAN", "veOCEAN", "0.1.0"); + const VeFeeDistributor = await ethers.getContractFactory("veFeeDistributor"); + const timestamp = Math.floor(new Date().getTime() / 1000) + veFeeDistributor = await VeFeeDistributor.connect(owner).deploy(veOcean.address, + 1, + oceanToken.address, + owner.address, + owner.address); + await veFeeDistributor.deployed() + const VeFeeDistributorOwner = await ethers.getContractFactory("veFeeDistributorOwner"); + veFeeDistributorOwner = await VeFeeDistributorOwner.connect(owner).deploy(veFeeDistributor.address); + await veFeeDistributorOwner.deployed() + await veFeeDistributor.connect(owner).toggle_allow_checkpoint_token() + assert(await veFeeDistributor.can_checkpoint_token()===true) + await oceanToken.transfer(alice.address,1000) + let tx = await oceanToken.transfer(bob.address,1000) + await tx.wait() + const aliceBalance = await oceanToken.balanceOf(alice.address) + assert(String(aliceBalance)==='1000', 'Alice balance is off. Expecting 1000, got '+aliceBalance) + tx = await veFeeDistributor.connect(owner).commit_admin(veFeeDistributorOwner.address) + await tx.wait() + tx = await veFeeDistributor.connect(owner).apply_admin() + await tx.wait() + assert(await veFeeDistributor.admin()==veFeeDistributorOwner.address, 'veFeeDistributor ownership change failed') + const token = await veFeeDistributorOwner.veFeeDistributorToken() + assert(token==oceanToken.address, ' veFeeDistributor token missmatch. Expecting '+oceanToken.address+', got '+token) + const veDistContract = await veFeeDistributorOwner.veFeeDistributorContract() + assert(veDistContract==veFeeDistributor.address, ' veFeeDistributor contract missmatch. Expecting '+veFeeDistributor.address+', got '+token) + const ownerCHeck = await veFeeDistributorOwner.owner() + assert(ownerCHeck===owner.address,"veFeeDistributor owner is wrong") + + + }) + it("#Alice locks OceanTokens", async () => { + //alice locks 1000 ocean + const unlockTime = Math.floor(new Date().getTime() / 1000) + 60*60*24*30 + await oceanToken.connect(alice).approve(veOcean.address,1000) + const tx = await veOcean.connect(alice).create_lock(1000,unlockTime) + const txReceipt = await tx.wait(); + + }); + it("#Owner sends 1000 Ocean to feeDistributor", async () => { + const tx = await oceanToken.connect(owner).transfer(veFeeDistributor.address,1000) + await tx.wait() + const veFeeDistributorBalance = await oceanToken.balanceOf(veFeeDistributor.address) + assert(String(veFeeDistributorBalance)==='1000', 'veFeeDistributor balance is off. Expecting 1000, got '+veFeeDistributorBalance) + }); + it("#Anyone can checkpoint veFeeDistributor", async () => { + let tx = await veFeeDistributorOwner.connect(alice).checkpoint_token() + let txReceipt = await tx.wait(); + let event = getEventFromTx(txReceipt,'CheckpointToken') + assert(event, "Cannot find CheckpointToken event") + tx = await veFeeDistributorOwner.connect(alice).checkpoint_total_supply() + txReceipt = await tx.wait(); + tx = await veFeeDistributorOwner.connect(alice).checkpoint() + txReceipt = await tx.wait(); + event = getEventFromTx(txReceipt,'CheckpointToken') + assert(event, "Cannot find CheckpointToken event") + }) + it("#Alice should fail to call veFeeDistributor checkpoint", async () => { + await expectRevert.unspecified( + veFeeDistributor + .connect(alice) + .checkpoint_token() + ); + }) + it("#Alice should fail to call veFeeDistributorOwner kill_me", async () => { + await expectRevert.unspecified( + veFeeDistributorOwner + .connect(alice) + .kill_me() + ); + await expectRevert.unspecified( + veFeeDistributorOwner + .connect(alice) + .recover_balance(oceanToken.address) + ); + await expectRevert.unspecified( + veFeeDistributorOwner + .connect(alice) + .commit_admin(alice.address) + ); + await expectRevert.unspecified( + veFeeDistributorOwner + .connect(alice) + .apply_admin() + ); + }) + it("#Owner should be able to call veFeeDistributorOwner recover_balance", async () => { + await mockERC20Token.connect(owner).transfer(veFeeDistributor.address,100) + let veFeeDistributorBalance = await mockERC20Token.balanceOf(veFeeDistributor.address) + assert(String(veFeeDistributorBalance)==='100', 'veFeeDistributor balance is off. Expecting 100, got '+veFeeDistributorBalance) + await + veFeeDistributorOwner + .connect(owner) + .recover_balance(mockERC20Token.address) + veFeeDistributorBalance = await mockERC20Token.balanceOf(veFeeDistributor.address) + assert(String(veFeeDistributorBalance)==='0', 'veFeeDistributor balance is off. Expecting 0, got '+veFeeDistributorBalance) + + }) + it("#Owner should be able to tranfer veFeeDistributor ownership to Alice", async () => { + assert(await veFeeDistributor.admin()!=alice.address,' Alice cannot be admin yet') + await veFeeDistributorOwner.connect(owner).commit_admin(alice.address) + const tx = await veFeeDistributorOwner.connect(owner).apply_admin() + await tx.wait() + assert(await veFeeDistributor.admin()===alice.address,' Alice should be the new admin') + }) + +}); \ No newline at end of file