Skip to content

Commit

Permalink
feat: Implement fee whitelist (#203)
Browse files Browse the repository at this point in the history
  • Loading branch information
mpetrun5 authored Oct 2, 2023
1 parent 0cc78cf commit 4463bcb
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 11 deletions.
29 changes: 27 additions & 2 deletions contracts/handlers/FeeHandlerRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ contract FeeHandlerRouter is IFeeHandler, AccessControl {

// destination domainID => resourceID => feeHandlerAddress
mapping (uint8 => mapping(bytes32 => IFeeHandler)) public _domainResourceIDToFeeHandlerAddress;
// whitelisted address => is whitelisted
mapping(address => bool) public _whitelist;

event FeeChanged(
uint256 newFee
event WhitelistChanged(
address whitelistAddress,
bool isWhitelisted
);

error IncorrectFeeSupplied(uint256);

modifier onlyBridge() {
_onlyBridge();
_;
Expand Down Expand Up @@ -55,6 +60,17 @@ contract FeeHandlerRouter is IFeeHandler, AccessControl {
_domainResourceIDToFeeHandlerAddress[destinationDomainID][resourceID] = handlerAddress;
}

/**
@notice Sets or revokes fee whitelist from an address.
@param whitelistAddress Address to be whitelisted.
@param isWhitelisted Set to true to exempt an address from paying fees.
*/
function adminSetWhitelist(address whitelistAddress, bool isWhitelisted) external onlyAdmin {
_whitelist[whitelistAddress] = isWhitelisted;

emit WhitelistChanged(whitelistAddress, isWhitelisted);
}


/**
@notice Initiates collecting fee with corresponding fee handler contract using IFeeHandler interface.
Expand All @@ -66,6 +82,11 @@ contract FeeHandlerRouter is IFeeHandler, AccessControl {
@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 onlyBridge {
if (_whitelist[sender]) {
if (msg.value != 0) revert IncorrectFeeSupplied(msg.value);
return;
}

IFeeHandler feeHandler = _domainResourceIDToFeeHandlerAddress[destinationDomainID][resourceID];
feeHandler.collectFee{value: msg.value}(sender, fromDomainID, destinationDomainID, resourceID, depositData, feeData);
}
Expand All @@ -82,6 +103,10 @@ contract FeeHandlerRouter is IFeeHandler, AccessControl {
@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 returns(uint256 fee, address tokenAddress) {
if (_whitelist[sender]) {
return (0, address(0));
}

IFeeHandler feeHandler = _domainResourceIDToFeeHandlerAddress[destinationDomainID][resourceID];
return feeHandler.calculateFee(sender, fromDomainID, destinationDomainID, resourceID, depositData, feeData);
}
Expand Down
Empty file removed test/feeRouter/feeRouter.js
Empty file.
161 changes: 152 additions & 9 deletions test/handlers/fee/handlerRouter.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
// The Licensed Work is (c) 2022 Sygma
// SPDX-License-Identifier: LGPL-3.0-only

const Ethers = require("ethers");

const TruffleAssert = require("truffle-assertions");

const Helpers = require("../../helpers");

const DynamicFeeHandlerContract = artifacts.require("DynamicERC20FeeHandlerEVM");
const BasicFeeHandlerContract = artifacts.require("BasicFeeHandler");
const FeeHandlerRouterContract = artifacts.require("FeeHandlerRouter");
const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser");

contract("FeeHandlerRouter", async (accounts) => {
const originDomainID = 1;
const destinationDomainID = 2;
const feeData = "0x0";
const nonAdmin = accounts[1];
const whitelistAddress = accounts[2];
const nonWhitelistAddress = accounts[3];
const recipientAddress = accounts[3];
const bridgeAddress = accounts[4];

const assertOnlyAdmin = (method, ...params) => {
return TruffleAssert.reverts(
Expand All @@ -21,27 +28,23 @@ contract("FeeHandlerRouter", async (accounts) => {
);
};

let BridgeInstance;
let FeeHandlerRouterInstance;
let BasicFeeHandlerInstance;
let ERC20MintableInstance;
let resourceID;

beforeEach(async () => {
await Promise.all([
(BridgeInstance = await Helpers.deployBridge(
destinationDomainID,
accounts[0]
)),
ERC20MintableContract.new("token", "TOK").then(
(instance) => (ERC20MintableInstance = instance)
),
]);

FeeHandlerRouterInstance = await FeeHandlerRouterContract.new(
BridgeInstance.address
bridgeAddress
);
DynamicFeeHandlerInstance = await DynamicFeeHandlerContract.new(
BridgeInstance.address,
BasicFeeHandlerInstance = await BasicFeeHandlerContract.new(
bridgeAddress,
FeeHandlerRouterInstance.address
);

Expand Down Expand Up @@ -82,4 +85,144 @@ contract("FeeHandlerRouter", async (accounts) => {
feeHandlerAddress
);
});

it("should successfully set whitelist on an address", async () => {
assert.equal(
await FeeHandlerRouterInstance._whitelist.call(
whitelistAddress
),
false
);

const whitelistTx = await FeeHandlerRouterInstance.adminSetWhitelist(
whitelistAddress,
true
);
assert.equal(
await FeeHandlerRouterInstance._whitelist.call(
whitelistAddress
),
true
);
TruffleAssert.eventEmitted(whitelistTx, "WhitelistChanged", (event) => {
return (
event.whitelistAddress === whitelistAddress &&
event.isWhitelisted === true
);
});
});

it("should require admin role to set whitelist address", async () => {
await assertOnlyAdmin(
FeeHandlerRouterInstance.adminSetWhitelist,
whitelistAddress,
true
);
});

it("should return fee 0 if address whitelisted", async () => {
await FeeHandlerRouterInstance.adminSetWhitelist(
whitelistAddress,
true
);
await FeeHandlerRouterInstance.adminSetResourceHandler(
destinationDomainID,
resourceID,
BasicFeeHandlerInstance.address
);
await BasicFeeHandlerInstance.changeFee(Ethers.utils.parseEther("0.5"));

const depositData = Helpers.createERCDepositData(100, 20, recipientAddress);
let res = await FeeHandlerRouterInstance.calculateFee.call(
whitelistAddress,
originDomainID,
destinationDomainID,
resourceID,
depositData,
feeData
);
assert.equal(web3.utils.fromWei(res[0], "ether"), "0")
res = await FeeHandlerRouterInstance.calculateFee.call(
nonWhitelistAddress,
originDomainID,
destinationDomainID,
resourceID,
depositData,
feeData
);
assert.equal(web3.utils.fromWei(res[0], "ether"), "0.5")
});

it("should revert if whitelisted address provides fee", async () => {
await FeeHandlerRouterInstance.adminSetWhitelist(
whitelistAddress,
true
);
await FeeHandlerRouterInstance.adminSetResourceHandler(
destinationDomainID,
resourceID,
BasicFeeHandlerInstance.address
);
await BasicFeeHandlerInstance.changeFee(Ethers.utils.parseEther("0.5"));

const depositData = Helpers.createERCDepositData(100, 20, recipientAddress);
await Helpers.expectToRevertWithCustomError(
FeeHandlerRouterInstance.collectFee(
whitelistAddress,
originDomainID,
destinationDomainID,
resourceID,
depositData,
feeData,
{
from: bridgeAddress,
value: Ethers.utils.parseEther("0.5").toString()
}
),
"IncorrectFeeSupplied(uint256)"
);
await TruffleAssert.passes(
FeeHandlerRouterInstance.collectFee(
nonWhitelistAddress,
originDomainID,
destinationDomainID,
resourceID,
depositData,
feeData,
{
from: bridgeAddress,
value: Ethers.utils.parseEther("0.5").toString()
}
),
);
});

it("should not collect fee from whitelisted address", async () => {
await FeeHandlerRouterInstance.adminSetWhitelist(
whitelistAddress,
true
);
await FeeHandlerRouterInstance.adminSetResourceHandler(
destinationDomainID,
resourceID,
BasicFeeHandlerInstance.address
);
await BasicFeeHandlerInstance.changeFee(Ethers.utils.parseEther("0.5"));

const depositData = Helpers.createERCDepositData(100, 20, recipientAddress);
await TruffleAssert.passes(
FeeHandlerRouterInstance.collectFee(
whitelistAddress,
originDomainID,
destinationDomainID,
resourceID,
depositData,
feeData,
{
from: bridgeAddress,
value: "0"
}
),
);
});
});

0 comments on commit 4463bcb

Please sign in to comment.