Skip to content

Commit

Permalink
burn network fees
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanzhelyazkov committed Oct 19, 2023
1 parent 3b566da commit bb50fd7
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 76 deletions.
76 changes: 47 additions & 29 deletions contracts/network/BancorNetwork.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,6 @@ contract BancorNetwork is IBancorNetwork, Upgradeable, ReentrancyGuardUpgradeabl
// the emergency manager role is required to pause/unpause the network
bytes32 private constant ROLE_EMERGENCY_STOPPER = keccak256("ROLE_EMERGENCY_STOPPER");

// the network fee manager role is required to pull the accumulated pending network fees
bytes32 private constant ROLE_NETWORK_FEE_MANAGER = keccak256("ROLE_NETWORK_FEE_MANAGER");

// the address of the BNT token
IERC20 private immutable _bnt;

Expand Down Expand Up @@ -149,15 +146,18 @@ contract BancorNetwork is IBancorNetwork, Upgradeable, ReentrancyGuardUpgradeabl
// a mapping between pools and their respective pool collections
mapping(Token => IPoolCollection) private _collectionByPool;

// the pending network fee amount to be burned by the vortex
// the pending network fee amount to be burned
uint256 internal _pendingNetworkFeeAmount;

bool private _depositingEnabled = true;

uint32 private _polRewardsPPM;

// min network fee amount that can be burned
uint256 private _minNetworkFeeBurn;

// upgrade forward-compatibility storage gap
uint256[MAX_GAP - 11] private __gap;
uint256[MAX_GAP - 12] private __gap;

/**
* @dev triggered when a new pool collection is added
Expand Down Expand Up @@ -217,9 +217,9 @@ contract BancorNetwork is IBancorNetwork, Upgradeable, ReentrancyGuardUpgradeabl
event FlashLoanCompleted(Token indexed token, address indexed borrower, uint256 amount, uint256 feeAmount);

/**
* @dev triggered when network fees are withdrawn
* @dev triggered when network fees are burned
*/
event NetworkFeesWithdrawn(address indexed caller, address indexed recipient, uint256 amount);
event NetworkFeesBurned(address indexed caller, uint256 amount);

/**
* @dev triggered when pool surplus tokens are withdrawn
Expand All @@ -231,6 +231,11 @@ contract BancorNetwork is IBancorNetwork, Upgradeable, ReentrancyGuardUpgradeabl
*/
event POLRewardsPPMUpdated(uint32 oldRewardsPPM, uint32 newRewardsPPM);

/**
* @dev triggered when the min network fee burn is updated
*/
event MinNetworkFeeBurnUpdated(uint256 oldMinNetworkFeeBurn, uint256 newMinNetworkFeeBurn);

/**
* @dev a "virtual" constructor that is only used to set immutable state variables
*/
Expand Down Expand Up @@ -315,11 +320,11 @@ contract BancorNetwork is IBancorNetwork, Upgradeable, ReentrancyGuardUpgradeabl
// set up administrative roles
_setRoleAdmin(ROLE_MIGRATION_MANAGER, ROLE_ADMIN);
_setRoleAdmin(ROLE_EMERGENCY_STOPPER, ROLE_ADMIN);
_setRoleAdmin(ROLE_NETWORK_FEE_MANAGER, ROLE_ADMIN);

_depositingEnabled = true;

_setPOLRewardsPPM(2000);
_setMinNetworkFeeBurn(1_000_000e18);
}

// solhint-enable func-name-mixedcase
Expand Down Expand Up @@ -360,14 +365,7 @@ contract BancorNetwork is IBancorNetwork, Upgradeable, ReentrancyGuardUpgradeabl
}

/**
* @dev returns the network fee manager role
*/
function roleNetworkFeeManager() external pure returns (bytes32) {
return ROLE_NETWORK_FEE_MANAGER;
}

/**
* @dev returns the pending network fee amount to be burned by the vortex
* @dev returns the pending network fee amount to be burned
*/
function pendingNetworkFeeAmount() external view returns (uint256) {
return _pendingNetworkFeeAmount;
Expand Down Expand Up @@ -820,26 +818,17 @@ contract BancorNetwork is IBancorNetwork, Upgradeable, ReentrancyGuardUpgradeabl
/**
* @inheritdoc IBancorNetwork
*/
function withdrawNetworkFees(
address recipient
)
external
whenNotPaused
onlyRoleMember(ROLE_NETWORK_FEE_MANAGER)
validAddress(recipient)
nonReentrant
returns (uint256)
{
function burnNetworkFees() external whenNotPaused nonReentrant returns (uint256) {
uint256 currentPendingNetworkFeeAmount = _pendingNetworkFeeAmount;
if (currentPendingNetworkFeeAmount == 0) {
if (currentPendingNetworkFeeAmount < _minNetworkFeeBurn) {
return 0;
}

_pendingNetworkFeeAmount = 0;

_masterVault.withdrawFunds(Token(address(_bnt)), payable(recipient), currentPendingNetworkFeeAmount);
_masterVault.withdrawFunds(Token(address(_bnt)), payable(address(_bnt)), currentPendingNetworkFeeAmount);

emit NetworkFeesWithdrawn(msg.sender, recipient, currentPendingNetworkFeeAmount);
emit NetworkFeesBurned(msg.sender, currentPendingNetworkFeeAmount);

return currentPendingNetworkFeeAmount;
}
Expand Down Expand Up @@ -915,6 +904,35 @@ contract BancorNetwork is IBancorNetwork, Upgradeable, ReentrancyGuardUpgradeabl
emit POLRewardsPPMUpdated(oldRewardsPPM, newRewardsPPM);
}

/**
* @dev returns the min network fee burn
*/
function minNetworkFeeBurn() external view returns (uint256) {
return _minNetworkFeeBurn;
}

/**
* @dev set the min network fee burn
*/
function setMinNetworkFeeBurn(
uint256 newMinNetworkFeeBurn
) external onlyAdmin greaterThanZero(newMinNetworkFeeBurn) {
_setMinNetworkFeeBurn(newMinNetworkFeeBurn);
}

/**
* @dev set the min network fee burn
*/
function _setMinNetworkFeeBurn(uint256 newMinNetworkFeeBurn) private {
uint256 oldMinNetworkFeeBurn = _minNetworkFeeBurn;
if (oldMinNetworkFeeBurn == newMinNetworkFeeBurn) {
return;
}

_minNetworkFeeBurn = newMinNetworkFeeBurn;
emit MinNetworkFeeBurnUpdated(oldMinNetworkFeeBurn, newMinNetworkFeeBurn);
}

/**
* @dev generates context ID for a deposit request
*/
Expand Down
8 changes: 2 additions & 6 deletions contracts/network/interfaces/IBancorNetwork.sol
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,9 @@ interface IBancorNetwork is IUpgradeable {
) external payable;

/**
* @dev withdraws pending network fees, and returns the amount of fees withdrawn
*
* requirements:
*
* - the caller must have the ROLE_NETWORK_FEE_MANAGER privilege
* @dev burns pending network fees, and returns the amount of fees burned
*/
function withdrawNetworkFees(address recipient) external returns (uint256);
function burnNetworkFees() external returns (uint256);

/**
* @dev withdraws surplus tokens from a given pool to CarbonPOL contract,
Expand Down
1 change: 0 additions & 1 deletion deploy/tests/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ import { getNamedAccounts } from 'hardhat';
await expectRoleMembers(network, Roles.Upgradeable.ROLE_ADMIN, [daoMultisig.address]);
await expectRoleMembers(network, Roles.BancorNetwork.ROLE_MIGRATION_MANAGER, [liquidityProtection.address]);
await expectRoleMembers(network, Roles.BancorNetwork.ROLE_EMERGENCY_STOPPER);
await expectRoleMembers(network, Roles.BancorNetwork.ROLE_NETWORK_FEE_MANAGER, [daoMultisig.address]);

await expectRoleMembers(standardRewards, Roles.Upgradeable.ROLE_ADMIN, [daoMultisig.address]);

Expand Down
112 changes: 74 additions & 38 deletions test/network/BancorNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,6 @@ describe('BancorNetwork', () => {
await expectRole(network, Roles.Upgradeable.ROLE_ADMIN, Roles.Upgradeable.ROLE_ADMIN, [deployer.address]);
await expectRole(network, Roles.BancorNetwork.ROLE_MIGRATION_MANAGER, Roles.Upgradeable.ROLE_ADMIN);
await expectRole(network, Roles.BancorNetwork.ROLE_EMERGENCY_STOPPER, Roles.Upgradeable.ROLE_ADMIN);
await expectRole(network, Roles.BancorNetwork.ROLE_NETWORK_FEE_MANAGER, Roles.Upgradeable.ROLE_ADMIN);

expect(await network.paused()).to.be.false;
expect(await network.poolCollections()).to.be.empty;
Expand Down Expand Up @@ -3224,29 +3223,53 @@ describe('BancorNetwork', () => {
await network
.connect(deployer)
.grantRole(Roles.BancorNetwork.ROLE_EMERGENCY_STOPPER, emergencyStopper.address);
await network
.connect(deployer)
.grantRole(Roles.BancorNetwork.ROLE_NETWORK_FEE_MANAGER, networkFeeManager.address);
});

it('should revert when a non-network fee manager is attempting to withdraw the fees', async () => {
await expect(network.connect(deployer).pause()).to.be.revertedWithError('AccessDenied');
it('should revert when a non-admin is attempting to set the min burn amount', async () => {
await expect(network.connect(emergencyStopper).setMinNetworkFeeBurn(1)).to.be.revertedWithError(
'AccessDenied'
);
});

it('should revert when attempting to set min burn amount to an invalid amount', async () => {
await expect(network.setMinNetworkFeeBurn(0)).to.be.revertedWithError('ZeroValue');
});

it('admin should be able to set min network fee burn', async () => {
const newMinNetworkFeeBurn = toWei(1000);
await network.connect(deployer).setMinNetworkFeeBurn(newMinNetworkFeeBurn);
const minNetworkFeeBurn = await network.minNetworkFeeBurn();
expect(minNetworkFeeBurn).to.be.eq(newMinNetworkFeeBurn);
});

it('setting min network fee burn should emit an event', async () => {
const newMinNetworkFeeBurn = toWei(1000);
const oldNetworkFeeBurn = await network.minNetworkFeeBurn();
await expect(network.connect(deployer).setMinNetworkFeeBurn(newMinNetworkFeeBurn))
.to.emit(network, 'MinNetworkFeeBurnUpdated')
.withArgs(oldNetworkFeeBurn, newMinNetworkFeeBurn);
});

it('should ignore setting the min network fee burn to the same value', async () => {
const oldMinNetworkFeeBurn = await network.minNetworkFeeBurn();
await expect(network.connect(deployer).setMinNetworkFeeBurn(oldMinNetworkFeeBurn)).not.to.emit(
network,
'MinNetworkFeeBurnUpdated'
);
});

context('without any pending network fees', () => {
it('should not withdraw any pending network fees', async () => {
const prevBNTBalance = await bnt.balanceOf(networkFeeManager.address);
it('should not burn any pending network fees', async () => {
const burnedNetworkFees = await network.callStatic.burnNetworkFees();
expect(burnedNetworkFees).to.equal(0);

const withdrawNetworkFees = await network
.connect(networkFeeManager)
.callStatic.withdrawNetworkFees(networkFeeManager.address);
expect(withdrawNetworkFees).to.equal(0);
const totalSupplyBefore = await bnt.totalSupply();

const res = await network.connect(networkFeeManager).withdrawNetworkFees(networkFeeManager.address);
const res = await network.burnNetworkFees();

await expect(res).to.not.emit(network, 'NetworkFeesWithdrawn');
await expect(res).to.not.emit(network, 'NetworkFeesBurned');

expect(await bnt.balanceOf(networkFeeManager.address)).to.equal(prevBNTBalance);
expect(await bnt.totalSupply()).to.equal(totalSupplyBefore);
});
});

Expand All @@ -3255,37 +3278,52 @@ describe('BancorNetwork', () => {
await tradeBySourceAmount(deployer, bnt, token, toWei(1000), 1, MAX_UINT256, deployer.address, network);

expect(await network.pendingNetworkFeeAmount()).to.be.gt(0);
expect(await network.minNetworkFeeBurn()).to.be.eq(toWei(1_000_000));
});

it('should revert when the withdrawal caller is not a network-fee manager', async () => {
await expect(
network.connect(deployer).withdrawNetworkFees(networkFeeManager.address)
).to.be.revertedWithError('AccessDenied');
});
it('should not burn any pending network fees if the pending amount is below the min network fee burn amount', async () => {
const minNetworkFeeBurnAmount = await network.minNetworkFeeBurn();
expect(await network.pendingNetworkFeeAmount()).to.be.lt(minNetworkFeeBurnAmount);

it('should revert when the withdrawal recipient is invalid', async () => {
await expect(
network.connect(networkFeeManager).withdrawNetworkFees(ZERO_ADDRESS)
).to.be.revertedWithError('InvalidAddress');
const burnedNetworkFees = await network.callStatic.burnNetworkFees();
expect(burnedNetworkFees).to.equal(0);

const totalSupplyBefore = await bnt.totalSupply();

const res = await network.burnNetworkFees();

await expect(res).to.not.emit(network, 'NetworkFeesBurned');

expect(await bnt.totalSupply()).to.equal(totalSupplyBefore);
});

it('should withdraw all the pending network fees', async () => {
const recipient = nonOwner.address;
const prevBNTBalance = await bnt.balanceOf(networkFeeManager.address);
it('should burn all the pending network fees if the amount is above the min network fee burn amount', async () => {
await tradeBySourceAmount(
deployer,
bnt,
token,
toWei(10000000),
1,
MAX_UINT256,
deployer.address,
network
);
const minNetworkFeeBurnAmount = await network.minNetworkFeeBurn();
const pendingNetworkFeeAmount = await network.pendingNetworkFeeAmount();
expect(await network.pendingNetworkFeeAmount()).to.be.gt(minNetworkFeeBurnAmount);

const burnedNetworkFees = await network.callStatic.burnNetworkFees();
expect(burnedNetworkFees).to.equal(pendingNetworkFeeAmount);

const withdrawNetworkFees = await network
.connect(networkFeeManager)
.callStatic.withdrawNetworkFees(recipient);
expect(withdrawNetworkFees).to.equal(pendingNetworkFeeAmount);
const totalSupplyBefore = await bnt.totalSupply();

const res = await network.connect(networkFeeManager).withdrawNetworkFees(recipient);
const res = await network.burnNetworkFees();

await expect(res)
.to.emit(network, 'NetworkFeesWithdrawn')
.withArgs(networkFeeManager.address, recipient, pendingNetworkFeeAmount);
.to.emit(network, 'NetworkFeesBurned')
.withArgs(deployer.address, pendingNetworkFeeAmount);

expect(await bnt.balanceOf(recipient)).to.equal(prevBNTBalance.add(pendingNetworkFeeAmount));
expect(await bnt.totalSupply()).to.equal(totalSupplyBefore.sub(pendingNetworkFeeAmount));

expect(await network.pendingNetworkFeeAmount()).to.equal(0);
});
Expand All @@ -3296,9 +3334,7 @@ describe('BancorNetwork', () => {
});

it('should revert when attempting to withdraw the pending network fees', async () => {
await expect(
network.connect(networkFeeManager).withdrawNetworkFees(networkFeeManager.address)
).to.be.revertedWithError('Pausable: paused');
await expect(network.burnNetworkFees()).to.be.revertedWithError('Pausable: paused');
});
});
});
Expand Down
3 changes: 1 addition & 2 deletions utils/Roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ export const Roles = {

BancorNetwork: {
ROLE_MIGRATION_MANAGER: id('ROLE_MIGRATION_MANAGER'),
ROLE_EMERGENCY_STOPPER: id('ROLE_EMERGENCY_STOPPER'),
ROLE_NETWORK_FEE_MANAGER: id('ROLE_NETWORK_FEE_MANAGER')
ROLE_EMERGENCY_STOPPER: id('ROLE_EMERGENCY_STOPPER')
},

MasterVault: {
Expand Down

0 comments on commit bb50fd7

Please sign in to comment.