Skip to content

Commit

Permalink
feat: percentage based fee handler (#194)
Browse files Browse the repository at this point in the history
  • Loading branch information
nmlinaric authored Sep 1, 2023
1 parent fb37549 commit 26dc82a
Show file tree
Hide file tree
Showing 13 changed files with 1,178 additions and 5 deletions.
6 changes: 2 additions & 4 deletions contracts/handlers/fee/BasicFeeHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ contract BasicFeeHandler is IFeeHandler, AccessControl {
@param depositData Additional data to be passed to specified handler.
@param feeData Additional data to be passed to the fee handler.
*/
function collectFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) payable external onlyBridgeOrRouter {
function collectFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) virtual payable external onlyBridgeOrRouter {
if (msg.value != _fee) revert IncorrectFeeSupplied(msg.value);
emit FeeCollected(sender, fromDomainID, destinationDomainID, resourceID, _fee, address(0));
}
Expand All @@ -85,7 +85,7 @@ contract BasicFeeHandler is IFeeHandler, AccessControl {
@param feeData Additional data to be passed to the fee handler.
@return Returns the fee amount.
*/
function calculateFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) external view returns(uint256, address) {
function calculateFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) virtual external view returns(uint256, address) {
return (_fee, address(0));
}

Expand Down Expand Up @@ -114,6 +114,4 @@ contract BasicFeeHandler is IFeeHandler, AccessControl {
emit FeeDistributed(address(0), addrs[i], amounts[i]);
}
}


}
133 changes: 133 additions & 0 deletions contracts/handlers/fee/PercentageERC20FeeHandlerEVM.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// The Licensed Work is (c) 2022 Sygma
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.11;

import "../../interfaces/IBridge.sol";
import "../../interfaces/IERCHandler.sol";
import "../../ERC20Safe.sol";
import { BasicFeeHandler } from "./BasicFeeHandler.sol";

/**
@title Handles deposit fees.
@author ChainSafe Systems.
@notice This contract is intended to be used with the Bridge contract.
*/
contract PercentageERC20FeeHandlerEVM is BasicFeeHandler, ERC20Safe {
uint32 public constant HUNDRED_PERCENT = 1e8;

/**
@notice _fee inherited from BasicFeeHandler in this implementation is
in BPS and should be multiplied by 10000 to avoid precision loss
*/
struct Bounds {
uint128 lowerBound; // min fee in token amount
uint128 upperBound; // max fee in token amount
}

mapping(bytes32 => Bounds) public _resourceIDToFeeBounds;

event FeeBoundsChanged(uint256 newLowerBound, uint256 newUpperBound);

/**
@param bridgeAddress Contract address of previously deployed Bridge.
@param feeHandlerRouterAddress Contract address of previously deployed FeeHandlerRouter.
*/
constructor(
address bridgeAddress,
address feeHandlerRouterAddress
) BasicFeeHandler(bridgeAddress, feeHandlerRouterAddress) {}

// Admin functions

/**
@notice Calculates fee for deposit.
@param sender Sender of the deposit.
@param fromDomainID ID of the source chain.
@param destinationDomainID ID of chain deposit will be bridged to.
@param resourceID ResourceID to be used when making deposits.
@param depositData Additional data about the deposit.
@param feeData Additional data to be passed to the fee handler.
@return fee Returns the fee amount.
@return tokenAddress Returns the address of the token to be used for fee.
*/
function calculateFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) external view override returns(uint256 fee, address tokenAddress) {
return _calculateFee(sender, fromDomainID, destinationDomainID, resourceID, depositData, feeData);
}

function _calculateFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) internal view returns(uint256 fee, address tokenAddress) {
address tokenHandler = IBridge(_bridgeAddress)._resourceIDToHandlerAddress(resourceID);
tokenAddress = IERCHandler(tokenHandler)._resourceIDToTokenContractAddress(resourceID);
Bounds memory bounds = _resourceIDToFeeBounds[resourceID];

(uint256 depositAmount) = abi.decode(depositData, (uint256));

fee = depositAmount * _fee / HUNDRED_PERCENT; // 10000 for BPS and 10000 to avoid precision loss

if (fee < bounds.lowerBound) {
fee = bounds.lowerBound;
}

// if upper bound is not set, fee is % of token amount
else if (fee > bounds.upperBound && bounds.upperBound > 0) {
fee = bounds.upperBound;
}

return (fee, tokenAddress);
}

/**
@notice Collects fee for deposit.
@param sender Sender of the deposit.
@param fromDomainID ID of the source chain.
@param destinationDomainID ID of chain deposit will be bridged to.
@param resourceID ResourceID to be used when making deposits.
@param depositData Additional data about the deposit.
@param feeData Additional data to be passed to the fee handler.
*/
function collectFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) payable external override onlyBridgeOrRouter {
require(msg.value == 0, "collectFee: msg.value != 0");

(uint256 fee, address tokenAddress) = _calculateFee(sender, fromDomainID, destinationDomainID, resourceID, depositData, feeData);
lockERC20(tokenAddress, sender, address(this), fee);

emit FeeCollected(sender, fromDomainID, destinationDomainID, resourceID, fee, tokenAddress);
}

/**
@notice Sets new value for lower and upper fee bounds, both are in token amount.
@notice Only callable by admin.
@param resourceID ResourceID for which new fee bounds will be set.
@param newLowerBound Value {_newLowerBound} will be updated to.
@param newUpperBound Value {_newUpperBound} will be updated to.
*/
function changeFeeBounds(bytes32 resourceID, uint128 newLowerBound, uint128 newUpperBound) external onlyAdmin {
require(newUpperBound == 0 || (newUpperBound > newLowerBound), "Upper bound must be larger than lower bound or 0");
Bounds memory existingBounds = _resourceIDToFeeBounds[resourceID];
require(existingBounds.lowerBound != newLowerBound ||
existingBounds.upperBound != newUpperBound,
"Current bounds are equal to new bounds"
);

Bounds memory newBounds = Bounds(newLowerBound, newUpperBound);
_resourceIDToFeeBounds[resourceID] = newBounds;

emit FeeBoundsChanged(newLowerBound, newUpperBound);
}

/**
@notice Transfers tokens from the contract to the specified addresses. The parameters addrs and amounts are mapped 1-1.
This means that the address at index 0 for addrs will receive the amount of tokens from amounts at index 0.
@param resourceID ResourceID of the token.
@param addrs Array of addresses to transfer {amounts} to.
@param amounts Array of amounts to transfer to {addrs}.
*/
function transferERC20Fee(bytes32 resourceID, address[] calldata addrs, uint[] calldata amounts) external onlyAdmin {
require(addrs.length == amounts.length, "addrs[], amounts[]: diff length");
address tokenHandler = IBridge(_bridgeAddress)._resourceIDToHandlerAddress(resourceID);
address tokenAddress = IERCHandler(tokenHandler)._resourceIDToTokenContractAddress(resourceID);
for (uint256 i = 0; i < addrs.length; i++) {
releaseERC20(tokenAddress, addrs[i], amounts[i]);
emit FeeDistributed(tokenAddress, addrs[i], amounts[i]);
}
}
}
18 changes: 18 additions & 0 deletions migrations/2_deploy_contracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const PermissionedGenericHandlerContract = artifacts.require(
const FeeRouterContract = artifacts.require("FeeHandlerRouter");
const BasicFeeHandlerContract = artifacts.require("BasicFeeHandler");
const DynamicFeeHandlerContract = artifacts.require("DynamicERC20FeeHandlerEVM");
const PercentageFeeHandler = artifacts.require("PercentageERC20FeeHandlerEVM");

module.exports = async function (deployer, network) {
const networksConfig = Utils.getNetworksConfig();
Expand Down Expand Up @@ -76,6 +77,11 @@ module.exports = async function (deployer, network) {
bridgeInstance.address,
feeRouterInstance.address
);
const percentageFeeHandlerInstance = await deployer.deploy(
PercentageFeeHandler,
bridgeInstance.address,
feeRouterInstance.address
)

// setup fee router and fee handlers
await bridgeInstance.adminChangeFeeHandler(feeRouterInstance.address);
Expand All @@ -89,6 +95,9 @@ module.exports = async function (deployer, network) {
await basicFeeHandlerInstance.changeFee(
Ethers.utils.parseEther(currentNetworkConfig.fee.basic.fee).toString()
);
await percentageFeeHandlerInstance.changeFee(
currentNetworkConfig.fee.percentage.fee
)

console.table({
"Deployer Address": deployerAddress,
Expand All @@ -101,6 +110,7 @@ module.exports = async function (deployer, network) {
"FeeRouterContract Address": feeRouterInstance.address,
"BasicFeeHandler Address": basicFeeHandlerInstance.address,
"DynamicFeeHandler Address": dynamicFeeHandlerInstance.address,
"PercentageFeeHandler Address": percentageFeeHandlerInstance.address
});

// setup erc20 tokens
Expand All @@ -116,8 +126,14 @@ module.exports = async function (deployer, network) {
feeRouterInstance,
dynamicFeeHandlerInstance,
basicFeeHandlerInstance,
percentageFeeHandlerInstance,
erc20
);
await percentageFeeHandlerInstance.changeFeeBounds(
erc20.resourceID,
Ethers.utils.parseEther(currentNetworkConfig.fee.percentage.lowerBound).toString(),
Ethers.utils.parseEther(currentNetworkConfig.fee.percentage.upperBound).toString()
)

console.log(
"-------------------------------------------------------------------------------"
Expand All @@ -143,6 +159,7 @@ module.exports = async function (deployer, network) {
feeRouterInstance,
dynamicFeeHandlerInstance,
basicFeeHandlerInstance,
percentageFeeHandlerInstance,
erc721
);

Expand All @@ -168,6 +185,7 @@ module.exports = async function (deployer, network) {
feeRouterInstance,
dynamicFeeHandlerInstance,
basicFeeHandlerInstance,
percentageFeeHandlerInstance,
generic
);

Expand Down
3 changes: 3 additions & 0 deletions migrations/3_deploy_permissionlessGenericHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const PermissionlessGenericHandlerContract = artifacts.require(
const FeeRouterContract = artifacts.require("FeeHandlerRouter");
const BasicFeeHandlerContract = artifacts.require("BasicFeeHandler");
const DynamicFeeHandlerContract = artifacts.require("DynamicERC20FeeHandlerEVM");
const PercentageFeeHandler = artifacts.require("PercentageERC20FeeHandlerEVM");

module.exports = async function (deployer, network) {
const networksConfig = Utils.getNetworksConfig();
Expand All @@ -23,6 +24,7 @@ module.exports = async function (deployer, network) {
const bridgeInstance = await BridgeContract.deployed();
const feeRouterInstance = await FeeRouterContract.deployed();
const basicFeeHandlerInstance = await BasicFeeHandlerContract.deployed();
const percentageFeeHandlerInstance = await PercentageFeeHandler.deployed();
const dynamicFeeHandlerInstance =
await DynamicFeeHandlerContract.deployed();

Expand Down Expand Up @@ -71,6 +73,7 @@ module.exports = async function (deployer, network) {
feeRouterInstance,
dynamicFeeHandlerInstance,
basicFeeHandlerInstance,
percentageFeeHandlerInstance,
currentNetworkConfig.permissionlessGeneric
);
}
Expand Down
4 changes: 4 additions & 0 deletions migrations/4_deploy_xc20_contracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const XC20HandlerContract = artifacts.require("XC20Handler");
const FeeRouterContract = artifacts.require("FeeHandlerRouter");
const BasicFeeHandlerContract = artifacts.require("BasicFeeHandler");
const DynamicFeeHandlerContract = artifacts.require("DynamicERC20FeeHandlerEVM");
const PercentageFeeHandler = artifacts.require("PercentageERC20FeeHandlerEVM");


module.exports = async function (deployer, network) {
// trim suffix from network name and fetch current network config
Expand All @@ -29,6 +31,7 @@ module.exports = async function (deployer, network) {
const basicFeeHandlerInstance = await BasicFeeHandlerContract.deployed();
const dynamicFeeHandlerInstance =
await DynamicFeeHandlerContract.deployed();
const percentageFeeHandlerInstance = await PercentageFeeHandler.deployed();

// deploy XC20 contracts
await deployer.deploy(XC20HandlerContract, bridgeInstance.address);
Expand All @@ -42,6 +45,7 @@ module.exports = async function (deployer, network) {
feeRouterInstance,
dynamicFeeHandlerInstance,
basicFeeHandlerInstance,
percentageFeeHandlerInstance,
xc20
);

Expand Down
5 changes: 5 additions & 0 deletions migrations/local.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
},
"basic": {
"fee": "0.001"
},
"percentage":{
"fee": "10000",
"lowerBound": "10",
"upperBound": "30"
}
},
"access": {
Expand Down
9 changes: 8 additions & 1 deletion migrations/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ async function setupFee(
feeRouterInstance,
dynamicFeeHandlerInstance,
basicFeeHandlerInstance,
percentageFeeHandlerInstance,
token
) {
for await (const network of Object.values(networksConfig)) {
Expand All @@ -40,12 +41,18 @@ async function setupFee(
token.resourceID,
dynamicFeeHandlerInstance.address
);
} else {
} else if (token.feeType == "basic") {
await feeRouterInstance.adminSetResourceHandler(
network.domainID,
token.resourceID,
basicFeeHandlerInstance.address
);
} else {
await feeRouterInstance.adminSetResourceHandler(
network.domainID,
token.resourceID,
percentageFeeHandlerInstance.address
)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"build/contracts/PermissionlessGenericHandler.json",
"build/contracts/BasicFeeHandler.json",
"build/contracts/DynamicERC20FeeHandlerEVM.json",
"build/contracts/PercentageFeeHandlerEVM.json",
"contracts/interfaces"
],
"directories": {
Expand Down
Loading

0 comments on commit 26dc82a

Please sign in to comment.