Skip to content
This repository was archived by the owner on May 9, 2024. It is now read-only.

Commit aa9614c

Browse files
authored
Add FeeHandlerRouter (#588)
* Make domainID immutable * Add FeeHandlerRouter contract * Update FeeHandlers with FeeRouter logic * Update old test with FeeHandlerRouter logic * Update old tests + add new tests for FeeHandlerRouter * Change resourceID-handler mapping type * Fix indentaton * Change onlyRouter modifier to onlyBridgeOrRouter * Remove redundant modifier * Update old tests + add new tests for onlyBridgeOrRouter modifier
1 parent d88949e commit aa9614c

File tree

15 files changed

+638
-170
lines changed

15 files changed

+638
-170
lines changed

contracts/Bridge.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import "./interfaces/IAccessControlSegregator.sol";
1818
contract Bridge is Pausable, Context {
1919
using ECDSA for bytes32;
2020

21-
2221
uint8 public immutable _domainID;
2322
address public _MPCAddress;
2423

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// SPDX-License-Identifier: LGPL-3.0-only
2+
pragma solidity 0.8.11;
3+
pragma experimental ABIEncoderV2;
4+
5+
import "../interfaces/IFeeHandler.sol";
6+
import "../utils/AccessControl.sol";
7+
8+
/**
9+
@title Handles FeeHandler routing for resources.
10+
@author ChainSafe Systems.
11+
@notice This contract is intended to be used with the Bridge contract.
12+
*/
13+
contract FeeHandlerRouter is IFeeHandler, AccessControl {
14+
address public immutable _bridgeAddress;
15+
16+
// destination domainID => resourceID => feeHandlerAddress
17+
mapping (uint8 => mapping(bytes32 => IFeeHandler)) public _domainResourceIDToFeeHandlerAddress;
18+
19+
event FeeChanged(
20+
uint256 newFee
21+
);
22+
23+
modifier onlyBridge() {
24+
_onlyBridge();
25+
_;
26+
}
27+
28+
function _onlyBridge() private view {
29+
require(msg.sender == _bridgeAddress, "sender must be bridge contract");
30+
}
31+
32+
modifier onlyAdmin() {
33+
_onlyAdmin();
34+
_;
35+
}
36+
37+
function _onlyAdmin() private view {
38+
require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "sender doesn't have admin role");
39+
}
40+
41+
/**
42+
@param bridgeAddress Contract address of previously deployed Bridge.
43+
*/
44+
constructor(address bridgeAddress) public {
45+
_bridgeAddress = bridgeAddress;
46+
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
47+
}
48+
/**
49+
@notice Maps the {handlerAddress} to {resourceID} to {destinantionDomainID} in {_domainResourceIDToFeeHandlerAddress}.
50+
@param destinationDomainID ID of chain FeeHandler contracts will be called.
51+
@param resourceID ResourceID for which the corresponding FeeHandler will collect/calcualte fee.
52+
@param handlerAddress Address of FeeHandler which will be called for specified resourceID.
53+
*/
54+
function adminSetResourceHandler(uint8 destinationDomainID, bytes32 resourceID, IFeeHandler handlerAddress) external onlyAdmin {
55+
_domainResourceIDToFeeHandlerAddress[destinationDomainID][resourceID] = handlerAddress;
56+
}
57+
58+
59+
/**
60+
@notice Initiates collecting fee with corresponding fee handler contract using IFeeHandler interface.
61+
@param sender Sender of the deposit.
62+
@param destinationDomainID ID of chain deposit will be bridged to.
63+
@param resourceID ResourceID to be used when making deposits.
64+
@param depositData Additional data to be passed to specified handler.
65+
@param feeData Additional data to be passed to the fee handler.
66+
*/
67+
function collectFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) payable external onlyBridge {
68+
IFeeHandler feeHandler = _domainResourceIDToFeeHandlerAddress[destinationDomainID][resourceID];
69+
feeHandler.collectFee{value: msg.value}(sender, fromDomainID, destinationDomainID, resourceID, depositData, feeData);
70+
}
71+
72+
/**
73+
@notice Initiates calculating fee with corresponding fee handler contract using IFeeHandler interface.
74+
@param sender Sender of the deposit.
75+
@param destinationDomainID ID of chain deposit will be bridged to.
76+
@param resourceID ResourceID to be used when making deposits.
77+
@param depositData Additional data to be passed to specified handler.
78+
@param feeData Additional data to be passed to the fee handler.
79+
@return fee Returns the fee amount.
80+
@return tokenAddress Returns the address of the token to be used for fee.
81+
*/
82+
function calculateFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) external view returns(uint256 fee, address tokenAddress) {
83+
IFeeHandler feeHandler = _domainResourceIDToFeeHandlerAddress[destinationDomainID][resourceID];
84+
return feeHandler.calculateFee(sender, fromDomainID, destinationDomainID, resourceID, depositData, feeData);
85+
}
86+
}

contracts/handlers/fee/BasicFeeHandler.sol

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,37 @@ import "../../utils/AccessControl.sol";
1212
*/
1313
contract BasicFeeHandler is IFeeHandler, AccessControl {
1414
address public immutable _bridgeAddress;
15+
address public immutable _feeHandlerRouterAddress;
1516

1617
uint256 public _fee;
1718

1819
event FeeChanged(
1920
uint256 newFee
2021
);
2122

22-
modifier onlyBridge() {
23-
_onlyBridge();
23+
modifier onlyAdmin() {
24+
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "sender doesn't have admin role");
25+
_;
26+
}
27+
28+
modifier onlyBridgeOrRouter() {
29+
_onlyBridgeOrRouter();
2430
_;
2531
}
2632

27-
function _onlyBridge() private view {
28-
require(msg.sender == _bridgeAddress, "sender must be bridge contract");
33+
function _onlyBridgeOrRouter() private view {
34+
require(
35+
msg.sender == _bridgeAddress || msg.sender == _feeHandlerRouterAddress,
36+
"sender must be bridge or fee router contract"
37+
);
2938
}
3039

3140
/**
32-
@param bridgeAddress Contract address of previously deployed Bridge.
41+
@param feeHandlerRouterAddress Contract address of previously deployed FeeHandlerRouter.
3342
*/
34-
constructor(address bridgeAddress) public {
43+
constructor(address bridgeAddress, address feeHandlerRouterAddress) public {
3544
_bridgeAddress = bridgeAddress;
45+
_feeHandlerRouterAddress = feeHandlerRouterAddress;
3646
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
3747
}
3848

@@ -41,7 +51,7 @@ contract BasicFeeHandler is IFeeHandler, AccessControl {
4151
@notice Only callable by an address that currently has the admin role.
4252
@param newAdmin Address that admin role will be granted to.
4353
*/
44-
function renounceAdmin(address newAdmin) external onlyAdmin {
54+
function renounceAdmin(address newAdmin) external {
4555
address sender = _msgSender();
4656
require(sender != newAdmin, 'Cannot renounce oneself');
4757
grantRole(DEFAULT_ADMIN_ROLE, newAdmin);
@@ -56,7 +66,7 @@ contract BasicFeeHandler is IFeeHandler, AccessControl {
5666
@param depositData Additional data to be passed to specified handler.
5767
@param feeData Additional data to be passed to the fee handler.
5868
*/
59-
function collectFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) payable external onlyBridge {
69+
function collectFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) payable external onlyBridgeOrRouter {
6070
require(msg.value == _fee, "Incorrect fee supplied");
6171
emit FeeCollected(sender, fromDomainID, destinationDomainID, resourceID, _fee, address(0));
6272
}
@@ -100,8 +110,5 @@ contract BasicFeeHandler is IFeeHandler, AccessControl {
100110
}
101111
}
102112

103-
modifier onlyAdmin() {
104-
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "sender doesn't have admin role");
105-
_;
106-
}
113+
107114
}

contracts/handlers/fee/FeeHandlerWithOracle.sol

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
1717
*/
1818
contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe {
1919
address public immutable _bridgeAddress;
20+
address public immutable _feeHandlerRouterAddress;
2021

2122
address public _oracleAddress;
2223

@@ -41,11 +42,30 @@ contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe {
4142
uint256 amount;
4243
}
4344

45+
modifier onlyAdmin() {
46+
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "sender doesn't have admin role");
47+
_;
48+
}
49+
50+
modifier onlyBridgeOrRouter() {
51+
_onlyBridgeOrRouter();
52+
_;
53+
}
54+
55+
function _onlyBridgeOrRouter() private view {
56+
require(
57+
msg.sender == _bridgeAddress || msg.sender == _feeHandlerRouterAddress,
58+
"sender must be bridge or fee router contract"
59+
);
60+
}
61+
4462
/**
4563
@param bridgeAddress Contract address of previously deployed Bridge.
64+
@param feeHandlerRouterAddress Contract address of previously deployed FeeHandlerRouter.
4665
*/
47-
constructor(address bridgeAddress) public {
66+
constructor(address bridgeAddress, address feeHandlerRouterAddress) public {
4867
_bridgeAddress = bridgeAddress;
68+
_feeHandlerRouterAddress = feeHandlerRouterAddress;
4969
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
5070
}
5171

@@ -56,7 +76,7 @@ contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe {
5676
@notice Only callable by an address that currently has the admin role.
5777
@param newAdmin Address that admin role will be granted to.
5878
*/
59-
function renounceAdmin(address newAdmin) external onlyAdmin {
79+
function renounceAdmin(address newAdmin) external {
6080
address sender = _msgSender();
6181
require(sender != newAdmin, 'Cannot renounce oneself');
6282
grantRole(DEFAULT_ADMIN_ROLE, newAdmin);
@@ -90,7 +110,7 @@ contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe {
90110
@param depositData Additional data to be passed to specified handler.
91111
@param feeData Additional data to be passed to the fee handler.
92112
*/
93-
function collectFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) payable external onlyBridge {
113+
function collectFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) payable external onlyBridgeOrRouter {
94114
require(msg.value == 0, "collectFee: msg.value != 0");
95115
(uint256 fee, address tokenAddress) = _calculateFee(sender, fromDomainID, destinationDomainID, resourceID, depositData, feeData);
96116
lockERC20(tokenAddress, sender, address(this), fee);
@@ -189,18 +209,4 @@ contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe {
189209
address signerAddressRecovered = ECDSA.recover(message, signature);
190210
require(signerAddressRecovered == signerAddress, 'Invalid signature');
191211
}
192-
193-
modifier onlyAdmin() {
194-
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "sender doesn't have admin role");
195-
_;
196-
}
197-
198-
modifier onlyBridge() {
199-
_onlyBridge();
200-
_;
201-
}
202-
203-
function _onlyBridge() private view {
204-
require(msg.sender == _bridgeAddress, "sender must be bridge contract");
205-
}
206212
}

test/feeRouter/feeRouter.js

Whitespace-only changes.

test/handlers/fee/basic/admin.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
const Helpers = require("../../../helpers");
99

1010
const BasicFeeHandlerContract = artifacts.require("BasicFeeHandler");
11+
const FeeHandlerRouterContract = artifacts.require("FeeHandlerRouter");
1112

1213
contract("BasicFeeHandler - [admin]", async accounts => {
1314
const domainID = 1;
@@ -24,7 +25,8 @@
2425

2526
beforeEach(async () => {
2627
BridgeInstance = awaitBridgeInstance = await Helpers.deployBridge(domainID, accounts[0]);
27-
BasicFeeHandlerInstance = await BasicFeeHandlerContract.new(BridgeInstance.address);
28+
FeeHandlerRouterInstance = await FeeHandlerRouterContract.new(BridgeInstance.address);
29+
BasicFeeHandlerInstance = await BasicFeeHandlerContract.new(BridgeInstance.address, FeeHandlerRouterInstance.address);
2830

2931
ADMIN_ROLE = await BasicFeeHandlerInstance.DEFAULT_ADMIN_ROLE();
3032
});

test/handlers/fee/basic/calculateFee.js

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ const Ethers = require("ethers");
88

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

11-
const BridgeContract = artifacts.require("Bridge");
1211
const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser");
1312
const ERC20HandlerContract = artifacts.require("ERC20Handler");
1413
const BasicFeeHandlerContract = artifacts.require("BasicFeeHandler");
14+
const FeeHandlerRouterContract = artifacts.require("FeeHandlerRouter");
1515

1616
contract("BasicFeeHandler - [calculateFee]", async (accounts) => {
1717
const originDomainID = 1;
@@ -27,42 +27,48 @@ contract("BasicFeeHandler - [calculateFee]", async (accounts) => {
2727
let initialResourceIDs;
2828
let initialContractAddresses;
2929
let ERC20MintableInstance;
30+
let FeeHandlerRouterInstance;
3031

3132
beforeEach(async () => {
3233
await Promise.all([
34+
BridgeInstance = await Helpers.deployBridge(destinationDomainID, accounts[0]),
3335
ERC20MintableContract.new("token", "TOK").then(instance => ERC20MintableInstance = instance),
34-
BridgeInstance = BridgeInstance = await Helpers.deployBridge(destinationDomainID, accounts[0])
3536
]);
3637

38+
ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address);
39+
FeeHandlerRouterInstance = await FeeHandlerRouterContract.new(BridgeInstance.address);
40+
BasicFeeHandlerInstance = await BasicFeeHandlerContract.new(BridgeInstance.address, FeeHandlerRouterInstance.address);
41+
3742
resourceID = Helpers.createResourceID(ERC20MintableInstance.address, originDomainID);
3843
initialResourceIDs = [resourceID];
3944
initialContractAddresses = [ERC20MintableInstance.address];
40-
burnableContractAddresses = [];
41-
42-
BasicFeeHandlerInstance = await BasicFeeHandlerContract.new(BridgeInstance.address);
43-
44-
ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address);
4545

46-
await BridgeInstance.adminSetResource(ERC20HandlerInstance.address, resourceID, ERC20MintableInstance.address)
46+
burnableContractAddresses = [];
4747

4848
depositData = Helpers.createERCDepositData(100, 20, recipientAddress);
49+
50+
await Promise.all([
51+
BridgeInstance.adminSetResource(ERC20HandlerInstance.address, resourceID, ERC20MintableInstance.address),
52+
BridgeInstance.adminChangeFeeHandler(FeeHandlerRouterInstance.address),
53+
FeeHandlerRouterInstance.adminSetResourceHandler(destinationDomainID, resourceID, BasicFeeHandlerInstance.address),
54+
]);
4955
});
5056

5157
it("should return amount of fee", async () => {
52-
await BridgeInstance.adminChangeFeeHandler(BasicFeeHandlerInstance.address);
5358
// current fee is set to 0
54-
let res = await BasicFeeHandlerInstance.calculateFee.call(
59+
let res = await FeeHandlerRouterInstance.calculateFee.call(
5560
relayer,
5661
originDomainID,
5762
destinationDomainID,
5863
resourceID,
5964
depositData,
6065
feeData
6166
);
67+
6268
assert.equal(web3.utils.fromWei(res[0], "ether"), "0");
6369
// Change fee to 0.5 ether
6470
await BasicFeeHandlerInstance.changeFee(Ethers.utils.parseEther("0.5"));
65-
res = await BasicFeeHandlerInstance.calculateFee.call(
71+
res = await FeeHandlerRouterInstance.calculateFee.call(
6672
relayer,
6773
originDomainID,
6874
destinationDomainID,

test/handlers/fee/basic/changeFee.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const Ethers = require("ethers");
99
const Helpers = require('../../../helpers');
1010

1111
const BasicFeeHandlerContract = artifacts.require("BasicFeeHandler");
12+
const FeeHandlerRouterContract = artifacts.require("FeeHandlerRouter");
1213

1314
contract("BasicFeeHandler - [changeFee]", async accounts => {
1415
const domainID = 1;
@@ -21,16 +22,17 @@ contract("BasicFeeHandler - [changeFee]", async accounts => {
2122
let BridgeInstance;
2223

2324
beforeEach(async () => {
24-
BridgeInstance = awaitBridgeInstance = await Helpers.deployBridge(domainID, accounts[0]);
25+
BridgeInstance = await Helpers.deployBridge(domainID, accounts[0]);
26+
FeeHandlerRouterInstance = await FeeHandlerRouterContract.new(BridgeInstance.address);
2527
});
2628

2729
it("[sanity] contract should be deployed successfully", async () => {
2830
TruffleAssert.passes(
29-
await BasicFeeHandlerContract.new(BridgeInstance.address));
31+
await BasicFeeHandlerContract.new(BridgeInstance.address, FeeHandlerRouterInstance.address));
3032
});
3133

3234
it("should set fee", async () => {
33-
const BasicFeeHandlerInstance = await BasicFeeHandlerContract.new(BridgeInstance.address);
35+
const BasicFeeHandlerInstance = await BasicFeeHandlerContract.new(BridgeInstance.address, FeeHandlerRouterInstance.address);
3436
const fee = Ethers.utils.parseEther("0.05");
3537
const tx = await BasicFeeHandlerInstance.changeFee(fee);
3638
TruffleAssert.eventEmitted(tx, "FeeChanged", (event) =>
@@ -41,12 +43,12 @@ contract("BasicFeeHandler - [changeFee]", async accounts => {
4143
});
4244

4345
it("should not set the same fee", async () => {
44-
const BasicFeeHandlerInstance = await BasicFeeHandlerContract.new(BridgeInstance.address);
46+
const BasicFeeHandlerInstance = await BasicFeeHandlerContract.new(BridgeInstance.address, FeeHandlerRouterInstance.address);
4547
await TruffleAssert.reverts(BasicFeeHandlerInstance.changeFee(0), "Current fee is equal to new fee");
4648
});
4749

4850
it("should require admin role to change fee", async () => {
49-
const BasicFeeHandlerInstance = await BasicFeeHandlerContract.new(BridgeInstance.address);
51+
const BasicFeeHandlerInstance = await BasicFeeHandlerContract.new(BridgeInstance.address, FeeHandlerRouterInstance.address);
5052
await assertOnlyAdmin(BasicFeeHandlerInstance.changeFee, 1);
5153
});
5254
});

0 commit comments

Comments
 (0)