Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SOV-2197 Recover incorrect fee allocation on feeSharingCollector #499

Merged
merged 8 commits into from
Jul 3, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions contracts/governance/FeeSharingCollector/FeeSharingCollector.sol
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ contract FeeSharingCollector is

event RBTCWithdrawn(address indexed sender, address indexed receiver, uint256 amount);

/* Modifier */
modifier oneTimeExecution(bytes4 _funcSig) {
require(
!isFunctionExecuted[_funcSig],
"FeeSharingCollector: function can only be called once"
);
_;
isFunctionExecuted[_funcSig] = true;
}

/* Functions */

/// @dev fallback function to support rbtc transfer when unwrap the wrbtc.
Expand Down Expand Up @@ -803,6 +813,42 @@ contract FeeSharingCollector is
IERC20(wRBTCAddress).safeTransfer(receiver, wrbtcAmount);
}

/**
* @dev This function is dedicated to recover the wrong fee allocation for the 4 year vesting contracts.
* This function can only be called once
* The affected tokens to be withdrawn
* 1. RBTC
* 2. ZUSD
* 3. SOV
* The amount for all of the tokens above is hardcoded
* The withdrawn tokens will be sent to the owner.
*/
function recoverIncorrectAllocatedFees()
external
oneTimeExecution(this.recoverIncorrectAllocatedFees.selector)
onlyOwner
{
uint256 rbtcAmount = 878778886164898400;
uint256 zusdAmount = 16658600400155126000000;
uint256 sovAmount = 6275898259771202000000;

address zusdToken = 0xdB107FA69E33f05180a4C2cE9c2E7CB481645C2d;
address sovToken = 0xEFc78fc7d48b64958315949279Ba181c2114ABBd;

// Withdraw rbtc
(bool success, ) = owner().call.value(rbtcAmount)("");
require(
success,
"FeeSharingCollector::recoverIncorrectAllocatedFees: Withdrawal rbtc failed"
);

// Withdraw ZUSD
IERC20(zusdToken).safeTransfer(owner(), zusdAmount);

// Withdraw SOV
IERC20(sovToken).safeTransfer(owner(), sovAmount);
}

/**
* @dev view function that calculate the total RBTC that includes:
* - RBTC
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ contract FeeSharingCollectorStorage is Ownable {
*/
EnumerableAddressSet.AddressSet internal whitelistedConverterList;

mapping(bytes4 => bool) public isFunctionExecuted;

/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* If you mark a function `nonReentrant`, you should also
Expand Down
74 changes: 73 additions & 1 deletion tests/FeeSharingCollectorTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@
// const { expect } = require("chai");
const { loadFixture, takeSnapshot } = require("@nomicfoundation/hardhat-network-helpers");
const { expectRevert, expectEvent, constants, BN } = require("@openzeppelin/test-helpers");
const { smock } = require("@defi-wonderland/smock");

const { ZERO_ADDRESS } = constants;

const { etherMantissa, mineBlock, increaseTime } = require("./Utils/Ethereum");
const { etherMantissa, mineBlock, increaseTime, etherGasCost } = require("./Utils/Ethereum");

const {
deployAndGetIStaking,
Expand Down Expand Up @@ -3294,6 +3295,77 @@ contract("FeeSharingCollector:", (accounts) => {
});
});

describe("recover incorrect allocated fees", async () => {
let mockSOV, mockZUSD;
let rbtcAmount = new BN(wei("878778886164898400", "wei"));

beforeEach(async () => {
mockSOV = await smock.fake("TestToken", {
address: "0xEFc78fc7d48b64958315949279Ba181c2114ABBd",
});

mockZUSD = await smock.fake("TestToken", {
address: "0xdB107FA69E33f05180a4C2cE9c2E7CB481645C2d",
});

mockSOV.transfer.returns(true);
mockZUSD.transfer.returns(true);

await web3.eth.sendTransaction({
from: accounts[2].toString(),
to: feeSharingCollector.address,
value: rbtcAmount,
gas: 50000,
});
});

it("recoverIncorrectAllocatedFees() can only be called by the owner", async () => {
await protocolDeploymentFixture();
await expectRevert(
feeSharingCollector.recoverIncorrectAllocatedFees({ from: accounts[1] }),
"unauthorized"
);
});

it("recoverIncorrectAllocatedFees() can only be executed once", async () => {
const owner = root;
await protocolDeploymentFixture();
await feeSharingCollector.recoverIncorrectAllocatedFees({ from: owner });
await expectRevert(
feeSharingCollector.recoverIncorrectAllocatedFees({ from: owner }),
"FeeSharingCollector: function can only be called once"
);
});

it("Should be able to withdraw the incorrect allocated fees properly", async () => {
await protocolDeploymentFixture();
const owner = await feeSharingCollector.owner();
const previousBalanceOwner = new BN(await web3.eth.getBalance(owner));
const tx = await feeSharingCollector.recoverIncorrectAllocatedFees();
const latestBalanceOwner = new BN(await web3.eth.getBalance(owner));
const txFee = new BN((await etherGasCost(tx.receipt)).toString());

expect(previousBalanceOwner.add(rbtcAmount).sub(txFee).toString()).to.be.equal(
latestBalanceOwner.toString()
);
});

it("Should revert if sov or zusd transfer failed", async () => {
await protocolDeploymentFixture();
mockSOV.transfer.returns(false);
await expectRevert(
feeSharingCollector.recoverIncorrectAllocatedFees(),
"SafeERC20: ERC20 operation did not succeed"
);
mockSOV.transfer.returns(true);
mockZUSD.transfer.returns(false);
await expectRevert(
feeSharingCollector.recoverIncorrectAllocatedFees(),
"SafeERC20: ERC20 operation did not succeed"
);
});
});

async function stake(amount, user, checkpointCount) {
await SOVToken.approve(staking.address, amount);
let kickoffTS = await staking.kickoffTS.call();
Expand Down