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

feat: Implement fee whitelist #203

Merged
merged 4 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
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"
}
),
);
});
});
Loading