From f90106ce5f57848efc6d6059212373fcd05654b8 Mon Sep 17 00:00:00 2001 From: viatrix Date: Thu, 9 Feb 2023 23:58:37 +0200 Subject: [PATCH 1/4] feat: add fee handler for substrate --- .../handlers/fee/FeeHandlerSubstrate.sol | 107 +++++ .../handlers/fee/FeeHandlerWithOracle.sol | 2 +- .../fee/withOracle/calculateFeeSubstrate.js | 368 ++++++++++++++++++ 3 files changed, 476 insertions(+), 1 deletion(-) create mode 100644 contracts/handlers/fee/FeeHandlerSubstrate.sol create mode 100644 test/handlers/fee/withOracle/calculateFeeSubstrate.js diff --git a/contracts/handlers/fee/FeeHandlerSubstrate.sol b/contracts/handlers/fee/FeeHandlerSubstrate.sol new file mode 100644 index 00000000..c00093a1 --- /dev/null +++ b/contracts/handlers/fee/FeeHandlerSubstrate.sol @@ -0,0 +1,107 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.11; + +import "./FeeHandlerWithOracle.sol"; + +/** + @title Handles deposit fees on Substrate based on Effective rates provided by Fee oracle. + @author ChainSafe Systems. + @notice This contract is intended to be used with the Bridge contract. + */ +contract FeeHandlerSubstrate is FeeHandlerWithOracle { + + struct SubstrateOracleMessageType { + // Base Effective Rate - effective rate between base currencies of source and dest networks (eg. MATIC/ETH) + uint256 ber; + // Token Effective Rate - rate between base currency of destination network and token that is being trasferred (eg. MATIC/USDT) + uint256 ter; + // Final fee - resulting fee calculated by the oracle + uint256 finalFee; + uint256 expiresAt; + uint8 fromDomainID; + uint8 toDomainID; + bytes32 resourceID; + uint256 msgGasLimit; + } + + /** + @param bridgeAddress Contract address of previously deployed Bridge. + @param feeHandlerRouterAddress Contract address of previously deployed FeeHandlerRouter. + */ + constructor(address bridgeAddress, address feeHandlerRouterAddress) FeeHandlerWithOracle(bridgeAddress, feeHandlerRouterAddress) { + } + + /** + @notice Calculates fee for deposit for Substrate. + This function is almost identical to the _calculateFee function in the base contract. + The differences are: unpacking of the oracle message and the txCost calculation formula. + Oracle will do the calculation of the tx cost and provide the resulting fee to the contract. + The resulting calculation is: + txCost = finalFee * oracleMessage.ter / 1e18 + @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 to be passed to specified handler. + @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) internal view override returns(uint256 fee, address tokenAddress) { + /** + Message: + ber * 10^18: uint256 (not used) + ter * 10^18: uint256 + finalFee: uint256 + expiresAt: uint256 + fromDomainID: uint8 encoded as uint256 + toDomainID: uint8 encoded as uint256 + resourceID: bytes32 + msgGasLimit: uint256 (not used) + sig: bytes(65 bytes) + + total in bytes: + message: + 32 * 8 = 256 + message + sig: + 256 + 65 = 321 + + amount: uint256 + total: 353 + */ + + require(feeData.length == 353, "Incorrect feeData length"); + + FeeDataType memory feeDataDecoded; + uint256 txCost; + + feeDataDecoded.message = bytes(feeData[: 256]); + feeDataDecoded.sig = bytes(feeData[256: 321]); + feeDataDecoded.amount = abi.decode(feeData[321:], (uint256)); + + SubstrateOracleMessageType memory oracleMessage = abi.decode(feeDataDecoded.message, (SubstrateOracleMessageType)); + require(block.timestamp <= oracleMessage.expiresAt, "Obsolete oracle data"); + require((oracleMessage.fromDomainID == fromDomainID) + && (oracleMessage.toDomainID == destinationDomainID) + && (oracleMessage.resourceID == resourceID), + "Incorrect deposit params" + ); + + bytes32 messageHash = keccak256(feeDataDecoded.message); + + verifySig(messageHash, feeDataDecoded.sig, _oracleAddress); + + address tokenHandler = IBridge(_bridgeAddress)._resourceIDToHandlerAddress(resourceID); + address tokenAddress = IERCHandler(tokenHandler)._resourceIDToTokenContractAddress(resourceID); + + txCost= oracleMessage.finalFee * oracleMessage.ter / 1e18; + + fee = feeDataDecoded.amount * _feePercent / 1e4; // 100 for percent and 100 to avoid precision loss + + if (fee < txCost) { + fee = txCost; + } + return (fee, tokenAddress); + } +} diff --git a/contracts/handlers/fee/FeeHandlerWithOracle.sol b/contracts/handlers/fee/FeeHandlerWithOracle.sol index 5580b576..f7e53330 100644 --- a/contracts/handlers/fee/FeeHandlerWithOracle.sol +++ b/contracts/handlers/fee/FeeHandlerWithOracle.sol @@ -133,7 +133,7 @@ contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe { 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) { + function _calculateFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) internal view virtual returns(uint256 fee, address tokenAddress) { /** Message: ber * 10^18: uint256 diff --git a/test/handlers/fee/withOracle/calculateFeeSubstrate.js b/test/handlers/fee/withOracle/calculateFeeSubstrate.js new file mode 100644 index 00000000..7cf11701 --- /dev/null +++ b/test/handlers/fee/withOracle/calculateFeeSubstrate.js @@ -0,0 +1,368 @@ +/** + * Copyright 2022 ChainSafe Systems + * SPDX-License-Identifier: LGPL-3.0-only + */ + +const TruffleAssert = require("truffle-assertions"); +const Ethers = require("ethers"); + +const Helpers = require("../../../helpers"); + +const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); +const ERC20HandlerContract = artifacts.require("ERC20Handler"); +const FeeHandlerSubstrateContract = artifacts.require("FeeHandlerSubstrate"); +const FeeHandlerRouterContract = artifacts.require("FeeHandlerRouter"); + +contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { + const originDomainID = 1; + const destinationDomainID = 1; + const oracle = new Ethers.Wallet.createRandom(); + const sender = accounts[0]; + const recipientAddress = accounts[1]; + const gasUsed = 100000; + const feePercent = 500; + const emptySetResourceData = "0x"; + const msgGasLimit = 0; + const ber = 0; + + let BridgeInstance; + let FeeHandlerSubstrateInstance; + let resourceID; + let FeeHandlerRouterInstance; + + /** + Message: + ber * 10^18: uint256 (not used) + ter * 10^18: uint256 + finalFee: uint256 + expiresAt: uint256 + fromDomainID: uint8 encoded as uint256 + toDomainID: uint8 encoded as uint256 + resourceID: bytes32 + msgGasLimit: uint256 (not used) + sig: bytes(65 bytes) + + total in bytes: + message: + 32 * 8 = 256 + message + sig: + 256 + 65 = 321 + + amount: uint256 + total: 353 + */ + + beforeEach(async () => { + await Promise.all([ + (BridgeInstance = await Helpers.deployBridge( + destinationDomainID, + accounts[0] + )), + (ERC20MintableInstance = await ERC20MintableContract.new( + "token", + "TOK" + ).then((instance) => (ERC20MintableInstance = instance))), + ]); + + ERC20HandlerInstance = await ERC20HandlerContract.new( + BridgeInstance.address + ); + FeeHandlerRouterInstance = await FeeHandlerRouterContract.new( + BridgeInstance.address + ); + FeeHandlerSubstrateInstance = await FeeHandlerSubstrateContract.new( + BridgeInstance.address, + FeeHandlerRouterInstance.address + ); + + resourceID = Helpers.createResourceID( + ERC20MintableInstance.address, + originDomainID + ); + + await Promise.all([ + FeeHandlerSubstrateInstance.setFeeOracle(oracle.address), + FeeHandlerSubstrateInstance.setFeeProperties(gasUsed, feePercent), + BridgeInstance.adminSetResource( + ERC20HandlerInstance.address, + resourceID, + ERC20MintableInstance.address, + emptySetResourceData + ), + BridgeInstance.adminChangeFeeHandler(FeeHandlerRouterInstance.address), + FeeHandlerRouterInstance.adminSetResourceHandler( + destinationDomainID, + resourceID, + FeeHandlerSubstrateInstance.address + ), + ]); + }); + + it("should calculate amount of fee and return token address", async () => { + const tokenAmount = 100; + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + + const oracleResponse = { + ber, + ter: Ethers.utils.parseEther("1.63934"), + // dstGasPrice is used as finalFee for Substrate calculations + dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), + expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, + fromDomainID: originDomainID, + toDomainID: destinationDomainID, + resourceID, + msgGasLimit, + }; + + const feeData = Helpers.createOracleFeeData( + oracleResponse, + oracle.privateKey, + tokenAmount + ); + const res = await FeeHandlerRouterInstance.calculateFee.call( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ); + assert.equal(Ethers.utils.formatUnits(res.fee.toString(), "wei"), "49180200000"); + assert.equal(res.tokenAddress, ERC20MintableInstance.address); + }); + + it("should return percent fee", async () => { + const tokenAmount = Ethers.utils.parseEther("1"); + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + const oracleResponse = { + ber, + ter: Ethers.utils.parseEther("1.63934"), + // dstGasPrice is used as finalFee for Substrate calculations + dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), + expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, + fromDomainID: originDomainID, + toDomainID: destinationDomainID, + resourceID, + msgGasLimit, + }; + + const feeData = Helpers.createOracleFeeData( + oracleResponse, + oracle.privateKey, + tokenAmount + ); + const res = await FeeHandlerRouterInstance.calculateFee.call( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ); + assert.equal(web3.utils.fromWei(res.fee, "ether"), "0.05"); + assert.equal(res.tokenAddress, ERC20MintableInstance.address); + }); + + it("should return fee to cover tx cost if percent fee does not cover tx cost", async () => { + const tokenAmount = 100; + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + + const oracleResponse = { + ber, + ter: Ethers.utils.parseEther("1.5"), + // dstGasPrice is used as finalFee for Substrate calculations + dstGasPrice: Ethers.utils.parseEther("0.003"), + expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, + fromDomainID: originDomainID, + toDomainID: destinationDomainID, + resourceID, + msgGasLimit, + }; + + const feeData = Helpers.createOracleFeeData( + oracleResponse, + oracle.privateKey, + tokenAmount + ); + const res = await FeeHandlerRouterInstance.calculateFee.call( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ); + assert.equal(Ethers.utils.formatEther(res.fee.toString()), "0.0045"); + assert.equal(res.tokenAddress, ERC20MintableInstance.address); + }); + + it("should not calculate fee if fee data is misformed", async () => { + const tokenAmount = 100; + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + + const oracleResponse = { + ber, + ter: Ethers.utils.parseEther("1.5"), + // dstGasPrice is used as finalFee for Substrate calculations + dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), + expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, + fromDomainID: originDomainID, + toDomainID: destinationDomainID, + resourceID, + msgGasLimit, + }; + + const feeData = + Helpers.createOracleFeeData( + oracleResponse, + oracle.privateKey, + tokenAmount + ) + "11"; + await TruffleAssert.reverts( + FeeHandlerRouterInstance.calculateFee( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ), + "Incorrect feeData length" + ); + }); + + it("should not calculate fee if deposit data differ from fee data", async () => { + const otherDestinationDomainID = 3; + const tokenAmount = 100; + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + + const oracleResponse = { + ber, + ter: Ethers.utils.parseEther("1.5"), + // dstGasPrice is used as finalFee for Substrate calculations + dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), + expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, + fromDomainID: originDomainID, + toDomainID: otherDestinationDomainID, + resourceID, + msgGasLimit, + }; + + const feeData = Helpers.createOracleFeeData( + oracleResponse, + oracle.privateKey, + tokenAmount + ); + await TruffleAssert.reverts( + FeeHandlerRouterInstance.calculateFee( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ), + "Incorrect deposit params" + ); + }); + + it("should not calculate fee if oracle signature is incorrect", async () => { + const tokenAmount = 100; + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + + const oracleResponse = { + ber, + ter: Ethers.utils.parseEther("1.5"), + // dstGasPrice is used as finalFee for Substrate calculations + dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), + expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, + fromDomainID: originDomainID, + toDomainID: destinationDomainID, + resourceID, + msgGasLimit, + }; + + const oracle2 = new Ethers.Wallet.createRandom(); + + const feeData = Helpers.createOracleFeeData( + oracleResponse, + oracle2.privateKey, + tokenAmount + ); + await TruffleAssert.reverts( + FeeHandlerRouterInstance.calculateFee( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ), + "Invalid signature" + ); + }); + + it("should not calculate fee if oracle data are outdated", async () => { + const gasUsed = 100000; + const feePercent = 500; + await FeeHandlerSubstrateInstance.setFeeProperties(gasUsed, feePercent); + + const tokenAmount = Ethers.utils.parseEther("1"); + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + const oracleResponse = { + ber, + ter: Ethers.utils.parseEther("1.63934"), + // dstGasPrice is used as finalFee for Substrate calculations + dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), + expiresAt: Math.floor(new Date().valueOf() / 1000) - 500, + fromDomainID: originDomainID, + toDomainID: destinationDomainID, + resourceID, + msgGasLimit, + }; + const feeData = Helpers.createOracleFeeData( + oracleResponse, + oracle.privateKey, + tokenAmount + ); + await TruffleAssert.reverts( + FeeHandlerRouterInstance.calculateFee( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ), + "Obsolete oracle data" + ); + }); +}); From 48f8cc9729d76589291f6c30100e212a3d0e83d3 Mon Sep 17 00:00:00 2001 From: viatrix Date: Fri, 10 Feb 2023 18:44:00 +0200 Subject: [PATCH 2/4] Add generic fee handler --- contracts/handlers/fee/FeeHandlerGeneric.sol | 92 ++++ .../handlers/fee/FeeHandlerSubstrate.sol | 2 +- .../handlers/fee/FeeHandlerWithOracle.sol | 13 +- test/handlers/fee/withOracle/calculateFee.js | 95 ++--- .../fee/withOracle/calculateFeeGeneric.js | 398 ++++++++++++++++++ .../fee/withOracle/calculateFeeSubstrate.js | 2 +- 6 files changed, 526 insertions(+), 76 deletions(-) create mode 100644 contracts/handlers/fee/FeeHandlerGeneric.sol create mode 100644 test/handlers/fee/withOracle/calculateFeeGeneric.js diff --git a/contracts/handlers/fee/FeeHandlerGeneric.sol b/contracts/handlers/fee/FeeHandlerGeneric.sol new file mode 100644 index 00000000..d9c4db30 --- /dev/null +++ b/contracts/handlers/fee/FeeHandlerGeneric.sol @@ -0,0 +1,92 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.11; + +import "./FeeHandlerWithOracle.sol"; + +/** + @title Handles deposit fees for generic messages based on Effective rates provided by Fee oracle. + @author ChainSafe Systems. + @notice This contract is intended to be used with the Bridge contract. + */ +contract FeeHandlerGeneric is FeeHandlerWithOracle { + + /** + @param bridgeAddress Contract address of previously deployed Bridge. + @param feeHandlerRouterAddress Contract address of previously deployed FeeHandlerRouter. + */ + constructor(address bridgeAddress, address feeHandlerRouterAddress) FeeHandlerWithOracle(bridgeAddress, feeHandlerRouterAddress) { + } + + /** + @notice Calculates fee for generic messages. + This function is almost identical to the _calculateFee function in the base contract. + The difference is the txCost calculation formula. + @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 to be passed to specified handler. + @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) internal view override returns(uint256 fee, address tokenAddress) { + /** + Message: + ber * 10^18: uint256 + ter * 10^18: uint256 (not used) + dstGasPrice: uint256 + expiresAt: uint256 + fromDomainID: uint8 encoded as uint256 + toDomainID: uint8 encoded as uint256 + resourceID: bytes32 + msgGasLimit: uint256 + sig: bytes(65 bytes) + + total in bytes: + message: + 32 * 8 = 256 + message + sig: + 256 + 65 = 321 + + amount: uint256 + total: 353 + */ + + require(feeData.length == 353, "Incorrect feeData length"); + + FeeDataType memory feeDataDecoded; + uint256 txCost; + + feeDataDecoded.message = bytes(feeData[: 256]); + feeDataDecoded.sig = bytes(feeData[256: 321]); + feeDataDecoded.amount = abi.decode(feeData[321:], (uint256)); + + OracleMessageType memory oracleMessage = abi.decode(feeDataDecoded.message, (OracleMessageType)); + require(block.timestamp <= oracleMessage.expiresAt, "Obsolete oracle data"); + require((oracleMessage.fromDomainID == fromDomainID) + && (oracleMessage.toDomainID == destinationDomainID) + && (oracleMessage.resourceID == resourceID), + "Incorrect deposit params" + ); + + bytes32 messageHash = keccak256(feeDataDecoded.message); + + verifySig(messageHash, feeDataDecoded.sig, _oracleAddress); + + address tokenHandler = IBridge(_bridgeAddress)._resourceIDToHandlerAddress(resourceID); + address tokenAddress = IERCHandler(tokenHandler)._resourceIDToTokenContractAddress(resourceID); + + require(oracleMessage.msgGasLimit > 0, "msgGasLimit == 0"); + // txCost = dstGasPrice * oracleMessage.msgGasLimit * Base Effective Rate (rate between base currencies of source and dest) + txCost = oracleMessage.dstGasPrice * oracleMessage.msgGasLimit * oracleMessage.ber / 1e18; + + fee = feeDataDecoded.amount * _feePercent / 1e4; // 100 for percent and 100 to avoid precision loss + + if (fee < txCost) { + fee = txCost; + } + return (fee, tokenAddress); + } +} diff --git a/contracts/handlers/fee/FeeHandlerSubstrate.sol b/contracts/handlers/fee/FeeHandlerSubstrate.sol index c00093a1..966b52c3 100644 --- a/contracts/handlers/fee/FeeHandlerSubstrate.sol +++ b/contracts/handlers/fee/FeeHandlerSubstrate.sol @@ -95,7 +95,7 @@ contract FeeHandlerSubstrate is FeeHandlerWithOracle { address tokenHandler = IBridge(_bridgeAddress)._resourceIDToHandlerAddress(resourceID); address tokenAddress = IERCHandler(tokenHandler)._resourceIDToTokenContractAddress(resourceID); - txCost= oracleMessage.finalFee * oracleMessage.ter / 1e18; + txCost = oracleMessage.finalFee * oracleMessage.ter / 1e18; fee = feeDataDecoded.amount * _feePercent / 1e4; // 100 for percent and 100 to avoid precision loss diff --git a/contracts/handlers/fee/FeeHandlerWithOracle.sol b/contracts/handlers/fee/FeeHandlerWithOracle.sol index f7e53330..77b37806 100644 --- a/contracts/handlers/fee/FeeHandlerWithOracle.sol +++ b/contracts/handlers/fee/FeeHandlerWithOracle.sol @@ -136,14 +136,14 @@ contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe { function _calculateFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) internal view virtual returns(uint256 fee, address tokenAddress) { /** Message: - ber * 10^18: uint256 + ber * 10^18: uint256 (not used) ter * 10^18: uint256 dstGasPrice: uint256 expiresAt: uint256 fromDomainID: uint8 encoded as uint256 toDomainID: uint8 encoded as uint256 resourceID: bytes32 - msgGasLimit: uint256 + msgGasLimit: uint256 (not used) sig: bytes(65 bytes) total in bytes: @@ -180,13 +180,8 @@ contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe { address tokenHandler = IBridge(_bridgeAddress)._resourceIDToHandlerAddress(resourceID); address tokenAddress = IERCHandler(tokenHandler)._resourceIDToTokenContractAddress(resourceID); - if(oracleMessage.msgGasLimit > 0) { - // txCost = dstGasPrice * oracleMessage.msgGasLimit * Base Effective Rate (rate between base currencies of source and dest) - txCost = oracleMessage.dstGasPrice * oracleMessage.msgGasLimit * oracleMessage.ber / 1e18; - } else { - // txCost = dstGasPrice * _gasUsed * Token Effective Rate (rate of dest base currency to token) - txCost = oracleMessage.dstGasPrice * _gasUsed * oracleMessage.ter / 1e18; - } + // txCost = dstGasPrice * _gasUsed * Token Effective Rate (rate of dest base currency to token) + txCost = oracleMessage.dstGasPrice * _gasUsed * oracleMessage.ter / 1e18; fee = feeDataDecoded.amount * _feePercent / 1e4; // 100 for percent and 100 to avoid precision loss diff --git a/test/handlers/fee/withOracle/calculateFee.js b/test/handlers/fee/withOracle/calculateFee.js index de585686..53e80cad 100644 --- a/test/handlers/fee/withOracle/calculateFee.js +++ b/test/handlers/fee/withOracle/calculateFee.js @@ -23,32 +23,34 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { const feePercent = 500; const emptySetResourceData = "0x"; const msgGasLimit = 0; + const ber = 0; let BridgeInstance; let FeeHandlerWithOracleInstance; let resourceID; let FeeHandlerRouterInstance; - /* - feeData structure: - ber*10^18: uint256 - ter*10^18: uint256 - dstGasPrice: uint256 - expiresAt: uint256 - fromDomainID: uint8 encoded as uint256 - toDomainID: uint8 encoded as uint256 - resourceID: bytes32 - sig: bytes(65 bytes) - - total in bytes: - message: - 32 * 7 = 224 - message + sig: - 224 + 65 = 289 - - amount: uint256 - total feeData length: 321 - */ + /** + Message: + ber * 10^18: uint256 (not used) + ter * 10^18: uint256 + dstGasPrice: uint256 + expiresAt: uint256 + fromDomainID: uint8 encoded as uint256 + toDomainID: uint8 encoded as uint256 + resourceID: bytes32 + msgGasLimit: uint256 (not used) + sig: bytes(65 bytes) + + total in bytes: + message: + 32 * 8 = 256 + message + sig: + 256 + 65 = 321 + + amount: uint256 + total: 353 + */ beforeEach(async () => { await Promise.all([ @@ -96,7 +98,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { ]); }); - it("should calculate amount of fee without msgGasLimit and return token address", async () => { + it("should calculate amount of fee and return token address", async () => { const tokenAmount = 100; const depositData = Helpers.createERCDepositData( tokenAmount, @@ -105,7 +107,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { ); const oracleResponse = { - ber: Ethers.utils.parseEther("0.000533"), + ber, ter: Ethers.utils.parseEther("1.63934"), dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, @@ -132,43 +134,6 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { assert.equal(res.tokenAddress, ERC20MintableInstance.address); }); - it("should calculate amount of fee with msgGasLimit and return token address", async () => { - const tokenAmount = 100; - const depositData = Helpers.createERCDepositData( - tokenAmount, - 20, - recipientAddress - ); - const msgGasLimit = 2300000; - - const oracleResponse = { - ber: Ethers.utils.parseEther("0.000533"), - ter: Ethers.utils.parseEther("1.63934"), - dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), - expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, - fromDomainID: originDomainID, - toDomainID: destinationDomainID, - resourceID, - msgGasLimit, - }; - - const feeData = Helpers.createOracleFeeData( - oracleResponse, - oracle.privateKey, - tokenAmount - ); - const res = await FeeHandlerRouterInstance.calculateFee.call( - sender, - originDomainID, - destinationDomainID, - resourceID, - depositData, - feeData - ); - assert.equal(Ethers.utils.formatEther(res.fee.toString()), "0.000036777"); - assert.equal(res.tokenAddress, ERC20MintableInstance.address); - }); - it("should return percent fee", async () => { const tokenAmount = Ethers.utils.parseEther("1"); const depositData = Helpers.createERCDepositData( @@ -177,7 +142,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { recipientAddress ); const oracleResponse = { - ber: Ethers.utils.parseEther("0.000533"), + ber, ter: Ethers.utils.parseEther("1.63934"), dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, @@ -213,7 +178,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { ); const oracleResponse = { - ber: Ethers.utils.parseEther("0.0005"), + ber, ter: Ethers.utils.parseEther("1.5"), dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, @@ -249,7 +214,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { ); const oracleResponse = { - ber: Ethers.utils.parseEther("0.0005"), + ber, ter: Ethers.utils.parseEther("1.5"), dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, @@ -288,7 +253,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { ); const oracleResponse = { - ber: Ethers.utils.parseEther("0.0005"), + ber, ter: Ethers.utils.parseEther("1.5"), dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, @@ -325,7 +290,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { ); const oracleResponse = { - ber: Ethers.utils.parseEther("0.0005"), + ber, ter: Ethers.utils.parseEther("1.5"), dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, @@ -367,7 +332,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { recipientAddress ); const oracleResponse = { - ber: Ethers.utils.parseEther("0.000533"), + ber, ter: Ethers.utils.parseEther("1.63934"), dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), expiresAt: Math.floor(new Date().valueOf() / 1000) - 500, diff --git a/test/handlers/fee/withOracle/calculateFeeGeneric.js b/test/handlers/fee/withOracle/calculateFeeGeneric.js new file mode 100644 index 00000000..db7dc0f0 --- /dev/null +++ b/test/handlers/fee/withOracle/calculateFeeGeneric.js @@ -0,0 +1,398 @@ +/** + * Copyright 2022 ChainSafe Systems + * SPDX-License-Identifier: LGPL-3.0-only + */ + +const TruffleAssert = require("truffle-assertions"); +const Ethers = require("ethers"); + +const Helpers = require("../../../helpers"); + +const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); +const ERC20HandlerContract = artifacts.require("ERC20Handler"); +const FeeHandlerGenericContract = artifacts.require("FeeHandlerGeneric"); +const FeeHandlerRouterContract = artifacts.require("FeeHandlerRouter"); + +contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { + const originDomainID = 1; + const destinationDomainID = 1; + const oracle = new Ethers.Wallet.createRandom(); + const sender = accounts[0]; + const recipientAddress = accounts[1]; + const gasUsed = 100000; + const feePercent = 500; + const emptySetResourceData = "0x"; + const msgGasLimit = 2300000; + const ter = 0; + + let BridgeInstance; + let FeeHandlerGenericInstance; + let resourceID; + let FeeHandlerRouterInstance; + + /** + Message: + ber * 10^18: uint256 + ter * 10^18: uint256 (not used) + dstGasPrice: uint256 + expiresAt: uint256 + fromDomainID: uint8 encoded as uint256 + toDomainID: uint8 encoded as uint256 + resourceID: bytes32 + msgGasLimit: uint256 + sig: bytes(65 bytes) + + total in bytes: + message: + 32 * 8 = 256 + message + sig: + 256 + 65 = 321 + + amount: uint256 + total: 353 + */ + + beforeEach(async () => { + await Promise.all([ + (BridgeInstance = await Helpers.deployBridge( + destinationDomainID, + accounts[0] + )), + (ERC20MintableInstance = await ERC20MintableContract.new( + "token", + "TOK" + ).then((instance) => (ERC20MintableInstance = instance))), + ]); + + ERC20HandlerInstance = await ERC20HandlerContract.new( + BridgeInstance.address + ); + FeeHandlerRouterInstance = await FeeHandlerRouterContract.new( + BridgeInstance.address + ); + FeeHandlerGenericInstance = await FeeHandlerGenericContract.new( + BridgeInstance.address, + FeeHandlerRouterInstance.address + ); + + resourceID = Helpers.createResourceID( + ERC20MintableInstance.address, + originDomainID + ); + + await Promise.all([ + FeeHandlerGenericInstance.setFeeOracle(oracle.address), + FeeHandlerGenericInstance.setFeeProperties(gasUsed, feePercent), + BridgeInstance.adminSetResource( + ERC20HandlerInstance.address, + resourceID, + ERC20MintableInstance.address, + emptySetResourceData + ), + BridgeInstance.adminChangeFeeHandler(FeeHandlerRouterInstance.address), + FeeHandlerRouterInstance.adminSetResourceHandler( + destinationDomainID, + resourceID, + FeeHandlerGenericInstance.address + ), + ]); + }); + + it("should calculate amount of fee with msgGasLimit and return token address", async () => { + const tokenAmount = 100; + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + + const oracleResponse = { + ber: Ethers.utils.parseEther("0.000533"), + ter, + dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), + expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, + fromDomainID: originDomainID, + toDomainID: destinationDomainID, + resourceID, + msgGasLimit, + }; + + const feeData = Helpers.createOracleFeeData( + oracleResponse, + oracle.privateKey, + tokenAmount + ); + const res = await FeeHandlerRouterInstance.calculateFee.call( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ); + assert.equal(Ethers.utils.formatEther(res.fee.toString()), "0.000036777"); + assert.equal(res.tokenAddress, ERC20MintableInstance.address); + }); + + it("should return percent fee", async () => { + const tokenAmount = Ethers.utils.parseEther("1"); + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + const oracleResponse = { + ber: Ethers.utils.parseEther("0.000533"), + ter, + dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), + expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, + fromDomainID: originDomainID, + toDomainID: destinationDomainID, + resourceID, + msgGasLimit, + }; + + const feeData = Helpers.createOracleFeeData( + oracleResponse, + oracle.privateKey, + tokenAmount + ); + const res = await FeeHandlerRouterInstance.calculateFee.call( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ); + assert.equal(web3.utils.fromWei(res.fee, "ether"), "0.05"); + assert.equal(res.tokenAddress, ERC20MintableInstance.address); + }); + + it("should return fee to cover tx cost if percent fee does not cover tx cost", async () => { + const tokenAmount = 100; + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + + const oracleResponse = { + ber: Ethers.utils.parseEther("0.0005"), + ter, + dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), + expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, + fromDomainID: originDomainID, + toDomainID: destinationDomainID, + resourceID, + msgGasLimit: Ethers.utils.parseUnits("10000000", "wei"), + }; + + const feeData = Helpers.createOracleFeeData( + oracleResponse, + oracle.privateKey, + tokenAmount + ); + const res = await FeeHandlerRouterInstance.calculateFee.call( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ); + assert.equal(Ethers.utils.formatEther(res.fee.toString()), "0.00015"); + assert.equal(res.tokenAddress, ERC20MintableInstance.address); + }); + + it("should not calculate fee if fee data is misformed", async () => { + const tokenAmount = 100; + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + + const oracleResponse = { + ber: Ethers.utils.parseEther("0.0005"), + ter, + dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), + expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, + fromDomainID: originDomainID, + toDomainID: destinationDomainID, + resourceID, + msgGasLimit, + }; + + const feeData = + Helpers.createOracleFeeData( + oracleResponse, + oracle.privateKey, + tokenAmount + ) + "11"; + await TruffleAssert.reverts( + FeeHandlerRouterInstance.calculateFee( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ), + "Incorrect feeData length" + ); + }); + + it("should not calculate fee if deposit data differ from fee data", async () => { + const otherDestinationDomainID = 3; + const tokenAmount = 100; + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + + const oracleResponse = { + ber: Ethers.utils.parseEther("0.0005"), + ter, + dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), + expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, + fromDomainID: originDomainID, + toDomainID: otherDestinationDomainID, + resourceID, + msgGasLimit, + }; + + const feeData = Helpers.createOracleFeeData( + oracleResponse, + oracle.privateKey, + tokenAmount + ); + await TruffleAssert.reverts( + FeeHandlerRouterInstance.calculateFee( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ), + "Incorrect deposit params" + ); + }); + + it("should not calculate fee if oracle signature is incorrect", async () => { + const tokenAmount = 100; + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + + const oracleResponse = { + ber: Ethers.utils.parseEther("0.0005"), + ter, + dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), + expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, + fromDomainID: originDomainID, + toDomainID: destinationDomainID, + resourceID, + msgGasLimit, + }; + + const oracle2 = new Ethers.Wallet.createRandom(); + + const feeData = Helpers.createOracleFeeData( + oracleResponse, + oracle2.privateKey, + tokenAmount + ); + await TruffleAssert.reverts( + FeeHandlerRouterInstance.calculateFee( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ), + "Invalid signature" + ); + }); + + it("should not calculate fee if oracle data are outdated", async () => { + const gasUsed = 100000; + const feePercent = 500; + await FeeHandlerGenericInstance.setFeeProperties(gasUsed, feePercent); + + const tokenAmount = Ethers.utils.parseEther("1"); + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + const oracleResponse = { + ber: Ethers.utils.parseEther("0.000533"), + ter, + dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), + expiresAt: Math.floor(new Date().valueOf() / 1000) - 500, + fromDomainID: originDomainID, + toDomainID: destinationDomainID, + resourceID, + msgGasLimit, + }; + const feeData = Helpers.createOracleFeeData( + oracleResponse, + oracle.privateKey, + tokenAmount + ); + await TruffleAssert.reverts( + FeeHandlerRouterInstance.calculateFee( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ), + "Obsolete oracle data" + ); + }); + + it("should not calculate amount of fee if msgGasLimit == 0", async () => { + const tokenAmount = 100; + const depositData = Helpers.createERCDepositData( + tokenAmount, + 20, + recipientAddress + ); + + const oracleResponse = { + ber: Ethers.utils.parseEther("0.000533"), + ter, + dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), + expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, + fromDomainID: originDomainID, + toDomainID: destinationDomainID, + resourceID, + msgGasLimit: 0, + }; + + const feeData = Helpers.createOracleFeeData( + oracleResponse, + oracle.privateKey, + tokenAmount + ); + await TruffleAssert.reverts( + FeeHandlerRouterInstance.calculateFee( + sender, + originDomainID, + destinationDomainID, + resourceID, + depositData, + feeData + ), + "msgGasLimit == 0" + ); + }); +}); diff --git a/test/handlers/fee/withOracle/calculateFeeSubstrate.js b/test/handlers/fee/withOracle/calculateFeeSubstrate.js index 7cf11701..7c42e823 100644 --- a/test/handlers/fee/withOracle/calculateFeeSubstrate.js +++ b/test/handlers/fee/withOracle/calculateFeeSubstrate.js @@ -131,7 +131,7 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { depositData, feeData ); - assert.equal(Ethers.utils.formatUnits(res.fee.toString(), "wei"), "49180200000"); + assert.equal(res.fee.toString(), "49180200000"); assert.equal(res.tokenAddress, ERC20MintableInstance.address); }); From f536fd9502a6052969ce3187a96ee98e3c26fc99 Mon Sep 17 00:00:00 2001 From: Tanya Bushenyova Date: Mon, 13 Feb 2023 14:18:16 +0200 Subject: [PATCH 3/4] Fix formatting Co-authored-by: Oleksii Matiiasevych --- test/handlers/fee/withOracle/calculateFee.js | 2 +- .../fee/withOracle/calculateFeeGeneric.js | 2 +- .../fee/withOracle/calculateFeeSubstrate.js | 42 +++++++++---------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/test/handlers/fee/withOracle/calculateFee.js b/test/handlers/fee/withOracle/calculateFee.js index 53e80cad..0af768b1 100644 --- a/test/handlers/fee/withOracle/calculateFee.js +++ b/test/handlers/fee/withOracle/calculateFee.js @@ -50,7 +50,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { amount: uint256 total: 353 - */ + */ beforeEach(async () => { await Promise.all([ diff --git a/test/handlers/fee/withOracle/calculateFeeGeneric.js b/test/handlers/fee/withOracle/calculateFeeGeneric.js index db7dc0f0..87dad2b8 100644 --- a/test/handlers/fee/withOracle/calculateFeeGeneric.js +++ b/test/handlers/fee/withOracle/calculateFeeGeneric.js @@ -50,7 +50,7 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { amount: uint256 total: 353 - */ + */ beforeEach(async () => { await Promise.all([ diff --git a/test/handlers/fee/withOracle/calculateFeeSubstrate.js b/test/handlers/fee/withOracle/calculateFeeSubstrate.js index 7c42e823..2311e41a 100644 --- a/test/handlers/fee/withOracle/calculateFeeSubstrate.js +++ b/test/handlers/fee/withOracle/calculateFeeSubstrate.js @@ -30,27 +30,27 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { let resourceID; let FeeHandlerRouterInstance; - /** - Message: - ber * 10^18: uint256 (not used) - ter * 10^18: uint256 - finalFee: uint256 - expiresAt: uint256 - fromDomainID: uint8 encoded as uint256 - toDomainID: uint8 encoded as uint256 - resourceID: bytes32 - msgGasLimit: uint256 (not used) - sig: bytes(65 bytes) - - total in bytes: - message: - 32 * 8 = 256 - message + sig: - 256 + 65 = 321 - - amount: uint256 - total: 353 - */ + /** + Message: + ber * 10^18: uint256 (not used) + ter * 10^18: uint256 + finalFee: uint256 + expiresAt: uint256 + fromDomainID: uint8 encoded as uint256 + toDomainID: uint8 encoded as uint256 + resourceID: bytes32 + msgGasLimit: uint256 (not used) + sig: bytes(65 bytes) + + total in bytes: + message: + 32 * 8 = 256 + message + sig: + 256 + 65 = 321 + + amount: uint256 + total: 353 + */ beforeEach(async () => { await Promise.all([ From f8e341af7088e20aee1ebec9eea97b719988801b Mon Sep 17 00:00:00 2001 From: viatrix Date: Thu, 16 Feb 2023 22:11:29 +0200 Subject: [PATCH 4/4] Update txCost calc, rename fee handlers --- .../fee/DynamicERC20FeeHandlerEVM.sol | 96 +++++++++++++++++++ ...ol => DynamicERC20FeeHandlerSubstrate.sol} | 18 ++-- ...erWithOracle.sol => DynamicFeeHandler.sol} | 70 ++------------ ...ic.sol => DynamicGenericFeeHandlerEVM.sol} | 22 ++--- migrations/2_deploy_contracts.js | 18 ++-- .../3_deploy_permissionlessGenericHandler.js | 8 +- migrations/4_deploy_xc20_contracts.js | 8 +- migrations/5_renounceAdmin.js | 8 +- migrations/utils.js | 4 +- package.json | 2 +- test/handlers/fee/basic/admin.js | 8 +- .../fee/{withOracle => dynamic}/admin.js | 46 ++++----- .../calculateFeeERC20EVM.js} | 37 +++---- .../calculateFeeERC20Substrate.js} | 33 +++---- .../calculateFeeGenericEVM.js} | 80 +++++----------- .../fee/{withOracle => dynamic}/collectFee.js | 42 ++++---- .../{withOracle => dynamic}/distributeFee.js | 34 +++---- test/handlers/fee/handlerRouter.js | 4 +- 18 files changed, 274 insertions(+), 264 deletions(-) create mode 100644 contracts/handlers/fee/DynamicERC20FeeHandlerEVM.sol rename contracts/handlers/fee/{FeeHandlerSubstrate.sol => DynamicERC20FeeHandlerSubstrate.sol} (87%) rename contracts/handlers/fee/{FeeHandlerWithOracle.sol => DynamicFeeHandler.sol} (72%) rename contracts/handlers/fee/{FeeHandlerGeneric.sol => DynamicGenericFeeHandlerEVM.sol} (84%) rename test/handlers/fee/{withOracle => dynamic}/admin.js (58%) rename test/handlers/fee/{withOracle/calculateFee.js => dynamic/calculateFeeERC20EVM.js} (91%) rename test/handlers/fee/{withOracle/calculateFeeSubstrate.js => dynamic/calculateFeeERC20Substrate.js} (91%) rename test/handlers/fee/{withOracle/calculateFeeGeneric.js => dynamic/calculateFeeGenericEVM.js} (83%) rename test/handlers/fee/{withOracle => dynamic}/collectFee.js (89%) rename test/handlers/fee/{withOracle => dynamic}/distributeFee.js (87%) diff --git a/contracts/handlers/fee/DynamicERC20FeeHandlerEVM.sol b/contracts/handlers/fee/DynamicERC20FeeHandlerEVM.sol new file mode 100644 index 00000000..9db3a6d8 --- /dev/null +++ b/contracts/handlers/fee/DynamicERC20FeeHandlerEVM.sol @@ -0,0 +1,96 @@ +// The Licensed Work is (c) 2022 Sygma +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.11; + +import "./DynamicFeeHandler.sol"; + +/** + @title Handles deposit fees on Substrate based on Effective rates provided by Fee oracle. + @author ChainSafe Systems. + @notice This contract is intended to be used with the Bridge contract. + */ +contract DynamicERC20FeeHandlerEVM is DynamicFeeHandler { + + /** + @param bridgeAddress Contract address of previously deployed Bridge. + @param feeHandlerRouterAddress Contract address of previously deployed FeeHandlerRouter. + */ + constructor(address bridgeAddress, address feeHandlerRouterAddress) DynamicFeeHandler(bridgeAddress, feeHandlerRouterAddress) { + } + + /** + @notice Calculates fee for deposit for Substrate. + This function is almost identical to the _calculateFee function in the base contract. + The differences are: unpacking of the oracle message and the txCost calculation formula. + Oracle will do the calculation of the tx cost and provide the resulting fee to the contract. + The resulting calculation is: + txCost = finalFee * oracleMessage.ter / 1e18 + @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) internal view override returns(uint256 fee, address tokenAddress) { + /** + Message: + ber * 10^18: uint256 (not used) + ter * 10^18: uint256 + dstGasPrice: uint256 + expiresAt: uint256 + fromDomainID: uint8 encoded as uint256 + toDomainID: uint8 encoded as uint256 + resourceID: bytes32 + msgGasLimit: uint256 (not used) + sig: bytes(65 bytes) + + total in bytes: + message: + 32 * 8 = 256 + message + sig: + 256 + 65 = 321 + + amount: uint256 (not used) + total: 353 + */ + + require(feeData.length == 353, "Incorrect feeData length"); + + FeeDataType memory feeDataDecoded; + uint256 txCost; + + feeDataDecoded.message = bytes(feeData[: 256]); + feeDataDecoded.sig = bytes(feeData[256: 321]); + + OracleMessageType memory oracleMessage = abi.decode(feeDataDecoded.message, (OracleMessageType)); + require(block.timestamp <= oracleMessage.expiresAt, "Obsolete oracle data"); + require((oracleMessage.fromDomainID == fromDomainID) + && (oracleMessage.toDomainID == destinationDomainID) + && (oracleMessage.resourceID == resourceID), + "Incorrect deposit params" + ); + + bytes32 messageHash = keccak256(feeDataDecoded.message); + + verifySig(messageHash, feeDataDecoded.sig, _oracleAddress); + + address tokenHandler = IBridge(_bridgeAddress)._resourceIDToHandlerAddress(resourceID); + tokenAddress = IERCHandler(tokenHandler)._resourceIDToTokenContractAddress(resourceID); + + // txCost = dstGasPrice * _gasUsed * Token Effective Rate (rate of dest base currency to token) + txCost = oracleMessage.dstGasPrice * _gasUsed * oracleMessage.ter / 1e18; + + uint256 depositAmount; + (depositAmount) = abi.decode(depositData, (uint256)); + + fee = depositAmount * _feePercent / 1e4; // 100 for percent and 100 to avoid precision loss + + if (fee < txCost) { + fee = txCost; + } + return (fee, tokenAddress); + } +} diff --git a/contracts/handlers/fee/FeeHandlerSubstrate.sol b/contracts/handlers/fee/DynamicERC20FeeHandlerSubstrate.sol similarity index 87% rename from contracts/handlers/fee/FeeHandlerSubstrate.sol rename to contracts/handlers/fee/DynamicERC20FeeHandlerSubstrate.sol index 966b52c3..2f04b60a 100644 --- a/contracts/handlers/fee/FeeHandlerSubstrate.sol +++ b/contracts/handlers/fee/DynamicERC20FeeHandlerSubstrate.sol @@ -2,14 +2,14 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.11; -import "./FeeHandlerWithOracle.sol"; +import "./DynamicFeeHandler.sol"; /** @title Handles deposit fees on Substrate based on Effective rates provided by Fee oracle. @author ChainSafe Systems. @notice This contract is intended to be used with the Bridge contract. */ -contract FeeHandlerSubstrate is FeeHandlerWithOracle { +contract DynamicERC20FeeHandlerSubstrate is DynamicFeeHandler { struct SubstrateOracleMessageType { // Base Effective Rate - effective rate between base currencies of source and dest networks (eg. MATIC/ETH) @@ -29,7 +29,7 @@ contract FeeHandlerSubstrate is FeeHandlerWithOracle { @param bridgeAddress Contract address of previously deployed Bridge. @param feeHandlerRouterAddress Contract address of previously deployed FeeHandlerRouter. */ - constructor(address bridgeAddress, address feeHandlerRouterAddress) FeeHandlerWithOracle(bridgeAddress, feeHandlerRouterAddress) { + constructor(address bridgeAddress, address feeHandlerRouterAddress) DynamicFeeHandler(bridgeAddress, feeHandlerRouterAddress) { } /** @@ -43,7 +43,7 @@ contract FeeHandlerSubstrate is FeeHandlerWithOracle { @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 to be passed to specified handler. + @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. @@ -67,7 +67,7 @@ contract FeeHandlerSubstrate is FeeHandlerWithOracle { message + sig: 256 + 65 = 321 - amount: uint256 + amount: uint256 (not used) total: 353 */ @@ -78,7 +78,6 @@ contract FeeHandlerSubstrate is FeeHandlerWithOracle { feeDataDecoded.message = bytes(feeData[: 256]); feeDataDecoded.sig = bytes(feeData[256: 321]); - feeDataDecoded.amount = abi.decode(feeData[321:], (uint256)); SubstrateOracleMessageType memory oracleMessage = abi.decode(feeDataDecoded.message, (SubstrateOracleMessageType)); require(block.timestamp <= oracleMessage.expiresAt, "Obsolete oracle data"); @@ -93,11 +92,14 @@ contract FeeHandlerSubstrate is FeeHandlerWithOracle { verifySig(messageHash, feeDataDecoded.sig, _oracleAddress); address tokenHandler = IBridge(_bridgeAddress)._resourceIDToHandlerAddress(resourceID); - address tokenAddress = IERCHandler(tokenHandler)._resourceIDToTokenContractAddress(resourceID); + tokenAddress = IERCHandler(tokenHandler)._resourceIDToTokenContractAddress(resourceID); txCost = oracleMessage.finalFee * oracleMessage.ter / 1e18; - fee = feeDataDecoded.amount * _feePercent / 1e4; // 100 for percent and 100 to avoid precision loss + uint256 depositAmount; + (depositAmount) = abi.decode(depositData, (uint256)); + + fee = depositAmount * _feePercent / 1e4; // 100 for percent and 100 to avoid precision loss if (fee < txCost) { fee = txCost; diff --git a/contracts/handlers/fee/FeeHandlerWithOracle.sol b/contracts/handlers/fee/DynamicFeeHandler.sol similarity index 72% rename from contracts/handlers/fee/FeeHandlerWithOracle.sol rename to contracts/handlers/fee/DynamicFeeHandler.sol index 77b37806..759b20ee 100644 --- a/contracts/handlers/fee/FeeHandlerWithOracle.sol +++ b/contracts/handlers/fee/DynamicFeeHandler.sol @@ -15,7 +15,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @author ChainSafe Systems. @notice This contract is intended to be used with the Bridge contract. */ -contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe { +abstract contract DynamicFeeHandler is IFeeHandler, AccessControl, ERC20Safe { address public immutable _bridgeAddress; address public immutable _feeHandlerRouterAddress; @@ -40,7 +40,7 @@ contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe { struct FeeDataType { bytes message; bytes sig; - uint256 amount; + uint256 amount; // not used } modifier onlyAdmin() { @@ -64,7 +64,7 @@ contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe { @param bridgeAddress Contract address of previously deployed Bridge. @param feeHandlerRouterAddress Contract address of previously deployed FeeHandlerRouter. */ - constructor(address bridgeAddress, address feeHandlerRouterAddress) public { + constructor(address bridgeAddress, address feeHandlerRouterAddress) { _bridgeAddress = bridgeAddress; _feeHandlerRouterAddress = feeHandlerRouterAddress; _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); @@ -95,7 +95,8 @@ contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe { /** @notice Sets the fee properties. @param gasUsed Gas used for transfer. - @param feePercent Added to fee amount. total fee = fee_from_oracle.amount * feePercent / 1e4 + @param feePercent Percent of deposited amount taken as a fee. + fee = depositAmount * feePercent / 1e4 */ function setFeeProperties(uint32 gasUsed, uint16 feePercent) external onlyAdmin { _gasUsed = gasUsed; @@ -108,7 +109,7 @@ contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe { @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 to be passed to specified handler. + @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 onlyBridgeOrRouter { @@ -124,7 +125,7 @@ contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe { @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 to be passed to specified handler. + @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. @@ -134,61 +135,6 @@ contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe { } function _calculateFee(address sender, uint8 fromDomainID, uint8 destinationDomainID, bytes32 resourceID, bytes calldata depositData, bytes calldata feeData) internal view virtual returns(uint256 fee, address tokenAddress) { - /** - Message: - ber * 10^18: uint256 (not used) - ter * 10^18: uint256 - dstGasPrice: uint256 - expiresAt: uint256 - fromDomainID: uint8 encoded as uint256 - toDomainID: uint8 encoded as uint256 - resourceID: bytes32 - msgGasLimit: uint256 (not used) - sig: bytes(65 bytes) - - total in bytes: - message: - 32 * 8 = 256 - message + sig: - 256 + 65 = 321 - - amount: uint256 - total: 353 - */ - - require(feeData.length == 353, "Incorrect feeData length"); - - FeeDataType memory feeDataDecoded; - uint256 txCost; - - feeDataDecoded.message = bytes(feeData[: 256]); - feeDataDecoded.sig = bytes(feeData[256: 321]); - feeDataDecoded.amount = abi.decode(feeData[321:], (uint256)); - - OracleMessageType memory oracleMessage = abi.decode(feeDataDecoded.message, (OracleMessageType)); - require(block.timestamp <= oracleMessage.expiresAt, "Obsolete oracle data"); - require((oracleMessage.fromDomainID == fromDomainID) - && (oracleMessage.toDomainID == destinationDomainID) - && (oracleMessage.resourceID == resourceID), - "Incorrect deposit params" - ); - - bytes32 messageHash = keccak256(feeDataDecoded.message); - - verifySig(messageHash, feeDataDecoded.sig, _oracleAddress); - - address tokenHandler = IBridge(_bridgeAddress)._resourceIDToHandlerAddress(resourceID); - address tokenAddress = IERCHandler(tokenHandler)._resourceIDToTokenContractAddress(resourceID); - - // txCost = dstGasPrice * _gasUsed * Token Effective Rate (rate of dest base currency to token) - txCost = oracleMessage.dstGasPrice * _gasUsed * oracleMessage.ter / 1e18; - - fee = feeDataDecoded.amount * _feePercent / 1e4; // 100 for percent and 100 to avoid precision loss - - if (fee < txCost) { - fee = txCost; - } - return (fee, tokenAddress); } /** @@ -208,7 +154,7 @@ contract FeeHandlerWithOracle is IFeeHandler, AccessControl, ERC20Safe { } } - function verifySig(bytes32 message, bytes memory signature, address signerAddress) internal view { + function verifySig(bytes32 message, bytes memory signature, address signerAddress) internal pure { address signerAddressRecovered = ECDSA.recover(message, signature); require(signerAddressRecovered == signerAddress, 'Invalid signature'); } diff --git a/contracts/handlers/fee/FeeHandlerGeneric.sol b/contracts/handlers/fee/DynamicGenericFeeHandlerEVM.sol similarity index 84% rename from contracts/handlers/fee/FeeHandlerGeneric.sol rename to contracts/handlers/fee/DynamicGenericFeeHandlerEVM.sol index d9c4db30..cf66f43e 100644 --- a/contracts/handlers/fee/FeeHandlerGeneric.sol +++ b/contracts/handlers/fee/DynamicGenericFeeHandlerEVM.sol @@ -2,20 +2,20 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.11; -import "./FeeHandlerWithOracle.sol"; +import "./DynamicFeeHandler.sol"; /** @title Handles deposit fees for generic messages based on Effective rates provided by Fee oracle. @author ChainSafe Systems. @notice This contract is intended to be used with the Bridge contract. */ -contract FeeHandlerGeneric is FeeHandlerWithOracle { +contract DynamicGenericFeeHandlerEVM is DynamicFeeHandler { /** @param bridgeAddress Contract address of previously deployed Bridge. @param feeHandlerRouterAddress Contract address of previously deployed FeeHandlerRouter. */ - constructor(address bridgeAddress, address feeHandlerRouterAddress) FeeHandlerWithOracle(bridgeAddress, feeHandlerRouterAddress) { + constructor(address bridgeAddress, address feeHandlerRouterAddress) DynamicFeeHandler(bridgeAddress, feeHandlerRouterAddress) { } /** @@ -27,7 +27,7 @@ contract FeeHandlerGeneric is FeeHandlerWithOracle { @param destinationDomainID ID of chain deposit will be bridged to. @param resourceID ResourceID to be used when making deposits. @param depositData Additional data to be passed to specified handler. - @param feeData Additional data to be passed to the fee handler. + @param feeData Additional data about the deposit. @return fee Returns the fee amount. @return tokenAddress Returns the address of the token to be used for fee. */ @@ -50,7 +50,7 @@ contract FeeHandlerGeneric is FeeHandlerWithOracle { message + sig: 256 + 65 = 321 - amount: uint256 + amount: uint256 (not used) total: 353 */ @@ -61,7 +61,6 @@ contract FeeHandlerGeneric is FeeHandlerWithOracle { feeDataDecoded.message = bytes(feeData[: 256]); feeDataDecoded.sig = bytes(feeData[256: 321]); - feeDataDecoded.amount = abi.decode(feeData[321:], (uint256)); OracleMessageType memory oracleMessage = abi.decode(feeDataDecoded.message, (OracleMessageType)); require(block.timestamp <= oracleMessage.expiresAt, "Obsolete oracle data"); @@ -69,24 +68,21 @@ contract FeeHandlerGeneric is FeeHandlerWithOracle { && (oracleMessage.toDomainID == destinationDomainID) && (oracleMessage.resourceID == resourceID), "Incorrect deposit params" - ); + ); + require(oracleMessage.msgGasLimit > 0, "msgGasLimit == 0"); bytes32 messageHash = keccak256(feeDataDecoded.message); verifySig(messageHash, feeDataDecoded.sig, _oracleAddress); address tokenHandler = IBridge(_bridgeAddress)._resourceIDToHandlerAddress(resourceID); - address tokenAddress = IERCHandler(tokenHandler)._resourceIDToTokenContractAddress(resourceID); + tokenAddress = IERCHandler(tokenHandler)._resourceIDToTokenContractAddress(resourceID); - require(oracleMessage.msgGasLimit > 0, "msgGasLimit == 0"); // txCost = dstGasPrice * oracleMessage.msgGasLimit * Base Effective Rate (rate between base currencies of source and dest) txCost = oracleMessage.dstGasPrice * oracleMessage.msgGasLimit * oracleMessage.ber / 1e18; - fee = feeDataDecoded.amount * _feePercent / 1e4; // 100 for percent and 100 to avoid precision loss + fee = txCost + txCost * _feePercent / 1e4; // 100 for percent and 100 to avoid precision loss - if (fee < txCost) { - fee = txCost; - } return (fee, tokenAddress); } } diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index 10fc1dcd..6ced75b9 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -20,7 +20,7 @@ const PermissionedGenericHandlerContract = artifacts.require( ); const FeeRouterContract = artifacts.require("FeeHandlerRouter"); const BasicFeeHandlerContract = artifacts.require("BasicFeeHandler"); -const FeeHandlerWithOracleContract = artifacts.require("FeeHandlerWithOracle"); +const DynamicFeeHandlerContract = artifacts.require("DynamicERC20FeeHandlerEVM"); module.exports = async function (deployer, network) { const networksConfig = Utils.getNetworksConfig(); @@ -75,18 +75,18 @@ module.exports = async function (deployer, network) { bridgeInstance.address, feeRouterInstance.address ); - const feeHandlerWithOracleInstance = await deployer.deploy( - FeeHandlerWithOracleContract, + const dynamicFeeHandlerInstance = await deployer.deploy( + DynamicFeeHandlerContract, bridgeInstance.address, feeRouterInstance.address ); // setup fee router and fee handlers await bridgeInstance.adminChangeFeeHandler(feeRouterInstance.address); - await feeHandlerWithOracleInstance.setFeeOracle( + await dynamicFeeHandlerInstance.setFeeOracle( currentNetworkConfig.fee.oracle.address ); - await feeHandlerWithOracleInstance.setFeeProperties( + await dynamicFeeHandlerInstance.setFeeProperties( currentNetworkConfig.fee.oracle.gasUsed, currentNetworkConfig.fee.oracle.feePercentage ); @@ -104,7 +104,7 @@ module.exports = async function (deployer, network) { permissionedGenericHandlerInstance.address, "FeeRouterContract Address": feeRouterInstance.address, "BasicFeeHandler Address": basicFeeHandlerInstance.address, - "FeeHandlerWithOracle Address": feeHandlerWithOracleInstance.address, + "DynamicFeeHandler Address": dynamicFeeHandlerInstance.address, }); // setup erc20 tokens @@ -118,7 +118,7 @@ module.exports = async function (deployer, network) { await Utils.setupFee( networksConfig, feeRouterInstance, - feeHandlerWithOracleInstance, + dynamicFeeHandlerInstance, basicFeeHandlerInstance, erc20 ); @@ -144,7 +144,7 @@ module.exports = async function (deployer, network) { await Utils.setupFee( networksConfig, feeRouterInstance, - feeHandlerWithOracleInstance, + dynamicFeeHandlerInstance, basicFeeHandlerInstance, erc721 ); @@ -169,7 +169,7 @@ module.exports = async function (deployer, network) { await Utils.setupFee( networksConfig, feeRouterInstance, - feeHandlerWithOracleInstance, + dynamicFeeHandlerInstance, basicFeeHandlerInstance, generic ); diff --git a/migrations/3_deploy_permissionlessGenericHandler.js b/migrations/3_deploy_permissionlessGenericHandler.js index 46444954..d463e4af 100644 --- a/migrations/3_deploy_permissionlessGenericHandler.js +++ b/migrations/3_deploy_permissionlessGenericHandler.js @@ -12,7 +12,7 @@ const PermissionlessGenericHandlerContract = artifacts.require( ); const FeeRouterContract = artifacts.require("FeeHandlerRouter"); const BasicFeeHandlerContract = artifacts.require("BasicFeeHandler"); -const FeeHandlerWithOracleContract = artifacts.require("FeeHandlerWithOracle"); +const DynamicFeeHandlerContract = artifacts.require("DynamicERC20FeeHandlerEVM"); module.exports = async function (deployer, network) { const networksConfig = Utils.getNetworksConfig(); @@ -25,8 +25,8 @@ module.exports = async function (deployer, network) { const bridgeInstance = await BridgeContract.deployed(); const feeRouterInstance = await FeeRouterContract.deployed(); const basicFeeHandlerInstance = await BasicFeeHandlerContract.deployed(); - const feeHandlerWithOracleInstance = - await FeeHandlerWithOracleContract.deployed(); + const dynamicFeeHandlerInstance = + await DynamicFeeHandlerContract.deployed(); // deploy generic handler const permissionlessGenericHandlerInstance = await deployer.deploy( @@ -71,7 +71,7 @@ module.exports = async function (deployer, network) { await Utils.setupFee( networksConfig, feeRouterInstance, - feeHandlerWithOracleInstance, + dynamicFeeHandlerInstance, basicFeeHandlerInstance, currentNetworkConfig.permissionlessGeneric ); diff --git a/migrations/4_deploy_xc20_contracts.js b/migrations/4_deploy_xc20_contracts.js index 2ef12e90..05220190 100644 --- a/migrations/4_deploy_xc20_contracts.js +++ b/migrations/4_deploy_xc20_contracts.js @@ -9,7 +9,7 @@ const BridgeContract = artifacts.require("Bridge"); const XC20HandlerContract = artifacts.require("XC20Handler"); const FeeRouterContract = artifacts.require("FeeHandlerRouter"); const BasicFeeHandlerContract = artifacts.require("BasicFeeHandler"); -const FeeHandlerWithOracleContract = artifacts.require("FeeHandlerWithOracle"); +const DynamicFeeHandlerContract = artifacts.require("DynamicERC20FeeHandlerEVM"); module.exports = async function (deployer, network) { // trim suffix from network name and fetch current network config @@ -29,8 +29,8 @@ module.exports = async function (deployer, network) { const bridgeInstance = await BridgeContract.deployed(); const feeRouterInstance = await FeeRouterContract.deployed(); const basicFeeHandlerInstance = await BasicFeeHandlerContract.deployed(); - const feeHandlerWithOracleInstance = - await FeeHandlerWithOracleContract.deployed(); + const dynamicFeeHandlerInstance = + await DynamicFeeHandlerContract.deployed(); // deploy XC20 contracts await deployer.deploy(XC20HandlerContract, bridgeInstance.address); @@ -42,7 +42,7 @@ module.exports = async function (deployer, network) { await Utils.setupFee( networksConfig, feeRouterInstance, - feeHandlerWithOracleInstance, + dynamicFeeHandlerInstance, basicFeeHandlerInstance, xc20 ); diff --git a/migrations/5_renounceAdmin.js b/migrations/5_renounceAdmin.js index 65499301..67db6ad7 100644 --- a/migrations/5_renounceAdmin.js +++ b/migrations/5_renounceAdmin.js @@ -10,7 +10,7 @@ const AccessControlSegregatorContract = artifacts.require( ); const FeeRouterContract = artifacts.require("FeeHandlerRouter"); const BasicFeeHandlerContract = artifacts.require("BasicFeeHandler"); -const FeeHandlerWithOracleContract = artifacts.require("FeeHandlerWithOracle"); +const DynamicFeeHandlerContract = artifacts.require("DynamicERC20FeeHandlerEVM"); module.exports = async function (deployer, network) { const networksConfig = Utils.getNetworksConfig(); @@ -23,8 +23,8 @@ module.exports = async function (deployer, network) { await AccessControlSegregatorContract.deployed(); const feeRouterInstance = await FeeRouterContract.deployed(); const basicFeeHandlerInstance = await BasicFeeHandlerContract.deployed(); - const feeHandlerWithOracleInstance = - await FeeHandlerWithOracleContract.deployed(); + const dynamicFeeHandlerInstance = + await DynamicFeeHandlerContract.deployed(); if (currentNetworkConfig.access.feeHandlerAdmin) { console.log( @@ -35,7 +35,7 @@ module.exports = async function (deployer, network) { await basicFeeHandlerInstance.renounceAdmin( currentNetworkConfig.access.feeHandlerAdmin ); - await feeHandlerWithOracleInstance.renounceAdmin( + await dynamicFeeHandlerInstance.renounceAdmin( currentNetworkConfig.access.feeHandlerAdmin ); } diff --git a/migrations/utils.js b/migrations/utils.js index 545de8ac..071d14c0 100644 --- a/migrations/utils.js +++ b/migrations/utils.js @@ -24,7 +24,7 @@ function getNetworksConfig() { async function setupFee( networksConfig, feeRouterInstance, - feeHandlerWithOracleInstance, + dynamicFeeHandlerInstance, basicFeeHandlerInstance, token ) { @@ -33,7 +33,7 @@ async function setupFee( await feeRouterInstance.adminSetResourceHandler( network.domainID, token.resourceID, - feeHandlerWithOracleInstance.address + dynamicFeeHandlerInstance.address ); } else { await feeRouterInstance.adminSetResourceHandler( diff --git a/package.json b/package.json index 7e1ff929..e12a3420 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "build/contracts/PermissionedGenericHandler.json", "build/contracts/PermissionlessGenericHandler.json", "build/contracts/BasicFeeHandler.json", - "build/contracts/FeeHandlerWithOracle.json", + "build/contracts/DynamicERC20FeeHandlerEVM.json", "contracts/interfaces" ], "directories": { diff --git a/test/handlers/fee/basic/admin.js b/test/handlers/fee/basic/admin.js index c0793040..7d9cc08c 100644 --- a/test/handlers/fee/basic/admin.js +++ b/test/handlers/fee/basic/admin.js @@ -54,8 +54,8 @@ contract("BasicFeeHandler - [admin]", async (accounts) => { await assertOnlyAdmin(BasicFeeHandlerInstance.changeFee, fee); }); - it("FeeHandlerWithOracle admin should be changed to expectedFeeHandlerWithOracleAdmin", async () => { - const expectedFeeHandlerWithOracleAdmin = accounts[1]; + it("BasicFeeHandler admin should be changed to expectedBasicFeeHandlerAdmin", async () => { + const expectedBasicFeeHandlerAdmin = accounts[1]; // check current admin assert.isTrue( @@ -63,12 +63,12 @@ contract("BasicFeeHandler - [admin]", async (accounts) => { ); await TruffleAssert.passes( - BasicFeeHandlerInstance.renounceAdmin(expectedFeeHandlerWithOracleAdmin) + BasicFeeHandlerInstance.renounceAdmin(expectedBasicFeeHandlerAdmin) ); assert.isTrue( await BasicFeeHandlerInstance.hasRole( ADMIN_ROLE, - expectedFeeHandlerWithOracleAdmin + expectedBasicFeeHandlerAdmin ) ); diff --git a/test/handlers/fee/withOracle/admin.js b/test/handlers/fee/dynamic/admin.js similarity index 58% rename from test/handlers/fee/withOracle/admin.js rename to test/handlers/fee/dynamic/admin.js index 0b22d55c..90e651c0 100644 --- a/test/handlers/fee/withOracle/admin.js +++ b/test/handlers/fee/dynamic/admin.js @@ -7,10 +7,10 @@ const TruffleAssert = require("truffle-assertions"); const Helpers = require("../../../helpers"); -const FeeHandlerWithOracleContract = artifacts.require("FeeHandlerWithOracle"); +const DynamicFeeHandlerContract = artifacts.require("DynamicERC20FeeHandlerEVM"); const FeeHandlerRouterContract = artifacts.require("FeeHandlerRouter"); -contract("FeeHandlerWithOracle - [admin]", async (accounts) => { +contract("DynamicFeeHandler - [admin]", async (accounts) => { const domainID = 1; const initialRelayers = accounts.slice(0, 3); const currentFeeHandlerAdmin = accounts[0]; @@ -23,7 +23,7 @@ contract("FeeHandlerWithOracle - [admin]", async (accounts) => { }; let BridgeInstance; - let FeeHandlerWithOracleInstance; + let DynamicFeeHandlerInstance; let FeeHandlerRouterInstance; let ADMIN_ROLE; @@ -32,28 +32,28 @@ contract("FeeHandlerWithOracle - [admin]", async (accounts) => { FeeHandlerRouterInstance = await FeeHandlerRouterContract.new( BridgeInstance.address ); - FeeHandlerWithOracleInstance = await FeeHandlerWithOracleContract.new( + DynamicFeeHandlerInstance = await DynamicFeeHandlerContract.new( BridgeInstance.address, FeeHandlerRouterInstance.address ); - ADMIN_ROLE = await FeeHandlerWithOracleInstance.DEFAULT_ADMIN_ROLE(); + ADMIN_ROLE = await DynamicFeeHandlerInstance.DEFAULT_ADMIN_ROLE(); }); it("should set fee oracle", async () => { const oracleAddress = accounts[1]; assert.equal( - await FeeHandlerWithOracleInstance._oracleAddress.call(), + await DynamicFeeHandlerInstance._oracleAddress.call(), "0x0000000000000000000000000000000000000000" ); - await FeeHandlerWithOracleInstance.setFeeOracle(oracleAddress); - const newOracle = await FeeHandlerWithOracleInstance._oracleAddress.call(); + await DynamicFeeHandlerInstance.setFeeOracle(oracleAddress); + const newOracle = await DynamicFeeHandlerInstance._oracleAddress.call(); assert.equal(newOracle, oracleAddress); }); it("should require admin role to change fee oracle", async () => { const oracleAddress = accounts[1]; await assertOnlyAdmin( - FeeHandlerWithOracleInstance.setFeeOracle, + DynamicFeeHandlerInstance.setFeeOracle, oracleAddress ); }); @@ -61,12 +61,12 @@ contract("FeeHandlerWithOracle - [admin]", async (accounts) => { it("should set fee properties", async () => { const gasUsed = 100000; const feePercent = 5; - assert.equal(await FeeHandlerWithOracleInstance._gasUsed.call(), "0"); - assert.equal(await FeeHandlerWithOracleInstance._feePercent.call(), "0"); - await FeeHandlerWithOracleInstance.setFeeProperties(gasUsed, feePercent); - assert.equal(await FeeHandlerWithOracleInstance._gasUsed.call(), gasUsed); + assert.equal(await DynamicFeeHandlerInstance._gasUsed.call(), "0"); + assert.equal(await DynamicFeeHandlerInstance._feePercent.call(), "0"); + await DynamicFeeHandlerInstance.setFeeProperties(gasUsed, feePercent); + assert.equal(await DynamicFeeHandlerInstance._gasUsed.call(), gasUsed); assert.equal( - await FeeHandlerWithOracleInstance._feePercent.call(), + await DynamicFeeHandlerInstance._feePercent.call(), feePercent ); }); @@ -75,38 +75,38 @@ contract("FeeHandlerWithOracle - [admin]", async (accounts) => { const gasUsed = 100000; const feePercent = 5; await assertOnlyAdmin( - FeeHandlerWithOracleInstance.setFeeProperties, + DynamicFeeHandlerInstance.setFeeProperties, gasUsed, feePercent ); }); - it("FeeHandlerWithOracle admin should be changed to expectedFeeHandlerWithOracleAdmin", async () => { - const expectedFeeHandlerWithOracleAdmin = accounts[1]; + it("DynamicFeeHandler admin should be changed to expectedDynamicFeeHandlerAdmin", async () => { + const expectedDynamicFeeHandlerAdmin = accounts[1]; // check current admin assert.isTrue( - await FeeHandlerWithOracleInstance.hasRole( + await DynamicFeeHandlerInstance.hasRole( ADMIN_ROLE, currentFeeHandlerAdmin ) ); await TruffleAssert.passes( - FeeHandlerWithOracleInstance.renounceAdmin( - expectedFeeHandlerWithOracleAdmin + DynamicFeeHandlerInstance.renounceAdmin( + expectedDynamicFeeHandlerAdmin ) ); assert.isTrue( - await FeeHandlerWithOracleInstance.hasRole( + await DynamicFeeHandlerInstance.hasRole( ADMIN_ROLE, - expectedFeeHandlerWithOracleAdmin + expectedDynamicFeeHandlerAdmin ) ); // check that former admin is no longer admin assert.isFalse( - await FeeHandlerWithOracleInstance.hasRole( + await DynamicFeeHandlerInstance.hasRole( ADMIN_ROLE, currentFeeHandlerAdmin ) diff --git a/test/handlers/fee/withOracle/calculateFee.js b/test/handlers/fee/dynamic/calculateFeeERC20EVM.js similarity index 91% rename from test/handlers/fee/withOracle/calculateFee.js rename to test/handlers/fee/dynamic/calculateFeeERC20EVM.js index 0af768b1..f3fdadb8 100644 --- a/test/handlers/fee/withOracle/calculateFee.js +++ b/test/handlers/fee/dynamic/calculateFeeERC20EVM.js @@ -10,10 +10,10 @@ const Helpers = require("../../../helpers"); const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); const ERC20HandlerContract = artifacts.require("ERC20Handler"); -const FeeHandlerWithOracleContract = artifacts.require("FeeHandlerWithOracle"); +const DynamicERC20FeeHandlerEVMContract = artifacts.require("DynamicERC20FeeHandlerEVM"); const FeeHandlerRouterContract = artifacts.require("FeeHandlerRouter"); -contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { +contract("DynamicERC20FeeHandlerEVM - [calculateFee]", async (accounts) => { const originDomainID = 1; const destinationDomainID = 1; const oracle = new Ethers.Wallet.createRandom(); @@ -22,11 +22,12 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { const gasUsed = 100000; const feePercent = 500; const emptySetResourceData = "0x"; - const msgGasLimit = 0; - const ber = 0; + const msgGasLimit = 0; // Not used + const ber = 0; // Not used + const feeDataAmount = 0; // Not used let BridgeInstance; - let FeeHandlerWithOracleInstance; + let DynamicERC20FeeHandlerEVMInstance; let resourceID; let FeeHandlerRouterInstance; @@ -48,7 +49,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { message + sig: 256 + 65 = 321 - amount: uint256 + amount: uint256 (not used) total: 353 */ @@ -70,7 +71,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { FeeHandlerRouterInstance = await FeeHandlerRouterContract.new( BridgeInstance.address ); - FeeHandlerWithOracleInstance = await FeeHandlerWithOracleContract.new( + DynamicERC20FeeHandlerEVMInstance = await DynamicERC20FeeHandlerEVMContract.new( BridgeInstance.address, FeeHandlerRouterInstance.address ); @@ -81,8 +82,8 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { ); await Promise.all([ - FeeHandlerWithOracleInstance.setFeeOracle(oracle.address), - FeeHandlerWithOracleInstance.setFeeProperties(gasUsed, feePercent), + DynamicERC20FeeHandlerEVMInstance.setFeeOracle(oracle.address), + DynamicERC20FeeHandlerEVMInstance.setFeeProperties(gasUsed, feePercent), BridgeInstance.adminSetResource( ERC20HandlerInstance.address, resourceID, @@ -93,7 +94,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { FeeHandlerRouterInstance.adminSetResourceHandler( destinationDomainID, resourceID, - FeeHandlerWithOracleInstance.address + DynamicERC20FeeHandlerEVMInstance.address ), ]); }); @@ -120,7 +121,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); const res = await FeeHandlerRouterInstance.calculateFee.call( sender, @@ -155,7 +156,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); const res = await FeeHandlerRouterInstance.calculateFee.call( sender, @@ -191,7 +192,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); const res = await FeeHandlerRouterInstance.calculateFee.call( sender, @@ -228,7 +229,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ) + "11"; await TruffleAssert.reverts( FeeHandlerRouterInstance.calculateFee( @@ -266,7 +267,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); await TruffleAssert.reverts( FeeHandlerRouterInstance.calculateFee( @@ -305,7 +306,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle2.privateKey, - tokenAmount + feeDataAmount ); await TruffleAssert.reverts( FeeHandlerRouterInstance.calculateFee( @@ -323,7 +324,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { it("should not calculate fee if oracle data are outdated", async () => { const gasUsed = 100000; const feePercent = 500; - await FeeHandlerWithOracleInstance.setFeeProperties(gasUsed, feePercent); + await DynamicERC20FeeHandlerEVMInstance.setFeeProperties(gasUsed, feePercent); const tokenAmount = Ethers.utils.parseEther("1"); const depositData = Helpers.createERCDepositData( @@ -344,7 +345,7 @@ contract("FeeHandlerWithOracle - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); await TruffleAssert.reverts( FeeHandlerRouterInstance.calculateFee( diff --git a/test/handlers/fee/withOracle/calculateFeeSubstrate.js b/test/handlers/fee/dynamic/calculateFeeERC20Substrate.js similarity index 91% rename from test/handlers/fee/withOracle/calculateFeeSubstrate.js rename to test/handlers/fee/dynamic/calculateFeeERC20Substrate.js index 2311e41a..fc4eced0 100644 --- a/test/handlers/fee/withOracle/calculateFeeSubstrate.js +++ b/test/handlers/fee/dynamic/calculateFeeERC20Substrate.js @@ -10,10 +10,10 @@ const Helpers = require("../../../helpers"); const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); const ERC20HandlerContract = artifacts.require("ERC20Handler"); -const FeeHandlerSubstrateContract = artifacts.require("FeeHandlerSubstrate"); +const DynamicERC20FeeHandlerSubstrateContract = artifacts.require("DynamicERC20FeeHandlerSubstrate"); const FeeHandlerRouterContract = artifacts.require("FeeHandlerRouter"); -contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { +contract("DynamicERC20FeeHandlerSubstrate - [calculateFee]", async (accounts) => { const originDomainID = 1; const destinationDomainID = 1; const oracle = new Ethers.Wallet.createRandom(); @@ -24,9 +24,10 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { const emptySetResourceData = "0x"; const msgGasLimit = 0; const ber = 0; + const feeDataAmount = 0; // Not used let BridgeInstance; - let FeeHandlerSubstrateInstance; + let DynamicERC20FeeHandlerSubstrateInstance; let resourceID; let FeeHandlerRouterInstance; @@ -48,7 +49,7 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { message + sig: 256 + 65 = 321 - amount: uint256 + amount: uint256 (not used) total: 353 */ @@ -70,7 +71,7 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { FeeHandlerRouterInstance = await FeeHandlerRouterContract.new( BridgeInstance.address ); - FeeHandlerSubstrateInstance = await FeeHandlerSubstrateContract.new( + DynamicERC20FeeHandlerSubstrateInstance = await DynamicERC20FeeHandlerSubstrateContract.new( BridgeInstance.address, FeeHandlerRouterInstance.address ); @@ -81,8 +82,8 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { ); await Promise.all([ - FeeHandlerSubstrateInstance.setFeeOracle(oracle.address), - FeeHandlerSubstrateInstance.setFeeProperties(gasUsed, feePercent), + DynamicERC20FeeHandlerSubstrateInstance.setFeeOracle(oracle.address), + DynamicERC20FeeHandlerSubstrateInstance.setFeeProperties(gasUsed, feePercent), BridgeInstance.adminSetResource( ERC20HandlerInstance.address, resourceID, @@ -93,7 +94,7 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { FeeHandlerRouterInstance.adminSetResourceHandler( destinationDomainID, resourceID, - FeeHandlerSubstrateInstance.address + DynamicERC20FeeHandlerSubstrateInstance.address ), ]); }); @@ -121,7 +122,7 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); const res = await FeeHandlerRouterInstance.calculateFee.call( sender, @@ -157,7 +158,7 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); const res = await FeeHandlerRouterInstance.calculateFee.call( sender, @@ -194,7 +195,7 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); const res = await FeeHandlerRouterInstance.calculateFee.call( sender, @@ -232,7 +233,7 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ) + "11"; await TruffleAssert.reverts( FeeHandlerRouterInstance.calculateFee( @@ -271,7 +272,7 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); await TruffleAssert.reverts( FeeHandlerRouterInstance.calculateFee( @@ -311,7 +312,7 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle2.privateKey, - tokenAmount + feeDataAmount ); await TruffleAssert.reverts( FeeHandlerRouterInstance.calculateFee( @@ -329,7 +330,7 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { it("should not calculate fee if oracle data are outdated", async () => { const gasUsed = 100000; const feePercent = 500; - await FeeHandlerSubstrateInstance.setFeeProperties(gasUsed, feePercent); + await DynamicERC20FeeHandlerSubstrateInstance.setFeeProperties(gasUsed, feePercent); const tokenAmount = Ethers.utils.parseEther("1"); const depositData = Helpers.createERCDepositData( @@ -351,7 +352,7 @@ contract("FeeHandlerSubstrate - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); await TruffleAssert.reverts( FeeHandlerRouterInstance.calculateFee( diff --git a/test/handlers/fee/withOracle/calculateFeeGeneric.js b/test/handlers/fee/dynamic/calculateFeeGenericEVM.js similarity index 83% rename from test/handlers/fee/withOracle/calculateFeeGeneric.js rename to test/handlers/fee/dynamic/calculateFeeGenericEVM.js index 87dad2b8..56659b44 100644 --- a/test/handlers/fee/withOracle/calculateFeeGeneric.js +++ b/test/handlers/fee/dynamic/calculateFeeGenericEVM.js @@ -10,23 +10,24 @@ const Helpers = require("../../../helpers"); const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); const ERC20HandlerContract = artifacts.require("ERC20Handler"); -const FeeHandlerGenericContract = artifacts.require("FeeHandlerGeneric"); +const DynamicGenericFeeHandlerEVMContract = artifacts.require("DynamicGenericFeeHandlerEVM"); const FeeHandlerRouterContract = artifacts.require("FeeHandlerRouter"); -contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { +contract("DynamicGenericFeeHandlerEVM - [calculateFee]", async (accounts) => { const originDomainID = 1; const destinationDomainID = 1; const oracle = new Ethers.Wallet.createRandom(); const sender = accounts[0]; const recipientAddress = accounts[1]; const gasUsed = 100000; - const feePercent = 500; + const feePercent = 0; const emptySetResourceData = "0x"; const msgGasLimit = 2300000; - const ter = 0; + const ter = 0; // Not used + const feeDataAmount = 0; // Not used let BridgeInstance; - let FeeHandlerGenericInstance; + let DynamicGenericFeeHandlerEVMInstance; let resourceID; let FeeHandlerRouterInstance; @@ -48,7 +49,7 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { message + sig: 256 + 65 = 321 - amount: uint256 + amount: uint256 (not used) total: 353 */ @@ -70,7 +71,7 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { FeeHandlerRouterInstance = await FeeHandlerRouterContract.new( BridgeInstance.address ); - FeeHandlerGenericInstance = await FeeHandlerGenericContract.new( + DynamicGenericFeeHandlerEVMInstance = await DynamicGenericFeeHandlerEVMContract.new( BridgeInstance.address, FeeHandlerRouterInstance.address ); @@ -81,8 +82,8 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { ); await Promise.all([ - FeeHandlerGenericInstance.setFeeOracle(oracle.address), - FeeHandlerGenericInstance.setFeeProperties(gasUsed, feePercent), + DynamicGenericFeeHandlerEVMInstance.setFeeOracle(oracle.address), + DynamicGenericFeeHandlerEVMInstance.setFeeProperties(gasUsed, feePercent), BridgeInstance.adminSetResource( ERC20HandlerInstance.address, resourceID, @@ -93,7 +94,7 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { FeeHandlerRouterInstance.adminSetResourceHandler( destinationDomainID, resourceID, - FeeHandlerGenericInstance.address + DynamicGenericFeeHandlerEVMInstance.address ), ]); }); @@ -120,7 +121,7 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); const res = await FeeHandlerRouterInstance.calculateFee.call( sender, @@ -135,48 +136,15 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { }); it("should return percent fee", async () => { + const feePercent = 1000; // 10% + await DynamicGenericFeeHandlerEVMInstance.setFeeProperties(gasUsed, feePercent); + const msgGasLimit = 3000000; const tokenAmount = Ethers.utils.parseEther("1"); const depositData = Helpers.createERCDepositData( tokenAmount, 20, recipientAddress ); - const oracleResponse = { - ber: Ethers.utils.parseEther("0.000533"), - ter, - dstGasPrice: Ethers.utils.parseUnits("30000000000", "wei"), - expiresAt: Math.floor(new Date().valueOf() / 1000) + 500, - fromDomainID: originDomainID, - toDomainID: destinationDomainID, - resourceID, - msgGasLimit, - }; - - const feeData = Helpers.createOracleFeeData( - oracleResponse, - oracle.privateKey, - tokenAmount - ); - const res = await FeeHandlerRouterInstance.calculateFee.call( - sender, - originDomainID, - destinationDomainID, - resourceID, - depositData, - feeData - ); - assert.equal(web3.utils.fromWei(res.fee, "ether"), "0.05"); - assert.equal(res.tokenAddress, ERC20MintableInstance.address); - }); - - it("should return fee to cover tx cost if percent fee does not cover tx cost", async () => { - const tokenAmount = 100; - const depositData = Helpers.createERCDepositData( - tokenAmount, - 20, - recipientAddress - ); - const oracleResponse = { ber: Ethers.utils.parseEther("0.0005"), ter, @@ -185,13 +153,13 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { fromDomainID: originDomainID, toDomainID: destinationDomainID, resourceID, - msgGasLimit: Ethers.utils.parseUnits("10000000", "wei"), + msgGasLimit, }; const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); const res = await FeeHandlerRouterInstance.calculateFee.call( sender, @@ -201,7 +169,7 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { depositData, feeData ); - assert.equal(Ethers.utils.formatEther(res.fee.toString()), "0.00015"); + assert.equal(web3.utils.fromWei(res.fee, "ether"), "0.0000495"); assert.equal(res.tokenAddress, ERC20MintableInstance.address); }); @@ -228,7 +196,7 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ) + "11"; await TruffleAssert.reverts( FeeHandlerRouterInstance.calculateFee( @@ -266,7 +234,7 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); await TruffleAssert.reverts( FeeHandlerRouterInstance.calculateFee( @@ -305,7 +273,7 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle2.privateKey, - tokenAmount + feeDataAmount ); await TruffleAssert.reverts( FeeHandlerRouterInstance.calculateFee( @@ -323,7 +291,7 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { it("should not calculate fee if oracle data are outdated", async () => { const gasUsed = 100000; const feePercent = 500; - await FeeHandlerGenericInstance.setFeeProperties(gasUsed, feePercent); + await DynamicGenericFeeHandlerEVMInstance.setFeeProperties(gasUsed, feePercent); const tokenAmount = Ethers.utils.parseEther("1"); const depositData = Helpers.createERCDepositData( @@ -344,7 +312,7 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); await TruffleAssert.reverts( FeeHandlerRouterInstance.calculateFee( @@ -381,7 +349,7 @@ contract("FeeHandlerGeneric - [calculateFee]", async (accounts) => { const feeData = Helpers.createOracleFeeData( oracleResponse, oracle.privateKey, - tokenAmount + feeDataAmount ); await TruffleAssert.reverts( FeeHandlerRouterInstance.calculateFee( diff --git a/test/handlers/fee/withOracle/collectFee.js b/test/handlers/fee/dynamic/collectFee.js similarity index 89% rename from test/handlers/fee/withOracle/collectFee.js rename to test/handlers/fee/dynamic/collectFee.js index 160661f2..12bc6bd8 100644 --- a/test/handlers/fee/withOracle/collectFee.js +++ b/test/handlers/fee/dynamic/collectFee.js @@ -10,10 +10,10 @@ const Helpers = require("../../../helpers"); const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); const ERC20HandlerContract = artifacts.require("ERC20Handler"); -const FeeHandlerWithOracleContract = artifacts.require("FeeHandlerWithOracle"); +const DynamicFeeHandlerContract = artifacts.require("DynamicERC20FeeHandlerEVM"); const FeeHandlerRouterContract = artifacts.require("FeeHandlerRouter"); -contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { +contract("DynamicFeeHandler - [collectFee]", async (accounts) => { const originDomainID = 1; const destinationDomainID = 2; const oracle = new Ethers.Wallet.createRandom(); @@ -28,7 +28,7 @@ contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { const msgGasLimit = 0; let BridgeInstance; - let FeeHandlerWithOracleInstance; + let DynamicFeeHandlerInstance; let resourceID; let depositData; @@ -76,13 +76,13 @@ contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { FeeHandlerRouterInstance = await FeeHandlerRouterContract.new( BridgeInstance.address ); - FeeHandlerWithOracleInstance = await FeeHandlerWithOracleContract.new( + DynamicFeeHandlerInstance = await DynamicFeeHandlerContract.new( BridgeInstance.address, FeeHandlerRouterInstance.address ); - await FeeHandlerWithOracleInstance.setFeeOracle(oracle.address); - await FeeHandlerWithOracleInstance.setFeeProperties(gasUsed, feePercent); + await DynamicFeeHandlerInstance.setFeeOracle(oracle.address); + await DynamicFeeHandlerInstance.setFeeProperties(gasUsed, feePercent); resourceID = Helpers.createResourceID( ERC20MintableInstance.address, @@ -100,14 +100,14 @@ contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { ERC20MintableInstance.approve(ERC20HandlerInstance.address, tokenAmount, { from: depositorAddress, }), - ERC20MintableInstance.approve(FeeHandlerWithOracleInstance.address, fee, { + ERC20MintableInstance.approve(DynamicFeeHandlerInstance.address, fee, { from: depositorAddress, }), BridgeInstance.adminChangeFeeHandler(FeeHandlerRouterInstance.address), FeeHandlerRouterInstance.adminSetResourceHandler( destinationDomainID, resourceID, - FeeHandlerWithOracleInstance.address + DynamicFeeHandlerInstance.address ), ]); @@ -141,7 +141,7 @@ contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { const balanceBefore = ( await ERC20MintableInstance.balanceOf( - FeeHandlerWithOracleInstance.address + DynamicFeeHandlerInstance.address ) ).toString(); @@ -161,7 +161,7 @@ contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { ); }); const internalTx = await TruffleAssert.createTransactionResult( - FeeHandlerWithOracleInstance, + DynamicFeeHandlerInstance, depositTx.tx ); TruffleAssert.eventEmitted(internalTx, "FeeCollected", (event) => { @@ -176,7 +176,7 @@ contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { }); const balanceAfter = ( await ERC20MintableInstance.balanceOf( - FeeHandlerWithOracleInstance.address + DynamicFeeHandlerInstance.address ) ).toString(); assert.equal(balanceAfter, fee.add(balanceBefore).toString()); @@ -237,7 +237,7 @@ contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { tokenAmount ); await ERC20MintableInstance.approve( - FeeHandlerWithOracleInstance.address, + DynamicFeeHandlerInstance.address, 0, {from: depositorAddress} ); @@ -255,7 +255,7 @@ contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { ); }); - it("deposit should revert if not called by router on FeeHandlerWithOracle contract", async () => { + it("deposit should revert if not called by router on DynamicFeeHandler contract", async () => { const depositData = Helpers.createERCDepositData( tokenAmount, 20, @@ -278,12 +278,12 @@ contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { tokenAmount ); await ERC20MintableInstance.approve( - FeeHandlerWithOracleInstance.address, + DynamicFeeHandlerInstance.address, 0, {from: depositorAddress} ); await TruffleAssert.reverts( - FeeHandlerWithOracleInstance.collectFee( + DynamicFeeHandlerInstance.collectFee( depositorAddress, originDomainID, destinationDomainID, @@ -322,7 +322,7 @@ contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { tokenAmount ); await ERC20MintableInstance.approve( - FeeHandlerWithOracleInstance.address, + DynamicFeeHandlerInstance.address, 0, {from: depositorAddress} ); @@ -343,9 +343,9 @@ contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { ); }); - it("should successfully change fee handler from FeeRouter to FeeHandlerWithOracle and collect fee", async () => { + it("should successfully change fee handler from FeeRouter to DynamicFeeHandler and collect fee", async () => { await BridgeInstance.adminChangeFeeHandler( - FeeHandlerWithOracleInstance.address + DynamicFeeHandlerInstance.address ); const oracleResponse = { @@ -367,7 +367,7 @@ contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { const balanceBefore = ( await ERC20MintableInstance.balanceOf( - FeeHandlerWithOracleInstance.address + DynamicFeeHandlerInstance.address ) ).toString(); @@ -387,7 +387,7 @@ contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { ); }); const internalTx = await TruffleAssert.createTransactionResult( - FeeHandlerWithOracleInstance, + DynamicFeeHandlerInstance, depositTx.tx ); TruffleAssert.eventEmitted(internalTx, "FeeCollected", (event) => { @@ -402,7 +402,7 @@ contract("FeeHandlerWithOracle - [collectFee]", async (accounts) => { }); const balanceAfter = ( await ERC20MintableInstance.balanceOf( - FeeHandlerWithOracleInstance.address + DynamicFeeHandlerInstance.address ) ).toString(); assert.equal(balanceAfter, fee.add(balanceBefore).toString()); diff --git a/test/handlers/fee/withOracle/distributeFee.js b/test/handlers/fee/dynamic/distributeFee.js similarity index 87% rename from test/handlers/fee/withOracle/distributeFee.js rename to test/handlers/fee/dynamic/distributeFee.js index 19b14ebc..ea194386 100644 --- a/test/handlers/fee/withOracle/distributeFee.js +++ b/test/handlers/fee/dynamic/distributeFee.js @@ -8,12 +8,12 @@ const Ethers = require("ethers"); const Helpers = require("../../../helpers"); -const FeeHandlerWithOracleContract = artifacts.require("FeeHandlerWithOracle"); +const DynamicFeeHandlerContract = artifacts.require("DynamicERC20FeeHandlerEVM"); const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); const ERC20HandlerContract = artifacts.require("ERC20Handler"); const FeeHandlerRouterContract = artifacts.require("FeeHandlerRouter"); -contract("FeeHandlerWithOracle - [distributeFee]", async (accounts) => { +contract("DynamicFeeHandler - [distributeFee]", async (accounts) => { const originDomainID = 1; const destinationDomainID = 2; const oracle = new Ethers.Wallet.createRandom(); @@ -25,7 +25,7 @@ contract("FeeHandlerWithOracle - [distributeFee]", async (accounts) => { const msgGasLimit = 0; let BridgeInstance; - let FeeHandlerWithOracleInstance; + let DynamicFeeHandlerInstance; let resourceID; let depositData; let feeData; @@ -44,15 +44,15 @@ contract("FeeHandlerWithOracle - [distributeFee]", async (accounts) => { FeeHandlerRouterInstance = await FeeHandlerRouterContract.new( BridgeInstance.address ); - FeeHandlerWithOracleInstance = await FeeHandlerWithOracleContract.new( + DynamicFeeHandlerInstance = await DynamicFeeHandlerContract.new( BridgeInstance.address, FeeHandlerRouterInstance.address ); - await FeeHandlerWithOracleInstance.setFeeOracle(oracle.address); + await DynamicFeeHandlerInstance.setFeeOracle(oracle.address); const gasUsed = 100000; const feePercent = 10000; - await FeeHandlerWithOracleInstance.setFeeProperties(gasUsed, feePercent); + await DynamicFeeHandlerInstance.setFeeProperties(gasUsed, feePercent); ERC20MintableInstance = await ERC20MintableContract.new("token", "TOK"); resourceID = Helpers.createResourceID( @@ -76,7 +76,7 @@ contract("FeeHandlerWithOracle - [distributeFee]", async (accounts) => { from: depositorAddress, }), ERC20MintableInstance.approve( - FeeHandlerWithOracleInstance.address, + DynamicFeeHandlerInstance.address, tokenAmount, {from: depositorAddress} ), @@ -84,7 +84,7 @@ contract("FeeHandlerWithOracle - [distributeFee]", async (accounts) => { FeeHandlerRouterInstance.adminSetResourceHandler( destinationDomainID, resourceID, - FeeHandlerWithOracleInstance.address + DynamicFeeHandlerInstance.address ), ]); @@ -135,14 +135,14 @@ contract("FeeHandlerWithOracle - [distributeFee]", async (accounts) => { ) ); const balance = await ERC20MintableInstance.balanceOf( - FeeHandlerWithOracleInstance.address + DynamicFeeHandlerInstance.address ); assert.equal(web3.utils.fromWei(balance, "ether"), "1"); const payout = Ethers.utils.parseEther("0.5"); // Transfer the funds - const tx = await FeeHandlerWithOracleInstance.transferFee( + const tx = await DynamicFeeHandlerInstance.transferFee( resourceID, [accounts[3], accounts[4]], [payout, payout] @@ -180,7 +180,7 @@ contract("FeeHandlerWithOracle - [distributeFee]", async (accounts) => { ) ); const balance = await ERC20MintableInstance.balanceOf( - FeeHandlerWithOracleInstance.address + DynamicFeeHandlerInstance.address ); assert.equal(web3.utils.fromWei(balance, "ether"), "1"); @@ -188,13 +188,13 @@ contract("FeeHandlerWithOracle - [distributeFee]", async (accounts) => { // Incorrect resourceID resourceID = Helpers.createResourceID( - FeeHandlerWithOracleInstance.address, + DynamicFeeHandlerInstance.address, originDomainID ); // Transfer the funds: fails await TruffleAssert.reverts( - FeeHandlerWithOracleInstance.transferFee( + DynamicFeeHandlerInstance.transferFee( resourceID, [accounts[3], accounts[4]], [payout, payout] @@ -215,13 +215,13 @@ contract("FeeHandlerWithOracle - [distributeFee]", async (accounts) => { ) ); const balance = await ERC20MintableInstance.balanceOf( - FeeHandlerWithOracleInstance.address + DynamicFeeHandlerInstance.address ); assert.equal(web3.utils.fromWei(balance, "ether"), "1"); const payout = Ethers.utils.parseEther("0.5"); await assertOnlyAdmin( - FeeHandlerWithOracleInstance.transferFee, + DynamicFeeHandlerInstance.transferFee, resourceID, [accounts[3], accounts[4]], [payout, payout] @@ -241,13 +241,13 @@ contract("FeeHandlerWithOracle - [distributeFee]", async (accounts) => { ) ); const balance = await ERC20MintableInstance.balanceOf( - FeeHandlerWithOracleInstance.address + DynamicFeeHandlerInstance.address ); assert.equal(web3.utils.fromWei(balance, "ether"), "1"); const payout = Ethers.utils.parseEther("0.5"); await TruffleAssert.reverts( - FeeHandlerWithOracleInstance.transferFee( + DynamicFeeHandlerInstance.transferFee( resourceID, [accounts[3], accounts[4]], [payout, payout, payout] diff --git a/test/handlers/fee/handlerRouter.js b/test/handlers/fee/handlerRouter.js index e83572cb..91d7e61f 100644 --- a/test/handlers/fee/handlerRouter.js +++ b/test/handlers/fee/handlerRouter.js @@ -7,7 +7,7 @@ const TruffleAssert = require("truffle-assertions"); const Helpers = require("../../helpers"); -const FeeHandlerWithOracleContract = artifacts.require("FeeHandlerWithOracle"); +const DynamicFeeHandlerContract = artifacts.require("DynamicERC20FeeHandlerEVM"); const FeeHandlerRouterContract = artifacts.require("FeeHandlerRouter"); const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser"); @@ -42,7 +42,7 @@ contract("FeeHandlerRouter", async (accounts) => { FeeHandlerRouterInstance = await FeeHandlerRouterContract.new( BridgeInstance.address ); - FeeHandlerWithOracleInstance = await FeeHandlerWithOracleContract.new( + DynamicFeeHandlerInstance = await DynamicFeeHandlerContract.new( BridgeInstance.address, FeeHandlerRouterInstance.address );