From 806054c2bd87d6eb93c8d4c331c4e715e17b4226 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Tue, 20 Aug 2024 17:36:07 +0530 Subject: [PATCH 01/27] refactor: add state vars to support rate limit --- .../FuelERC20Gateway/FuelERC20GatewayV4.sol | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol index b4d82369..c243d05c 100644 --- a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol +++ b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol @@ -56,6 +56,8 @@ contract FuelERC20GatewayV4 is /// @dev The admin related contract roles bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); + /// @dev The rate limit setter role + bytes32 public constant SET_RATE_LIMITER_ROLE = keccak256("SET_RATE_LIMITER_ROLE"); uint256 public constant FUEL_ASSET_DECIMALS = 9; uint256 constant NO_DECIMALS = type(uint256).max; @@ -66,14 +68,26 @@ contract FuelERC20GatewayV4 is bool public whitelistRequired; bytes32 public assetIssuerId; - mapping(address => uint256) internal _deposits; - mapping(address => uint256) internal _depositLimits; - mapping(address => uint256) internal _decimalsCache; - mapping(bytes32 => bool) internal _isBridge; + mapping(address => uint256) _deposits; + mapping(address => uint256) _depositLimits; + mapping(address => uint256) _decimalsCache; + mapping(bytes32 => bool) _isBridge; + + /// @notice Amounts already withdrawn this period for each token. + mapping(address => uint256) rateLimitDuration; + + /// @notice Amounts already withdrawn this period for each token. + mapping(address => uint256) currentPeriodAmount; + + /// @notice The time at which the current period ends at for each token. + mapping(address => uint256) currentPeriodEnd; + + /// @notice The eth withdrawal limit amount for each token. + mapping(address => uint256) limitAmount; /// @notice Contract initializer to setup starting values /// @param fuelMessagePortal The FuelMessagePortal contract - function initialize(FuelMessagePortal fuelMessagePortal) public initializer { + function initialize(FuelMessagePortal fuelMessagePortal, uint256 _limitAmount, uint256 _rateLimitDuration) public initializer { __FuelMessagesEnabled_init(fuelMessagePortal); __Pausable_init(); __AccessControl_init(); @@ -82,6 +96,8 @@ contract FuelERC20GatewayV4 is //grant initial roles _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(PAUSER_ROLE, msg.sender); + // set rate limiter role + _grantRole(SET_RATE_LIMITER_ROLE, msg.sender); } ///////////////////// @@ -121,6 +137,37 @@ contract FuelERC20GatewayV4 is require(success); } + /** + * @notice Resets the rate limit amount. + * @param _amount The amount to reset the limit to. + */ + function resetRateLimitAmount(address _token, uint256 _amount) external onlyRole(SET_RATE_LIMITER_ROLE) { + uint256 withdrawalLimitAmountToSet; + bool amountWithdrawnLoweredToLimit; + bool withdrawalAmountResetToZero; + + // if period has expired then currentPeriodAmount is zero + if (currentPeriodEnd[_token] < block.timestamp) { + unchecked { + currentPeriodEnd[_token] = block.timestamp + rateLimitDuration[_token]; + } + withdrawalAmountResetToZero = true; + } else { + // If the withdrawn amount is higher, it is set to the new limit amount + if (_amount < currentPeriodAmount[_token]) { + withdrawalLimitAmountToSet = _amount; + amountWithdrawnLoweredToLimit = true; + } + } + + limitAmount[_token] = _amount; + + if (withdrawalAmountResetToZero || amountWithdrawnLoweredToLimit) { + currentPeriodAmount[_token] = withdrawalLimitAmountToSet; + } + } + + ////////////////////// // Public Functions // ////////////////////// From 8130f0db7118e9b1af696891d9eb478e3b301036 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Wed, 21 Aug 2024 12:51:40 +0530 Subject: [PATCH 02/27] feat: complete logic for handling rate limit for erc20's --- .../FuelERC20Gateway/FuelERC20GatewayV4.sol | 55 ++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol index c243d05c..9ca72501 100644 --- a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol +++ b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol @@ -36,6 +36,9 @@ contract FuelERC20GatewayV4 is error CannotWithdrawZero(); error InvalidSender(); error InvalidAmount(); + error RateLimitAlreadySet(); + error RateLimitExceeded(); + error RateLimitNotInitialized(); /// @dev Emitted when tokens are deposited from Ethereum to Fuel event Deposit(bytes32 indexed sender, address indexed tokenAddress, uint256 amount); @@ -87,7 +90,7 @@ contract FuelERC20GatewayV4 is /// @notice Contract initializer to setup starting values /// @param fuelMessagePortal The FuelMessagePortal contract - function initialize(FuelMessagePortal fuelMessagePortal, uint256 _limitAmount, uint256 _rateLimitDuration) public initializer { + function initialize(FuelMessagePortal fuelMessagePortal) public initializer { __FuelMessagesEnabled_init(fuelMessagePortal); __Pausable_init(); __AccessControl_init(); @@ -137,8 +140,23 @@ contract FuelERC20GatewayV4 is require(success); } + /** + * @notice Intitializing rate limit params for each token. + * @param _token The token address to set rate limit params for. + * @param _limitAmount The amount to reset the limit to. + * @param _rateLimitDuration rate limit duration. + */ + function initializeRateLimit(address _token, uint256 _limitAmount, uint256 _rateLimitDuration) external onlyRole(SET_RATE_LIMITER_ROLE) { + if (currentPeriodEnd[_token] != 0) revert RateLimitAlreadySet(); + + rateLimitDuration[_token] = _rateLimitDuration; + currentPeriodEnd[_token] = block.timestamp + _rateLimitDuration; + limitAmount[_token] = _limitAmount; + } + /** * @notice Resets the rate limit amount. + * @param _token The token address to set rate limit for. * @param _amount The amount to reset the limit to. */ function resetRateLimitAmount(address _token, uint256 _amount) external onlyRole(SET_RATE_LIMITER_ROLE) { @@ -146,8 +164,13 @@ contract FuelERC20GatewayV4 is bool amountWithdrawnLoweredToLimit; bool withdrawalAmountResetToZero; + // avoid multiple SLOADS + uint256 rateLimitDurationEndTimestamp = currentPeriodEnd[_token]; + + if (rateLimitDurationEndTimestamp == 0) revert RateLimitNotInitialized(); + // if period has expired then currentPeriodAmount is zero - if (currentPeriodEnd[_token] < block.timestamp) { + if (rateLimitDurationEndTimestamp < block.timestamp) { unchecked { currentPeriodEnd[_token] = block.timestamp + rateLimitDuration[_token]; } @@ -273,6 +296,9 @@ contract FuelERC20GatewayV4 is uint8 decimals = _getTokenDecimals(tokenAddress); uint256 amount = _adjustWithdrawalDecimals(decimals, l2BurntAmount); + // rate limit check + _addWithdrawnAmount(tokenAddress, amount); + //reduce deposit balance and transfer tokens (math will underflow if amount is larger than allowed) _deposits[tokenAddress] = _deposits[tokenAddress] - l2BurntAmount; IERC20MetadataUpgradeable(tokenAddress).safeTransfer(to, amount); @@ -376,6 +402,31 @@ contract FuelERC20GatewayV4 is //should revert if msg.sender is not authorized to upgrade the contract (currently only owner) } + /** + * @notice Increments the amount withdrawn in the period. + * @dev Reverts if the withdrawn limit is breached. + * @param _token Token address to update withdrawn amount for. + * @param _withdrawnAmount The amount withdrawn to be added. + */ + function _addWithdrawnAmount(address _token, uint256 _withdrawnAmount) internal { + uint256 currentPeriodAmountTemp; + + if (currentPeriodEnd[_token] < block.timestamp) { + currentPeriodEnd[_token] = block.timestamp + rateLimitDuration[_token]; + currentPeriodAmountTemp = _withdrawnAmount; + } else { + unchecked { + currentPeriodAmountTemp = currentPeriodAmount[_token] + _withdrawnAmount; + } + } + + if (currentPeriodAmountTemp > limitAmount[_token]) { + revert RateLimitExceeded(); + } + + currentPeriodAmount[_token] = currentPeriodAmountTemp; + } + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. From 3e18057a47af983961228eeb7ad366c9e2883dd2 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Wed, 21 Aug 2024 15:27:50 +0530 Subject: [PATCH 03/27] fix: existing unit tests --- .../solidity-contracts/protocol/constants.ts | 1 + .../behaviors/erc20GatewayV4.behavior.test.ts | 26 ++++++++++++++++++- .../test/erc20GatewayV4.test.ts | 11 ++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/solidity-contracts/protocol/constants.ts b/packages/solidity-contracts/protocol/constants.ts index 6fc40753..e68eb518 100644 --- a/packages/solidity-contracts/protocol/constants.ts +++ b/packages/solidity-contracts/protocol/constants.ts @@ -10,5 +10,6 @@ export const CONTRACT_MESSAGE_PREDICATE = // From application header: https://github.com/FuelLabs/fuel-specs/blob/master/src/protocol/block-header.md export const CONSENSUS_PARAMETERS_VERSION = 0n; export const STATE_TRANSITION_BYTECODE_VERSION = 0n; +export const STANDARD_TOKEN_DECIMALS = 18; export const RATE_LIMIT_AMOUNT = 10e18; // 10 ether export const RATE_LIMIT_DURATION = 604800; // 1 week diff --git a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts index ad844386..f7108551 100644 --- a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts +++ b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts @@ -13,7 +13,12 @@ import type { BytesLike } from 'ethers'; import hre from 'hardhat'; import { random } from 'lodash'; -import { CONTRACT_MESSAGE_PREDICATE } from '../../protocol/constants'; +import { + CONTRACT_MESSAGE_PREDICATE, + RATE_LIMIT_AMOUNT, + RATE_LIMIT_DURATION, + STANDARD_TOKEN_DECIMALS, +} from '../../protocol/constants'; import { randomAddress, randomBytes32 } from '../../protocol/utils'; import { CustomToken__factory, @@ -1008,6 +1013,17 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { assetIssuerId, } = env; + const rateLimitAmount = + RATE_LIMIT_AMOUNT / 10 ** (STANDARD_TOKEN_DECIMALS - decimals); + + await erc20Gateway + .connect(deployer) + .initializeRateLimit( + token.getAddress(), + rateLimitAmount.toString(), + RATE_LIMIT_DURATION + ); + const amount = parseUnits( random(0.01, 1, true).toFixed(decimals), Number(decimals) @@ -1125,6 +1141,14 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { await token.mint(user, amount); await token.approve(erc20Gateway, MaxUint256); + await erc20Gateway + .connect(deployer) + .initializeRateLimit( + token.getAddress(), + amount.toString(), + RATE_LIMIT_DURATION + ); + await erc20Gateway.connect(user).deposit(recipient, token, amount); const withdrawAmount = amount / 4n; diff --git a/packages/solidity-contracts/test/erc20GatewayV4.test.ts b/packages/solidity-contracts/test/erc20GatewayV4.test.ts index f590c0e7..9e691f93 100644 --- a/packages/solidity-contracts/test/erc20GatewayV4.test.ts +++ b/packages/solidity-contracts/test/erc20GatewayV4.test.ts @@ -2,12 +2,15 @@ import { zeroPadValue } from 'ethers'; import hre, { deployments } from 'hardhat'; import { randomBytes32 } from '../protocol/utils'; + import { FuelERC20GatewayV4__factory, type MockFuelMessagePortal, type Token, } from '../typechain'; +import { RATE_LIMIT_AMOUNT, RATE_LIMIT_DURATION } from '../protocol/constants'; + import { behavesLikeErc20GatewayV4 } from './behaviors'; import { deployProxy } from './utils'; @@ -37,6 +40,14 @@ describe('erc20GatewayV4', () => { await erc20Gateway.connect(deployer).setAssetIssuerId(assetIssuerId); + await erc20Gateway + .connect(deployer) + .initializeRateLimit( + token.getAddress(), + RATE_LIMIT_AMOUNT.toString(), + RATE_LIMIT_DURATION + ); + return { fuelMessagePortal, erc20Gateway, From d4e16e9ddc842e4d2a8630952b8ee0f1f417203c Mon Sep 17 00:00:00 2001 From: viraj124 Date: Fri, 23 Aug 2024 15:24:28 +0530 Subject: [PATCH 04/27] chore: update var visibility --- .../gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol index 9ca72501..ea6a388a 100644 --- a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol +++ b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol @@ -77,16 +77,16 @@ contract FuelERC20GatewayV4 is mapping(bytes32 => bool) _isBridge; /// @notice Amounts already withdrawn this period for each token. - mapping(address => uint256) rateLimitDuration; + mapping(address => uint256) public rateLimitDuration; /// @notice Amounts already withdrawn this period for each token. - mapping(address => uint256) currentPeriodAmount; + mapping(address => uint256) public currentPeriodAmount; /// @notice The time at which the current period ends at for each token. - mapping(address => uint256) currentPeriodEnd; + mapping(address => uint256) public currentPeriodEnd; /// @notice The eth withdrawal limit amount for each token. - mapping(address => uint256) limitAmount; + mapping(address => uint256) public limitAmount; /// @notice Contract initializer to setup starting values /// @param fuelMessagePortal The FuelMessagePortal contract From f7bc3bcbd76ba78ee3b8ba359256062176841eaf Mon Sep 17 00:00:00 2001 From: viraj124 Date: Fri, 23 Aug 2024 15:37:02 +0530 Subject: [PATCH 05/27] refactor: add assertion checks to existing integration test --- .../integration-tests/tests/bridge_erc20.ts | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/integration-tests/tests/bridge_erc20.ts b/packages/integration-tests/tests/bridge_erc20.ts index 9b816f27..4ccdd71f 100644 --- a/packages/integration-tests/tests/bridge_erc20.ts +++ b/packages/integration-tests/tests/bridge_erc20.ts @@ -1,6 +1,10 @@ import { BridgeFungibleTokenAbi } from '@fuel-bridge/fungible-token'; import type { Token } from '@fuel-bridge/solidity-contracts/typechain'; import type { TestEnvironment } from '@fuel-bridge/test-utils'; +import { + RATE_LIMIT_AMOUNT, + RATE_LIMIT_DURATION, +} from '@fuel-bridge/solidity-contracts/protocol/constants'; import { setupEnvironment, relayCommonMessage, @@ -18,7 +22,7 @@ import { FUEL_CALL_TX_PARAMS, } from '@fuel-bridge/test-utils'; import chai from 'chai'; -import { toBeHex } from 'ethers'; +import { toBeHex, parseEther } from 'ethers'; import type { Signer } from 'ethers'; import { Address, BN } from 'fuels'; import type { @@ -52,6 +56,7 @@ describe('Bridging ERC20 tokens', async function () { eth_erc20GatewayAddress = ( await env.eth.fuelERC20Gateway.getAddress() ).toLowerCase(); + eth_testToken = await getOrDeployECR20Contract(env); eth_testTokenAddress = (await eth_testToken.getAddress()).toLowerCase(); @@ -68,6 +73,15 @@ describe('Bridging ERC20 tokens', async function () { await env.eth.fuelERC20Gateway.setAssetIssuerId(fuel_bridgeContractId); fuel_testAssetId = getTokenId(fuel_bridge, eth_testTokenAddress); + // initializing rate limit params for the token + await env.eth.fuelERC20Gateway + .connect(env.eth.deployer) + .initializeRateLimit( + eth_testTokenAddress, + RATE_LIMIT_AMOUNT.toString(), + RATE_LIMIT_DURATION + ); + const { value: expectedGatewayContractId } = await fuel_bridge.functions .bridged_token_gateway() .addContracts([fuel_bridge, fuel_bridgeImpl]) @@ -335,6 +349,14 @@ describe('Bridging ERC20 tokens', async function () { }); it('Relay Message from Fuel on Ethereum', async () => { + const withdrawnAmountBeforeRelay = + await env.eth.fuelERC20Gateway.currentPeriodAmount( + eth_testTokenAddress + ); + + const rateLimitEndDuratioBeforeRelay = + await env.eth.fuelERC20Gateway.currentPeriodEnd(eth_testTokenAddress); + // wait for block finalization await waitForBlockFinalization(env, withdrawMessageProof); @@ -352,6 +374,22 @@ describe('Bridging ERC20 tokens', async function () { relayMessageParams.blockInHistoryProof, relayMessageParams.messageInBlockProof ); + + // check rate limit params + const withdrawnAmountAfterRelay = + await env.eth.fuelERC20Gateway.currentPeriodAmount( + eth_testTokenAddress + ); + + const rateLimitEndDuratioAfterRelay = + await env.eth.fuelERC20Gateway.currentPeriodEnd(eth_testTokenAddress); + + expect(rateLimitEndDuratioAfterRelay === rateLimitEndDuratioBeforeRelay) + .to.be.true; + + expect( + withdrawnAmountAfterRelay === NUM_TOKENS + withdrawnAmountBeforeRelay + ).to.be.true; }); it('Check ERC20 arrived on Ethereum', async () => { From 2a75533bb5b1218807772b409a1417af3478c5bd Mon Sep 17 00:00:00 2001 From: viraj124 Date: Sun, 25 Aug 2024 20:46:37 +0530 Subject: [PATCH 06/27] refactor: add -ve erc20 rate limit unit tests --- .../behaviors/erc20GatewayV4.behavior.test.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts index f7108551..befdff47 100644 --- a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts +++ b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts @@ -1005,6 +1005,66 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { ).connect(user); }); + it('reverts when rate limit is reset without initializing it first', async () => { + const { + erc20Gateway, + signers: [deployer, user], + } = env; + + const rateLimitAmount = + RATE_LIMIT_AMOUNT / 10 ** (STANDARD_TOKEN_DECIMALS - decimals); + + await expect( + erc20Gateway + .connect(deployer) + .resetRateLimitAmount( + token.getAddress(), + rateLimitAmount.toString() + ) + ).to.be.revertedWithCustomError( + erc20Gateway, + 'RateLimitNotInitialized' + ); + }); + + it('reverts when rate limit is initialized again', async () => { + const { + erc20Gateway, + signers: [deployer, user], + } = env; + + const rateLimitAmount = + RATE_LIMIT_AMOUNT / 10 ** (STANDARD_TOKEN_DECIMALS - decimals); + + await erc20Gateway + .connect(deployer) + .initializeRateLimit( + token.getAddress(), + rateLimitAmount.toString(), + RATE_LIMIT_DURATION + ); + + await erc20Gateway + .connect(deployer) + .resetRateLimitAmount( + token.getAddress(), + rateLimitAmount.toString() + ); + + await expect( + erc20Gateway + .connect(deployer) + .initializeRateLimit( + token.getAddress(), + rateLimitAmount.toString(), + RATE_LIMIT_DURATION + ) + ).to.be.revertedWithCustomError( + erc20Gateway, + 'RateLimitAlreadySet' + ); + }); + it('reduces deposits and transfers out without upscaling', async () => { const { erc20Gateway, @@ -1100,6 +1160,13 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { token, withdrawAmount ); + + // check rate limit params + const withdrawnAmountAfterRelay = + await erc20Gateway.currentPeriodAmount(token.getAddress()); + + await expect(withdrawnAmountAfterRelay == withdrawAmount * 2n).to + .be.true; } }); }); From 2bc3cf24ab600729b52cdf0df34891a7d856ba3b Mon Sep 17 00:00:00 2001 From: viraj124 Date: Mon, 26 Aug 2024 17:22:21 +0530 Subject: [PATCH 07/27] feat: add erc20 rate limit integration tests --- .../integration-tests/tests/bridge_erc20.ts | 166 +++++++++++++++++- 1 file changed, 161 insertions(+), 5 deletions(-) diff --git a/packages/integration-tests/tests/bridge_erc20.ts b/packages/integration-tests/tests/bridge_erc20.ts index 4ccdd71f..f2d0d49c 100644 --- a/packages/integration-tests/tests/bridge_erc20.ts +++ b/packages/integration-tests/tests/bridge_erc20.ts @@ -20,8 +20,10 @@ import { getTokenId, getBlock, FUEL_CALL_TX_PARAMS, + hardhatSkipTime, } from '@fuel-bridge/test-utils'; import chai from 'chai'; +import type { JsonRpcProvider } from 'ethers'; import { toBeHex, parseEther } from 'ethers'; import type { Signer } from 'ethers'; import { Address, BN } from 'fuels'; @@ -112,7 +114,7 @@ describe('Bridging ERC20 tokens', async function () { }); describe('Bridge ERC20 to Fuel', async () => { - const NUM_TOKENS = 10_000_000_000n; + const NUM_TOKENS = 100000000000000000000n; let ethereumTokenSender: Signer; let ethereumTokenSenderAddress: string; let ethereumTokenSenderBalance: bigint; @@ -274,7 +276,7 @@ describe('Bridging ERC20 tokens', async function () { }); describe('Bridge ERC20 from Fuel', async () => { - const NUM_TOKENS = 10_000_000_000n; + const NUM_TOKENS = 10000000000000000000n; let fuelTokenSender: FuelWallet; let ethereumTokenReceiver: Signer; let ethereumTokenReceiverAddress: string; @@ -308,7 +310,9 @@ describe('Bridging ERC20 tokens', async function () { }) .callParams({ forward: { - amount: fuelTokenSenderBalance, + amount: new BN(NUM_TOKENS.toString()).div( + new BN(DECIMAL_DIFF.toString()) + ), assetId: fuel_testAssetId, }, }) @@ -322,6 +326,7 @@ describe('Bridging ERC20 tokens', async function () { const newSenderBalance = await fuelTokenSender.getBalance( fuel_testAssetId ); + expect( newSenderBalance.eq( fuelTokenSenderBalance.sub(toBeHex(NUM_TOKENS / DECIMAL_DIFF)) @@ -364,7 +369,6 @@ describe('Bridging ERC20 tokens', async function () { const relayMessageParams = createRelayMessageParams(withdrawMessageProof); // relay message - await env.eth.fuelMessagePortal .connect(env.eth.signers[0]) .relayMessage( @@ -392,12 +396,164 @@ describe('Bridging ERC20 tokens', async function () { ).to.be.true; }); + it('Rate limit parameters are updated when current withdrawn amount is more than the new limit', async () => { + const deployer = await env.eth.deployer; + const newRateLimit = '5'; + + await env.eth.fuelERC20Gateway + .connect(deployer) + .resetRateLimitAmount(eth_testTokenAddress, parseEther(newRateLimit)); + + const currentWithdrawnAmountAfterSettingLimit = + await env.eth.fuelERC20Gateway.currentPeriodAmount( + eth_testTokenAddress + ); + + expect( + currentWithdrawnAmountAfterSettingLimit === parseEther(newRateLimit) + ).to.be.true; + }); + + it('Rate limit parameters are updated when the initial duration is over', async () => { + const deployer = await env.eth.deployer; + const newRateLimit = `30`; + + const rateLimitDuration = + await env.eth.fuelERC20Gateway.rateLimitDuration(eth_testTokenAddress); + + // fast forward time + await hardhatSkipTime( + env.eth.provider as JsonRpcProvider, + rateLimitDuration * 2n + ); + const currentPeriodEndBeforeRelay = + await env.eth.fuelERC20Gateway.currentPeriodEnd(eth_testTokenAddress); + + await env.eth.fuelERC20Gateway + .connect(deployer) + .resetRateLimitAmount(eth_testTokenAddress, parseEther(newRateLimit)); + + // withdraw tokens back to the base chain + fuel_bridge.account = fuelTokenSender; + const paddedAddress = + '0x' + ethereumTokenReceiverAddress.slice(2).padStart(64, '0'); + + const transactionRequest = await fuel_bridge.functions + .withdraw(paddedAddress) + .addContracts([fuel_bridge, fuel_bridgeImpl]) + .txParams({ + tip: 0, + gasLimit: 1_000_000, + maxFee: 1, + }) + .callParams({ + forward: { + amount: new BN(NUM_TOKENS.toString()).div( + new BN(DECIMAL_DIFF.toString()) + ), + assetId: fuel_testAssetId, + }, + }) + .fundWithRequiredCoins(); + + const tx = await fuelTokenSender.sendTransaction(transactionRequest); + const fWithdrawTxResult = await tx.waitForResult(); + expect(fWithdrawTxResult.status).to.equal('success'); + + // Wait for the commited block + const withdrawBlock = await getBlock( + env.fuel.provider.url, + fWithdrawTxResult.blockId + ); + const commitHashAtL1 = await waitForBlockCommit( + env, + withdrawBlock.header.height + ); + + const messageOutReceipt = getMessageOutReceipt( + fWithdrawTxResult.receipts + ); + withdrawMessageProof = await fuelTokenSender.provider.getMessageProof( + tx.id, + messageOutReceipt.nonce, + commitHashAtL1 + ); + + // wait for block finalization + await waitForBlockFinalization(env, withdrawMessageProof); + + // construct relay message proof data + const relayMessageParams = createRelayMessageParams(withdrawMessageProof); + + // relay message + await env.eth.fuelMessagePortal + .connect(env.eth.signers[0]) + .relayMessage( + relayMessageParams.message, + relayMessageParams.rootBlockHeader, + relayMessageParams.blockHeader, + relayMessageParams.blockInHistoryProof, + relayMessageParams.messageInBlockProof + ); + + const currentPeriodEndAfterRelay = + await env.eth.fuelERC20Gateway.currentPeriodEnd(eth_testTokenAddress); + + expect(currentPeriodEndAfterRelay > currentPeriodEndBeforeRelay).to.be + .true; + + const currentPeriodAmount = + await env.eth.fuelERC20Gateway.currentPeriodAmount( + eth_testTokenAddress + ); + + expect(currentPeriodAmount === NUM_TOKENS).to.be.true; + }); + + it('Rate limit parameters are updated when new limit is set after the initial duration', async () => { + const rateLimitDuration = await env.eth.fuelERC20Gateway.rateLimitDuration(eth_testTokenAddress); + + const deployer = await env.eth.deployer; + const newRateLimit = `40`; + + // fast forward time + await hardhatSkipTime( + env.eth.provider as JsonRpcProvider, + rateLimitDuration * 2n + ); + + const currentWithdrawnAmountBeforeSettingLimit = + await env.eth.fuelERC20Gateway.currentPeriodAmount(eth_testTokenAddress); + const currentPeriodEndBeforeSettingLimit = + await env.eth.fuelERC20Gateway.currentPeriodEnd(eth_testTokenAddress); + + await env.eth.fuelERC20Gateway + .connect(deployer) + .resetRateLimitAmount(eth_testTokenAddress, parseEther(newRateLimit)); + + const currentPeriodEndAfterSettingLimit = + await env.eth.fuelERC20Gateway.currentPeriodEnd(eth_testTokenAddress); + const currentWithdrawnAmountAfterSettingLimit = + await env.eth.fuelERC20Gateway.currentPeriodAmount(eth_testTokenAddress); + + expect( + currentPeriodEndAfterSettingLimit > currentPeriodEndBeforeSettingLimit + ).to.be.true; + + expect( + currentWithdrawnAmountBeforeSettingLimit > + currentWithdrawnAmountAfterSettingLimit + ).to.be.true; + + expect(currentWithdrawnAmountAfterSettingLimit === 0n).to.be.true; + }); + it('Check ERC20 arrived on Ethereum', async () => { // check that the recipient balance has increased by the expected amount const newReceiverBalance = await eth_testToken.balanceOf( ethereumTokenReceiverAddress ); - expect(newReceiverBalance === ethereumTokenReceiverBalance + NUM_TOKENS) + expect(newReceiverBalance === ethereumTokenReceiverBalance + NUM_TOKENS * 2n) .to.be.true; }); }); From 0ee8e147f2139b0d0fdf898b6983eb71974b70eb Mon Sep 17 00:00:00 2001 From: viraj124 Date: Mon, 26 Aug 2024 17:23:58 +0530 Subject: [PATCH 08/27] chore: add changeset --- .changeset/moody-ghosts-lie.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/moody-ghosts-lie.md diff --git a/.changeset/moody-ghosts-lie.md b/.changeset/moody-ghosts-lie.md new file mode 100644 index 00000000..500f0355 --- /dev/null +++ b/.changeset/moody-ghosts-lie.md @@ -0,0 +1,5 @@ +--- +'@fuel-bridge/solidity-contracts': major +--- + +erc20 rate limit From a7eb9c3430532ca2bac5e02582e7040273ec2bf9 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Mon, 26 Aug 2024 18:04:06 +0530 Subject: [PATCH 09/27] refactor: reduce codesize in integration tests --- .../integration-tests/tests/bridge_erc20.ts | 242 ++++++++---------- 1 file changed, 108 insertions(+), 134 deletions(-) diff --git a/packages/integration-tests/tests/bridge_erc20.ts b/packages/integration-tests/tests/bridge_erc20.ts index f2d0d49c..cbc04b23 100644 --- a/packages/integration-tests/tests/bridge_erc20.ts +++ b/packages/integration-tests/tests/bridge_erc20.ts @@ -53,6 +53,89 @@ describe('Bridging ERC20 tokens', async function () { // override the default test timeout from 2000ms this.timeout(DEFAULT_TIMEOUT_MS); + async function generateWithdrawalMessageProof( + fuel_bridge: BridgeFungibleTokenAbi, + fuelTokenSender: FuelWallet, + ethereumTokenReceiverAddress: string, + NUM_TOKENS: bigint, + DECIMAL_DIFF: bigint + ): Promise { + // withdraw tokens back to the base chain + fuel_bridge.account = fuelTokenSender; + const paddedAddress = + '0x' + ethereumTokenReceiverAddress.slice(2).padStart(64, '0'); + const fuelTokenSenderBalance = await fuelTokenSender.getBalance( + fuel_testAssetId + ); + const transactionRequest = await fuel_bridge.functions + .withdraw(paddedAddress) + .addContracts([fuel_bridge, fuel_bridgeImpl]) + .txParams({ + tip: 0, + gasLimit: 1_000_000, + maxFee: 1, + }) + .callParams({ + forward: { + amount: new BN(NUM_TOKENS.toString()).div( + new BN(DECIMAL_DIFF.toString()) + ), + assetId: fuel_testAssetId, + }, + }) + .fundWithRequiredCoins(); + + const tx = await fuelTokenSender.sendTransaction(transactionRequest); + const fWithdrawTxResult = await tx.waitForResult(); + expect(fWithdrawTxResult.status).to.equal('success'); + + // check that the sender balance has decreased by the expected amount + const newSenderBalance = await fuelTokenSender.getBalance(fuel_testAssetId); + + expect( + newSenderBalance.eq( + fuelTokenSenderBalance.sub(toBeHex(NUM_TOKENS / DECIMAL_DIFF)) + ) + ).to.be.true; + + // Wait for the commited block + const withdrawBlock = await getBlock( + env.fuel.provider.url, + fWithdrawTxResult.blockId + ); + const commitHashAtL1 = await waitForBlockCommit( + env, + withdrawBlock.header.height + ); + + const messageOutReceipt = getMessageOutReceipt(fWithdrawTxResult.receipts); + return await fuelTokenSender.provider.getMessageProof( + tx.id, + messageOutReceipt.nonce, + commitHashAtL1 + ); + } + + async function relayMessage( + env: TestEnvironment, + withdrawMessageProof: MessageProof + ) { + // wait for block finalization + await waitForBlockFinalization(env, withdrawMessageProof); + + // construct relay message proof data + const relayMessageParams = createRelayMessageParams(withdrawMessageProof); + + // relay message + await env.eth.fuelMessagePortal.relayMessage( + relayMessageParams.message, + relayMessageParams.rootBlockHeader, + relayMessageParams.blockHeader, + relayMessageParams.blockInHistoryProof, + relayMessageParams.messageInBlockProof + ); + } + before(async () => { env = await setupEnvironment({}); eth_erc20GatewayAddress = ( @@ -294,62 +377,12 @@ describe('Bridging ERC20 tokens', async function () { it('Bridge ERC20 via Fuel token contract', async () => { // withdraw tokens back to the base chain - fuel_bridge.account = fuelTokenSender; - const paddedAddress = - '0x' + ethereumTokenReceiverAddress.slice(2).padStart(64, '0'); - const fuelTokenSenderBalance = await fuelTokenSender.getBalance( - fuel_testAssetId - ); - const transactionRequest = await fuel_bridge.functions - .withdraw(paddedAddress) - .addContracts([fuel_bridge, fuel_bridgeImpl]) - .txParams({ - tip: 0, - gasLimit: 1_000_000, - maxFee: 1, - }) - .callParams({ - forward: { - amount: new BN(NUM_TOKENS.toString()).div( - new BN(DECIMAL_DIFF.toString()) - ), - assetId: fuel_testAssetId, - }, - }) - .fundWithRequiredCoins(); - - const tx = await fuelTokenSender.sendTransaction(transactionRequest); - const fWithdrawTxResult = await tx.waitForResult(); - expect(fWithdrawTxResult.status).to.equal('success'); - - // check that the sender balance has decreased by the expected amount - const newSenderBalance = await fuelTokenSender.getBalance( - fuel_testAssetId - ); - - expect( - newSenderBalance.eq( - fuelTokenSenderBalance.sub(toBeHex(NUM_TOKENS / DECIMAL_DIFF)) - ) - ).to.be.true; - - // Wait for the commited block - const withdrawBlock = await getBlock( - env.fuel.provider.url, - fWithdrawTxResult.blockId - ); - const commitHashAtL1 = await waitForBlockCommit( - env, - withdrawBlock.header.height - ); - - const messageOutReceipt = getMessageOutReceipt( - fWithdrawTxResult.receipts - ); - withdrawMessageProof = await fuelTokenSender.provider.getMessageProof( - tx.id, - messageOutReceipt.nonce, - commitHashAtL1 + withdrawMessageProof = await generateWithdrawalMessageProof( + fuel_bridge, + fuelTokenSender, + ethereumTokenReceiverAddress, + NUM_TOKENS, + DECIMAL_DIFF ); }); @@ -362,22 +395,8 @@ describe('Bridging ERC20 tokens', async function () { const rateLimitEndDuratioBeforeRelay = await env.eth.fuelERC20Gateway.currentPeriodEnd(eth_testTokenAddress); - // wait for block finalization - await waitForBlockFinalization(env, withdrawMessageProof); - - // construct relay message proof data - const relayMessageParams = createRelayMessageParams(withdrawMessageProof); - // relay message - await env.eth.fuelMessagePortal - .connect(env.eth.signers[0]) - .relayMessage( - relayMessageParams.message, - relayMessageParams.rootBlockHeader, - relayMessageParams.blockHeader, - relayMessageParams.blockInHistoryProof, - relayMessageParams.messageInBlockProof - ); + await relayMessage(env, withdrawMessageProof); // check rate limit params const withdrawnAmountAfterRelay = @@ -434,67 +453,16 @@ describe('Bridging ERC20 tokens', async function () { .resetRateLimitAmount(eth_testTokenAddress, parseEther(newRateLimit)); // withdraw tokens back to the base chain - fuel_bridge.account = fuelTokenSender; - const paddedAddress = - '0x' + ethereumTokenReceiverAddress.slice(2).padStart(64, '0'); - - const transactionRequest = await fuel_bridge.functions - .withdraw(paddedAddress) - .addContracts([fuel_bridge, fuel_bridgeImpl]) - .txParams({ - tip: 0, - gasLimit: 1_000_000, - maxFee: 1, - }) - .callParams({ - forward: { - amount: new BN(NUM_TOKENS.toString()).div( - new BN(DECIMAL_DIFF.toString()) - ), - assetId: fuel_testAssetId, - }, - }) - .fundWithRequiredCoins(); - - const tx = await fuelTokenSender.sendTransaction(transactionRequest); - const fWithdrawTxResult = await tx.waitForResult(); - expect(fWithdrawTxResult.status).to.equal('success'); - - // Wait for the commited block - const withdrawBlock = await getBlock( - env.fuel.provider.url, - fWithdrawTxResult.blockId + withdrawMessageProof = await generateWithdrawalMessageProof( + fuel_bridge, + fuelTokenSender, + ethereumTokenReceiverAddress, + NUM_TOKENS, + DECIMAL_DIFF ); - const commitHashAtL1 = await waitForBlockCommit( - env, - withdrawBlock.header.height - ); - - const messageOutReceipt = getMessageOutReceipt( - fWithdrawTxResult.receipts - ); - withdrawMessageProof = await fuelTokenSender.provider.getMessageProof( - tx.id, - messageOutReceipt.nonce, - commitHashAtL1 - ); - - // wait for block finalization - await waitForBlockFinalization(env, withdrawMessageProof); - - // construct relay message proof data - const relayMessageParams = createRelayMessageParams(withdrawMessageProof); // relay message - await env.eth.fuelMessagePortal - .connect(env.eth.signers[0]) - .relayMessage( - relayMessageParams.message, - relayMessageParams.rootBlockHeader, - relayMessageParams.blockHeader, - relayMessageParams.blockInHistoryProof, - relayMessageParams.messageInBlockProof - ); + await relayMessage(env, withdrawMessageProof); const currentPeriodEndAfterRelay = await env.eth.fuelERC20Gateway.currentPeriodEnd(eth_testTokenAddress); @@ -511,7 +479,8 @@ describe('Bridging ERC20 tokens', async function () { }); it('Rate limit parameters are updated when new limit is set after the initial duration', async () => { - const rateLimitDuration = await env.eth.fuelERC20Gateway.rateLimitDuration(eth_testTokenAddress); + const rateLimitDuration = + await env.eth.fuelERC20Gateway.rateLimitDuration(eth_testTokenAddress); const deployer = await env.eth.deployer; const newRateLimit = `40`; @@ -523,7 +492,9 @@ describe('Bridging ERC20 tokens', async function () { ); const currentWithdrawnAmountBeforeSettingLimit = - await env.eth.fuelERC20Gateway.currentPeriodAmount(eth_testTokenAddress); + await env.eth.fuelERC20Gateway.currentPeriodAmount( + eth_testTokenAddress + ); const currentPeriodEndBeforeSettingLimit = await env.eth.fuelERC20Gateway.currentPeriodEnd(eth_testTokenAddress); @@ -534,7 +505,9 @@ describe('Bridging ERC20 tokens', async function () { const currentPeriodEndAfterSettingLimit = await env.eth.fuelERC20Gateway.currentPeriodEnd(eth_testTokenAddress); const currentWithdrawnAmountAfterSettingLimit = - await env.eth.fuelERC20Gateway.currentPeriodAmount(eth_testTokenAddress); + await env.eth.fuelERC20Gateway.currentPeriodAmount( + eth_testTokenAddress + ); expect( currentPeriodEndAfterSettingLimit > currentPeriodEndBeforeSettingLimit @@ -553,8 +526,9 @@ describe('Bridging ERC20 tokens', async function () { const newReceiverBalance = await eth_testToken.balanceOf( ethereumTokenReceiverAddress ); - expect(newReceiverBalance === ethereumTokenReceiverBalance + NUM_TOKENS * 2n) - .to.be.true; + expect( + newReceiverBalance === ethereumTokenReceiverBalance + NUM_TOKENS * 2n + ).to.be.true; }); }); }); From 54709e71e797e57de78ab2f3c71c83e7638e0cfc Mon Sep 17 00:00:00 2001 From: viraj124 Date: Mon, 26 Aug 2024 18:23:57 +0530 Subject: [PATCH 10/27] chore: formatting --- packages/integration-tests/tests/bridge_erc20.ts | 9 ++++----- .../deploy/hardhat/002.fuel_message_portal_v3.ts | 3 +-- .../solidity-contracts/test/erc20GatewayV4.test.ts | 4 +--- .../solidity-contracts/test/erc721Gateway.test.ts | 1 - .../test/messagesIncomingV3.test.ts | 3 +-- .../test/messagesOutgoingV2.test.ts | 1 - packages/solidity-contracts/test/upgrade.ts | 1 - packages/test-utils/src/deploy-scripts/bridge.ts | 11 ++--------- .../test-utils/src/utils/fuels/getOrDeployL2Bridge.ts | 6 ++++-- 9 files changed, 13 insertions(+), 26 deletions(-) diff --git a/packages/integration-tests/tests/bridge_erc20.ts b/packages/integration-tests/tests/bridge_erc20.ts index cbc04b23..f42f1b02 100644 --- a/packages/integration-tests/tests/bridge_erc20.ts +++ b/packages/integration-tests/tests/bridge_erc20.ts @@ -1,10 +1,10 @@ -import { BridgeFungibleTokenAbi } from '@fuel-bridge/fungible-token'; -import type { Token } from '@fuel-bridge/solidity-contracts/typechain'; -import type { TestEnvironment } from '@fuel-bridge/test-utils'; +import type { BridgeFungibleTokenAbi } from '@fuel-bridge/fungible-token'; import { RATE_LIMIT_AMOUNT, RATE_LIMIT_DURATION, } from '@fuel-bridge/solidity-contracts/protocol/constants'; +import type { Token } from '@fuel-bridge/solidity-contracts/typechain'; +import type { TestEnvironment } from '@fuel-bridge/test-utils'; import { setupEnvironment, relayCommonMessage, @@ -23,9 +23,8 @@ import { hardhatSkipTime, } from '@fuel-bridge/test-utils'; import chai from 'chai'; -import type { JsonRpcProvider } from 'ethers'; import { toBeHex, parseEther } from 'ethers'; -import type { Signer } from 'ethers'; +import type { JsonRpcProvider, Signer } from 'ethers'; import { Address, BN } from 'fuels'; import type { AbstractAddress, diff --git a/packages/solidity-contracts/deploy/hardhat/002.fuel_message_portal_v3.ts b/packages/solidity-contracts/deploy/hardhat/002.fuel_message_portal_v3.ts index 0496f6b8..5f77f143 100644 --- a/packages/solidity-contracts/deploy/hardhat/002.fuel_message_portal_v3.ts +++ b/packages/solidity-contracts/deploy/hardhat/002.fuel_message_portal_v3.ts @@ -2,12 +2,11 @@ import { MaxUint256 } from 'ethers'; import type { HardhatRuntimeEnvironment } from 'hardhat/types'; import type { DeployFunction } from 'hardhat-deploy/dist/types'; -import { FuelMessagePortalV3__factory as FuelMessagePortal } from '../../typechain'; - import { RATE_LIMIT_AMOUNT, RATE_LIMIT_DURATION, } from '../../protocol/constants'; +import { FuelMessagePortalV3__factory as FuelMessagePortal } from '../../typechain'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const { diff --git a/packages/solidity-contracts/test/erc20GatewayV4.test.ts b/packages/solidity-contracts/test/erc20GatewayV4.test.ts index 9e691f93..905e78fa 100644 --- a/packages/solidity-contracts/test/erc20GatewayV4.test.ts +++ b/packages/solidity-contracts/test/erc20GatewayV4.test.ts @@ -1,16 +1,14 @@ import { zeroPadValue } from 'ethers'; import hre, { deployments } from 'hardhat'; +import { RATE_LIMIT_AMOUNT, RATE_LIMIT_DURATION } from '../protocol/constants'; import { randomBytes32 } from '../protocol/utils'; - import { FuelERC20GatewayV4__factory, type MockFuelMessagePortal, type Token, } from '../typechain'; -import { RATE_LIMIT_AMOUNT, RATE_LIMIT_DURATION } from '../protocol/constants'; - import { behavesLikeErc20GatewayV4 } from './behaviors'; import { deployProxy } from './utils'; diff --git a/packages/solidity-contracts/test/erc721Gateway.test.ts b/packages/solidity-contracts/test/erc721Gateway.test.ts index 1b6f2725..c9de32d8 100644 --- a/packages/solidity-contracts/test/erc721Gateway.test.ts +++ b/packages/solidity-contracts/test/erc721Gateway.test.ts @@ -32,7 +32,6 @@ import { TIME_TO_FINALIZE, COMMIT_COOLDOWN, } from './utils'; - import { createBlock } from './utils/createBlock'; const { expect } = chai; diff --git a/packages/solidity-contracts/test/messagesIncomingV3.test.ts b/packages/solidity-contracts/test/messagesIncomingV3.test.ts index 51e90634..442c1483 100644 --- a/packages/solidity-contracts/test/messagesIncomingV3.test.ts +++ b/packages/solidity-contracts/test/messagesIncomingV3.test.ts @@ -17,6 +17,7 @@ import { computeBlockId, generateBlockHeaderLite, } from '../protocol/blockHeader'; +import { RATE_LIMIT_AMOUNT, RATE_LIMIT_DURATION } from '../protocol/constants'; import Message, { computeMessageId } from '../protocol/message'; import { randomBytes32, tai64Time } from '../protocol/utils'; import type { @@ -37,8 +38,6 @@ import { getLeafIndexKey, } from './utils/merkle'; -import { RATE_LIMIT_AMOUNT, RATE_LIMIT_DURATION } from '../protocol/constants'; - const ETH_DECIMALS = 18n; const FUEL_BASE_ASSET_DECIMALS = 9n; const BASE_ASSET_CONVERSION = 10n ** (ETH_DECIMALS - FUEL_BASE_ASSET_DECIMALS); diff --git a/packages/solidity-contracts/test/messagesOutgoingV2.test.ts b/packages/solidity-contracts/test/messagesOutgoingV2.test.ts index 8384d442..ded0ff36 100644 --- a/packages/solidity-contracts/test/messagesOutgoingV2.test.ts +++ b/packages/solidity-contracts/test/messagesOutgoingV2.test.ts @@ -24,7 +24,6 @@ import { COMMIT_COOLDOWN, TIME_TO_FINALIZE, } from './utils'; - import { addressToB256 } from './utils/addressConversion'; const { expect } = chai; diff --git a/packages/solidity-contracts/test/upgrade.ts b/packages/solidity-contracts/test/upgrade.ts index 927354d0..048d807f 100644 --- a/packages/solidity-contracts/test/upgrade.ts +++ b/packages/solidity-contracts/test/upgrade.ts @@ -5,7 +5,6 @@ import type { DeployedContractAddresses, HarnessObject, } from '../protocol/harness'; - import { setupFuel, upgradeFuel } from '../protocol/harness'; import type { UpgradeableTester } from '../typechain'; diff --git a/packages/test-utils/src/deploy-scripts/bridge.ts b/packages/test-utils/src/deploy-scripts/bridge.ts index c019d9b2..a792dbfc 100644 --- a/packages/test-utils/src/deploy-scripts/bridge.ts +++ b/packages/test-utils/src/deploy-scripts/bridge.ts @@ -9,15 +9,8 @@ import { BridgeFungibleTokenAbi__factory, ProxyAbi__factory, } from '@fuel-bridge/fungible-token'; - -import { - B256Coder, - DeployContractResult, - Provider, - Wallet, - WalletUnlocked, - ZeroBytes32, -} from 'fuels'; +import type { DeployContractResult, WalletUnlocked } from 'fuels'; +import { B256Coder, Provider, Wallet, ZeroBytes32 } from 'fuels'; const { L1_TOKEN_GATEWAY, L2_SIGNER, L2_RPC } = process.env; diff --git a/packages/test-utils/src/utils/fuels/getOrDeployL2Bridge.ts b/packages/test-utils/src/utils/fuels/getOrDeployL2Bridge.ts index 376dbc6f..f75d62af 100644 --- a/packages/test-utils/src/utils/fuels/getOrDeployL2Bridge.ts +++ b/packages/test-utils/src/utils/fuels/getOrDeployL2Bridge.ts @@ -1,9 +1,11 @@ +import type { + BridgeFungibleTokenAbi, + ProxyAbi, +} from '@fuel-bridge/fungible-token'; import { fungibleTokenBinary, bridgeProxyBinary, - BridgeFungibleTokenAbi, BridgeFungibleTokenAbi__factory, - ProxyAbi, ProxyAbi__factory, } from '@fuel-bridge/fungible-token'; import { resolveAddress, type AddressLike } from 'ethers'; From 8dacfd1e9cb5cfc8b69d4651d5d50d8d84e45495 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Mon, 26 Aug 2024 19:13:58 +0530 Subject: [PATCH 11/27] chore: some more formatting --- packages/test-utils/src/utils/fuels/getOrDeployL2Bridge.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/test-utils/src/utils/fuels/getOrDeployL2Bridge.ts b/packages/test-utils/src/utils/fuels/getOrDeployL2Bridge.ts index 6a2cc6c9..dc735d7c 100644 --- a/packages/test-utils/src/utils/fuels/getOrDeployL2Bridge.ts +++ b/packages/test-utils/src/utils/fuels/getOrDeployL2Bridge.ts @@ -87,4 +87,4 @@ export async function getOrDeployL2Bridge( l2Bridge.account = fuelAcct; return { contract: l2Bridge, proxy, implementation }; -} \ No newline at end of file +} From bbd172f2da39596d885b5b4634e1634774835077 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Mon, 26 Aug 2024 19:27:59 +0530 Subject: [PATCH 12/27] fix: test compile time error --- packages/integration-tests/tests/bridge_erc20.ts | 4 ++-- packages/integration-tests/tests/bridge_proxy.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/integration-tests/tests/bridge_erc20.ts b/packages/integration-tests/tests/bridge_erc20.ts index d4692a61..f42f1b02 100644 --- a/packages/integration-tests/tests/bridge_erc20.ts +++ b/packages/integration-tests/tests/bridge_erc20.ts @@ -44,8 +44,8 @@ describe('Bridging ERC20 tokens', async function () { let eth_testToken: Token; let eth_testTokenAddress: string; let eth_erc20GatewayAddress: string; - let fuel_bridge: BridgeFungibleToken; - let fuel_bridgeImpl: BridgeFungibleToken; + let fuel_bridge: BridgeFungibleTokenAbi; + let fuel_bridgeImpl: BridgeFungibleTokenAbi; let fuel_bridgeContractId: string; let fuel_testAssetId: string; diff --git a/packages/integration-tests/tests/bridge_proxy.ts b/packages/integration-tests/tests/bridge_proxy.ts index 8898c2a3..54094d4c 100644 --- a/packages/integration-tests/tests/bridge_proxy.ts +++ b/packages/integration-tests/tests/bridge_proxy.ts @@ -2,7 +2,7 @@ import type { TestEnvironment } from '@fuel-bridge/test-utils'; import { setupEnvironment, getOrDeployL2Bridge } from '@fuel-bridge/test-utils'; import chai from 'chai'; import type { Contract, FuelError } from 'fuels'; -import { Proxy } from '@fuel-bridge/fungible-token'; +import { ProxyAbi } from '@fuel-bridge/fungible-token'; const { expect } = chai; @@ -13,7 +13,7 @@ describe('Proxy', async function () { let env: TestEnvironment; let fuel_bridgeImpl: Contract; - let fuel_proxy: Proxy; + let fuel_proxy: ProxyAbi; before(async () => { env = await setupEnvironment({}); From e908ab0df4e1a2f6cbaae215cb49947918c73369 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Mon, 26 Aug 2024 20:43:01 +0530 Subject: [PATCH 13/27] chore; revert latest integration test updates --- packages/integration-tests/tests/bridge_erc20.ts | 8 ++++---- packages/integration-tests/tests/bridge_proxy.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/integration-tests/tests/bridge_erc20.ts b/packages/integration-tests/tests/bridge_erc20.ts index f42f1b02..4b6663c1 100644 --- a/packages/integration-tests/tests/bridge_erc20.ts +++ b/packages/integration-tests/tests/bridge_erc20.ts @@ -1,4 +1,4 @@ -import type { BridgeFungibleTokenAbi } from '@fuel-bridge/fungible-token'; +import type { BridgeFungibleToken } from '@fuel-bridge/fungible-token'; import { RATE_LIMIT_AMOUNT, RATE_LIMIT_DURATION, @@ -44,8 +44,8 @@ describe('Bridging ERC20 tokens', async function () { let eth_testToken: Token; let eth_testTokenAddress: string; let eth_erc20GatewayAddress: string; - let fuel_bridge: BridgeFungibleTokenAbi; - let fuel_bridgeImpl: BridgeFungibleTokenAbi; + let fuel_bridge: BridgeFungibleToken; + let fuel_bridgeImpl: BridgeFungibleToken; let fuel_bridgeContractId: string; let fuel_testAssetId: string; @@ -53,7 +53,7 @@ describe('Bridging ERC20 tokens', async function () { this.timeout(DEFAULT_TIMEOUT_MS); async function generateWithdrawalMessageProof( - fuel_bridge: BridgeFungibleTokenAbi, + fuel_bridge: BridgeFungibleToken, fuelTokenSender: FuelWallet, ethereumTokenReceiverAddress: string, NUM_TOKENS: bigint, diff --git a/packages/integration-tests/tests/bridge_proxy.ts b/packages/integration-tests/tests/bridge_proxy.ts index 54094d4c..8898c2a3 100644 --- a/packages/integration-tests/tests/bridge_proxy.ts +++ b/packages/integration-tests/tests/bridge_proxy.ts @@ -2,7 +2,7 @@ import type { TestEnvironment } from '@fuel-bridge/test-utils'; import { setupEnvironment, getOrDeployL2Bridge } from '@fuel-bridge/test-utils'; import chai from 'chai'; import type { Contract, FuelError } from 'fuels'; -import { ProxyAbi } from '@fuel-bridge/fungible-token'; +import { Proxy } from '@fuel-bridge/fungible-token'; const { expect } = chai; @@ -13,7 +13,7 @@ describe('Proxy', async function () { let env: TestEnvironment; let fuel_bridgeImpl: Contract; - let fuel_proxy: ProxyAbi; + let fuel_proxy: Proxy; before(async () => { env = await setupEnvironment({}); From 044686396d9c44cc43b3e09f2f962c7ee99f0206 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Tue, 27 Aug 2024 10:54:06 +0530 Subject: [PATCH 14/27] chore: add events --- .../fuelchain/FuelMessagePortal/v3/FuelMessagePortalV3.sol | 6 ++++++ .../gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/packages/solidity-contracts/contracts/fuelchain/FuelMessagePortal/v3/FuelMessagePortalV3.sol b/packages/solidity-contracts/contracts/fuelchain/FuelMessagePortal/v3/FuelMessagePortalV3.sol index f69f990a..11d5353b 100644 --- a/packages/solidity-contracts/contracts/fuelchain/FuelMessagePortal/v3/FuelMessagePortalV3.sol +++ b/packages/solidity-contracts/contracts/fuelchain/FuelMessagePortal/v3/FuelMessagePortalV3.sol @@ -8,8 +8,12 @@ contract FuelMessagePortalV3 is FuelMessagePortalV2 { using FuelBlockHeaderLib for FuelBlockHeader; using FuelBlockHeaderLiteLib for FuelBlockHeaderLite; + /// @dev Emitted when fuel chain state is emitted event FuelChainStateUpdated(address indexed sender, address indexed oldValue, address indexed newValue); + /// @dev Emitted when rate limit is reset + event ResetRateLimit(uint256 amount); + error MessageBlacklisted(); error MessageRelayFailed(); error NotSupported(); @@ -98,6 +102,8 @@ contract FuelMessagePortalV3 is FuelMessagePortalV2 { if (withdrawalAmountResetToZero || amountWithdrawnLoweredToLimit) { currentPeriodAmount = withdrawalLimitAmountToSet; } + + emit ResetRateLimit(_amount); } /////////////////////////////////////// diff --git a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol index ea6a388a..2084cb90 100644 --- a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol +++ b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol @@ -46,6 +46,9 @@ contract FuelERC20GatewayV4 is /// @dev Emitted when tokens are withdrawn from Fuel to Ethereum event Withdrawal(bytes32 indexed recipient, address indexed tokenAddress, uint256 amount); + /// @dev Emitted when rate limit is reset + event ResetRateLimit(address indexed tokenAddress, uint256 amount); + enum MessageType { DEPOSIT_TO_ADDRESS, DEPOSIT_TO_CONTRACT, @@ -188,6 +191,8 @@ contract FuelERC20GatewayV4 is if (withdrawalAmountResetToZero || amountWithdrawnLoweredToLimit) { currentPeriodAmount[_token] = withdrawalLimitAmountToSet; } + + emit ResetRateLimit(_token, _amount); } From baae9e4345e581ad564888b1c544b5052b791177 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Tue, 27 Aug 2024 10:54:32 +0530 Subject: [PATCH 15/27] chore: update unit tests --- .../test/behaviors/erc20GatewayV4.behavior.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts index befdff47..c65d7e64 100644 --- a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts +++ b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts @@ -1044,13 +1044,17 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { RATE_LIMIT_DURATION ); - await erc20Gateway + const tx = erc20Gateway .connect(deployer) .resetRateLimitAmount( token.getAddress(), rateLimitAmount.toString() ); + await expect(tx) + .to.emit(erc20Gateway, 'ResetRateLimit') + .withArgs(token.getAddress(), rateLimitAmount.toString()); + await expect( erc20Gateway .connect(deployer) From bd3e54ab5e148306ef20d5ef26e624fcf7d118be Mon Sep 17 00:00:00 2001 From: viraj124 Date: Tue, 27 Aug 2024 11:45:53 +0530 Subject: [PATCH 16/27] refactor; make rate limit duration for erc20 dynammic --- .../gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol index 2084cb90..3da4cf03 100644 --- a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol +++ b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol @@ -161,8 +161,9 @@ contract FuelERC20GatewayV4 is * @notice Resets the rate limit amount. * @param _token The token address to set rate limit for. * @param _amount The amount to reset the limit to. + * @param _rateLimitDuration The new rate limit duration. */ - function resetRateLimitAmount(address _token, uint256 _amount) external onlyRole(SET_RATE_LIMITER_ROLE) { + function resetRateLimitAmount(address _token, uint256 _amount, uint256 _rateLimitDuration) external onlyRole(SET_RATE_LIMITER_ROLE) { uint256 withdrawalLimitAmountToSet; bool amountWithdrawnLoweredToLimit; bool withdrawalAmountResetToZero; @@ -170,12 +171,14 @@ contract FuelERC20GatewayV4 is // avoid multiple SLOADS uint256 rateLimitDurationEndTimestamp = currentPeriodEnd[_token]; + rateLimitDuration[_token] = _rateLimitDuration; + if (rateLimitDurationEndTimestamp == 0) revert RateLimitNotInitialized(); // if period has expired then currentPeriodAmount is zero if (rateLimitDurationEndTimestamp < block.timestamp) { unchecked { - currentPeriodEnd[_token] = block.timestamp + rateLimitDuration[_token]; + currentPeriodEnd[_token] = block.timestamp + _rateLimitDuration; } withdrawalAmountResetToZero = true; } else { From 53a75d381c3879c3f9d53c66afde67a7aa78d838 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Tue, 27 Aug 2024 11:46:22 +0530 Subject: [PATCH 17/27] refactor: update tests --- .../integration-tests/tests/bridge_erc20.ts | 18 +++++++++++++++--- .../behaviors/erc20GatewayV4.behavior.test.ts | 6 ++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/integration-tests/tests/bridge_erc20.ts b/packages/integration-tests/tests/bridge_erc20.ts index 4b6663c1..a40ff9a9 100644 --- a/packages/integration-tests/tests/bridge_erc20.ts +++ b/packages/integration-tests/tests/bridge_erc20.ts @@ -420,7 +420,11 @@ describe('Bridging ERC20 tokens', async function () { await env.eth.fuelERC20Gateway .connect(deployer) - .resetRateLimitAmount(eth_testTokenAddress, parseEther(newRateLimit)); + .resetRateLimitAmount( + eth_testTokenAddress, + parseEther(newRateLimit), + RATE_LIMIT_DURATION + ); const currentWithdrawnAmountAfterSettingLimit = await env.eth.fuelERC20Gateway.currentPeriodAmount( @@ -449,7 +453,11 @@ describe('Bridging ERC20 tokens', async function () { await env.eth.fuelERC20Gateway .connect(deployer) - .resetRateLimitAmount(eth_testTokenAddress, parseEther(newRateLimit)); + .resetRateLimitAmount( + eth_testTokenAddress, + parseEther(newRateLimit), + RATE_LIMIT_DURATION + ); // withdraw tokens back to the base chain withdrawMessageProof = await generateWithdrawalMessageProof( @@ -499,7 +507,11 @@ describe('Bridging ERC20 tokens', async function () { await env.eth.fuelERC20Gateway .connect(deployer) - .resetRateLimitAmount(eth_testTokenAddress, parseEther(newRateLimit)); + .resetRateLimitAmount( + eth_testTokenAddress, + parseEther(newRateLimit), + RATE_LIMIT_DURATION + ); const currentPeriodEndAfterSettingLimit = await env.eth.fuelERC20Gateway.currentPeriodEnd(eth_testTokenAddress); diff --git a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts index c65d7e64..27f21d2f 100644 --- a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts +++ b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts @@ -1019,7 +1019,8 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { .connect(deployer) .resetRateLimitAmount( token.getAddress(), - rateLimitAmount.toString() + rateLimitAmount.toString(), + RATE_LIMIT_DURATION ) ).to.be.revertedWithCustomError( erc20Gateway, @@ -1048,7 +1049,8 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { .connect(deployer) .resetRateLimitAmount( token.getAddress(), - rateLimitAmount.toString() + rateLimitAmount.toString(), + RATE_LIMIT_DURATION ); await expect(tx) From 0ea7759972ef29a45b14dc4e2bf1aa18f3e9e273 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Tue, 27 Aug 2024 11:52:42 +0530 Subject: [PATCH 18/27] chore: small touchup --- .../gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol index 3da4cf03..845f1213 100644 --- a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol +++ b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol @@ -171,10 +171,11 @@ contract FuelERC20GatewayV4 is // avoid multiple SLOADS uint256 rateLimitDurationEndTimestamp = currentPeriodEnd[_token]; - rateLimitDuration[_token] = _rateLimitDuration; - if (rateLimitDurationEndTimestamp == 0) revert RateLimitNotInitialized(); + // set new rate limit duration + rateLimitDuration[_token] = _rateLimitDuration; + // if period has expired then currentPeriodAmount is zero if (rateLimitDurationEndTimestamp < block.timestamp) { unchecked { From 7007dce856d3bfc5f26b3a5fed47a1f3334cdb47 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Tue, 27 Aug 2024 12:05:42 +0530 Subject: [PATCH 19/27] refactor: add missing unchecked --- .../fuelchain/FuelMessagePortal/v3/FuelMessagePortalV3.sol | 4 +++- .../messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/solidity-contracts/contracts/fuelchain/FuelMessagePortal/v3/FuelMessagePortalV3.sol b/packages/solidity-contracts/contracts/fuelchain/FuelMessagePortal/v3/FuelMessagePortalV3.sol index 11d5353b..43077042 100644 --- a/packages/solidity-contracts/contracts/fuelchain/FuelMessagePortal/v3/FuelMessagePortalV3.sol +++ b/packages/solidity-contracts/contracts/fuelchain/FuelMessagePortal/v3/FuelMessagePortalV3.sol @@ -230,7 +230,9 @@ contract FuelMessagePortalV3 is FuelMessagePortalV2 { uint256 currentPeriodAmountTemp; if (currentPeriodEnd < block.timestamp) { - currentPeriodEnd = block.timestamp + rateLimitDuration; + unchecked { + currentPeriodEnd = block.timestamp + rateLimitDuration; + } currentPeriodAmountTemp = _withdrawnAmount; } else { unchecked { diff --git a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol index 845f1213..db6fb9b9 100644 --- a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol +++ b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol @@ -421,7 +421,9 @@ contract FuelERC20GatewayV4 is uint256 currentPeriodAmountTemp; if (currentPeriodEnd[_token] < block.timestamp) { - currentPeriodEnd[_token] = block.timestamp + rateLimitDuration[_token]; + unchecked { + currentPeriodEnd[_token] = block.timestamp + rateLimitDuration[_token]; + } currentPeriodAmountTemp = _withdrawnAmount; } else { unchecked { From 4db4c3c7ed2ab3f0cf4ad5c991fd6960c1981f17 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Tue, 27 Aug 2024 16:53:45 +0530 Subject: [PATCH 20/27] refactor: update events --- .../FuelERC20Gateway/FuelERC20GatewayV4.sol | 14 ++++++++------ .../test/behaviors/erc20GatewayV4.behavior.test.ts | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol index db6fb9b9..9b579f8a 100644 --- a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol +++ b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol @@ -47,7 +47,7 @@ contract FuelERC20GatewayV4 is event Withdrawal(bytes32 indexed recipient, address indexed tokenAddress, uint256 amount); /// @dev Emitted when rate limit is reset - event ResetRateLimit(address indexed tokenAddress, uint256 amount); + event RateLimitUpdated(address indexed tokenAddress, uint256 amount); enum MessageType { DEPOSIT_TO_ADDRESS, @@ -74,10 +74,10 @@ contract FuelERC20GatewayV4 is bool public whitelistRequired; bytes32 public assetIssuerId; - mapping(address => uint256) _deposits; - mapping(address => uint256) _depositLimits; - mapping(address => uint256) _decimalsCache; - mapping(bytes32 => bool) _isBridge; + mapping(address => uint256) private _deposits; + mapping(address => uint256) private _depositLimits; + mapping(address => uint256) private _decimalsCache; + mapping(bytes32 => bool) private _isBridge; /// @notice Amounts already withdrawn this period for each token. mapping(address => uint256) public rateLimitDuration; @@ -155,6 +155,8 @@ contract FuelERC20GatewayV4 is rateLimitDuration[_token] = _rateLimitDuration; currentPeriodEnd[_token] = block.timestamp + _rateLimitDuration; limitAmount[_token] = _limitAmount; + + emit RateLimitUpdated(_token, _amount); } /** @@ -196,7 +198,7 @@ contract FuelERC20GatewayV4 is currentPeriodAmount[_token] = withdrawalLimitAmountToSet; } - emit ResetRateLimit(_token, _amount); + emit RateLimitUpdated(_token, _amount); } diff --git a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts index 27f21d2f..4af1b9c0 100644 --- a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts +++ b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts @@ -1054,7 +1054,7 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { ); await expect(tx) - .to.emit(erc20Gateway, 'ResetRateLimit') + .to.emit(erc20Gateway, 'RateLimitUpdated') .withArgs(token.getAddress(), rateLimitAmount.toString()); await expect( From a3e45702e5d9c735c35022dc04250693a04edc7d Mon Sep 17 00:00:00 2001 From: viraj124 Date: Tue, 27 Aug 2024 17:03:56 +0530 Subject: [PATCH 21/27] fix: compilation --- .../messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol index 9b579f8a..426dab4d 100644 --- a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol +++ b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol @@ -156,7 +156,7 @@ contract FuelERC20GatewayV4 is currentPeriodEnd[_token] = block.timestamp + _rateLimitDuration; limitAmount[_token] = _limitAmount; - emit RateLimitUpdated(_token, _amount); + emit RateLimitUpdated(_token, _limitAmount); } /** From d50ae01d05ef7d5b088ee0c67ed4b18e46e0cb32 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Tue, 27 Aug 2024 22:41:44 +0530 Subject: [PATCH 22/27] refactor: add conditional rate limit check --- .../messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol index 426dab4d..bf1e14e5 100644 --- a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol +++ b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol @@ -307,8 +307,8 @@ contract FuelERC20GatewayV4 is uint8 decimals = _getTokenDecimals(tokenAddress); uint256 amount = _adjustWithdrawalDecimals(decimals, l2BurntAmount); - // rate limit check - _addWithdrawnAmount(tokenAddress, amount); + // rate limit check only if rate limit is initialized + if (currentPeriodEnd[tokenAddress] != 0) _addWithdrawnAmount(tokenAddress, amount); //reduce deposit balance and transfer tokens (math will underflow if amount is larger than allowed) _deposits[tokenAddress] = _deposits[tokenAddress] - l2BurntAmount; From 73e64d15a830aac946d21a82f6e33a351d338191 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Tue, 27 Aug 2024 22:42:22 +0530 Subject: [PATCH 23/27] refactor: add test for conditional rate limit check --- .../behaviors/erc20GatewayV4.behavior.test.ts | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts index 4af1b9c0..74190388 100644 --- a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts +++ b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts @@ -1071,6 +1071,68 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { ); }); + it('does not update rate limit vars when it is not initialized', async () => { + const { + erc20Gateway, + fuelMessagePortal, + signers: [deployer, user], + assetIssuerId, + } = env; + + const amount = parseUnits( + random(0.01, 1, true).toFixed(decimals), + Number(decimals) + ); + let recipient = randomBytes32(); + + await fuelMessagePortal + .connect(deployer) + .setMessageSender(assetIssuerId); + + const impersonatedPortal = await impersonateAccount( + fuelMessagePortal, + hre + ); + + await token.mint(user, amount); + await token.approve(erc20Gateway, MaxUint256); + + await erc20Gateway.connect(user).deposit(recipient, token, amount); + const withdrawAmount = amount / 4n; + + // Withdrawal + recipient = randomAddress(); + const withdrawalTx = erc20Gateway + .connect(impersonatedPortal) + .finalizeWithdrawal(recipient, token, withdrawAmount, 0); + + await withdrawalTx; + + const expectedTokenTotals = amount - withdrawAmount; + expect(await erc20Gateway.tokensDeposited(token)).to.be.equal( + expectedTokenTotals + ); + + await expect(withdrawalTx).to.changeTokenBalances( + token, + [erc20Gateway, recipient], + [withdrawAmount * -1n, withdrawAmount] + ); + await expect(withdrawalTx) + .to.emit(erc20Gateway, 'Withdrawal') + .withArgs( + zeroPadValue(recipient, 32).toLowerCase(), + token, + withdrawAmount + ); + + // check rate limit params + const withdrawnAmountAfterRelay = + await erc20Gateway.currentPeriodAmount(token.getAddress()); + + await expect(withdrawnAmountAfterRelay == 0n).to.be.true; + }); + it('reduces deposits and transfers out without upscaling', async () => { const { erc20Gateway, From 0aeaef62ae5ff5c0a272daada9cbdfced332e7ac Mon Sep 17 00:00:00 2001 From: viraj124 Date: Tue, 27 Aug 2024 23:14:12 +0530 Subject: [PATCH 24/27] chore: revert some formatting changes --- packages/solidity-contracts/test/erc721Gateway.test.ts | 1 + packages/solidity-contracts/test/messagesIncomingV3.test.ts | 3 ++- packages/solidity-contracts/test/messagesOutgoingV2.test.ts | 1 + packages/solidity-contracts/test/upgrade.ts | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/solidity-contracts/test/erc721Gateway.test.ts b/packages/solidity-contracts/test/erc721Gateway.test.ts index c9de32d8..1b6f2725 100644 --- a/packages/solidity-contracts/test/erc721Gateway.test.ts +++ b/packages/solidity-contracts/test/erc721Gateway.test.ts @@ -32,6 +32,7 @@ import { TIME_TO_FINALIZE, COMMIT_COOLDOWN, } from './utils'; + import { createBlock } from './utils/createBlock'; const { expect } = chai; diff --git a/packages/solidity-contracts/test/messagesIncomingV3.test.ts b/packages/solidity-contracts/test/messagesIncomingV3.test.ts index 442c1483..51e90634 100644 --- a/packages/solidity-contracts/test/messagesIncomingV3.test.ts +++ b/packages/solidity-contracts/test/messagesIncomingV3.test.ts @@ -17,7 +17,6 @@ import { computeBlockId, generateBlockHeaderLite, } from '../protocol/blockHeader'; -import { RATE_LIMIT_AMOUNT, RATE_LIMIT_DURATION } from '../protocol/constants'; import Message, { computeMessageId } from '../protocol/message'; import { randomBytes32, tai64Time } from '../protocol/utils'; import type { @@ -38,6 +37,8 @@ import { getLeafIndexKey, } from './utils/merkle'; +import { RATE_LIMIT_AMOUNT, RATE_LIMIT_DURATION } from '../protocol/constants'; + const ETH_DECIMALS = 18n; const FUEL_BASE_ASSET_DECIMALS = 9n; const BASE_ASSET_CONVERSION = 10n ** (ETH_DECIMALS - FUEL_BASE_ASSET_DECIMALS); diff --git a/packages/solidity-contracts/test/messagesOutgoingV2.test.ts b/packages/solidity-contracts/test/messagesOutgoingV2.test.ts index ded0ff36..8384d442 100644 --- a/packages/solidity-contracts/test/messagesOutgoingV2.test.ts +++ b/packages/solidity-contracts/test/messagesOutgoingV2.test.ts @@ -24,6 +24,7 @@ import { COMMIT_COOLDOWN, TIME_TO_FINALIZE, } from './utils'; + import { addressToB256 } from './utils/addressConversion'; const { expect } = chai; diff --git a/packages/solidity-contracts/test/upgrade.ts b/packages/solidity-contracts/test/upgrade.ts index 048d807f..927354d0 100644 --- a/packages/solidity-contracts/test/upgrade.ts +++ b/packages/solidity-contracts/test/upgrade.ts @@ -5,6 +5,7 @@ import type { DeployedContractAddresses, HarnessObject, } from '../protocol/harness'; + import { setupFuel, upgradeFuel } from '../protocol/harness'; import type { UpgradeableTester } from '../typechain'; From 0876f6c084592f7ec010d46b6af1062ed94a88a8 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Tue, 27 Aug 2024 23:25:38 +0530 Subject: [PATCH 25/27] chore; revert visibility change --- .../gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol index bf1e14e5..e0d3ffa5 100644 --- a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol +++ b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol @@ -74,10 +74,10 @@ contract FuelERC20GatewayV4 is bool public whitelistRequired; bytes32 public assetIssuerId; - mapping(address => uint256) private _deposits; - mapping(address => uint256) private _depositLimits; - mapping(address => uint256) private _decimalsCache; - mapping(bytes32 => bool) private _isBridge; + mapping(address => uint256) _deposits; + mapping(address => uint256) _depositLimits; + mapping(address => uint256) _decimalsCache; + mapping(bytes32 => bool) _isBridge; /// @notice Amounts already withdrawn this period for each token. mapping(address => uint256) public rateLimitDuration; From a59a2e21fce3c2a11418f6d40a24b12c6f6e1128 Mon Sep 17 00:00:00 2001 From: viraj124 Date: Wed, 28 Aug 2024 16:10:30 +0530 Subject: [PATCH 26/27] refactor: remove redunddant initialize code for erc20 limiter --- .../FuelERC20Gateway/FuelERC20GatewayV4.sol | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol index e0d3ffa5..9f8aa7c9 100644 --- a/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol +++ b/packages/solidity-contracts/contracts/messaging/gateway/FuelERC20Gateway/FuelERC20GatewayV4.sol @@ -36,9 +36,7 @@ contract FuelERC20GatewayV4 is error CannotWithdrawZero(); error InvalidSender(); error InvalidAmount(); - error RateLimitAlreadySet(); error RateLimitExceeded(); - error RateLimitNotInitialized(); /// @dev Emitted when tokens are deposited from Ethereum to Fuel event Deposit(bytes32 indexed sender, address indexed tokenAddress, uint256 amount); @@ -74,10 +72,10 @@ contract FuelERC20GatewayV4 is bool public whitelistRequired; bytes32 public assetIssuerId; - mapping(address => uint256) _deposits; - mapping(address => uint256) _depositLimits; - mapping(address => uint256) _decimalsCache; - mapping(bytes32 => bool) _isBridge; + mapping(address => uint256) internal _deposits; + mapping(address => uint256) internal _depositLimits; + mapping(address => uint256) internal _decimalsCache; + mapping(bytes32 => bool) internal _isBridge; /// @notice Amounts already withdrawn this period for each token. mapping(address => uint256) public rateLimitDuration; @@ -143,22 +141,6 @@ contract FuelERC20GatewayV4 is require(success); } - /** - * @notice Intitializing rate limit params for each token. - * @param _token The token address to set rate limit params for. - * @param _limitAmount The amount to reset the limit to. - * @param _rateLimitDuration rate limit duration. - */ - function initializeRateLimit(address _token, uint256 _limitAmount, uint256 _rateLimitDuration) external onlyRole(SET_RATE_LIMITER_ROLE) { - if (currentPeriodEnd[_token] != 0) revert RateLimitAlreadySet(); - - rateLimitDuration[_token] = _rateLimitDuration; - currentPeriodEnd[_token] = block.timestamp + _rateLimitDuration; - limitAmount[_token] = _limitAmount; - - emit RateLimitUpdated(_token, _limitAmount); - } - /** * @notice Resets the rate limit amount. * @param _token The token address to set rate limit for. @@ -172,8 +154,6 @@ contract FuelERC20GatewayV4 is // avoid multiple SLOADS uint256 rateLimitDurationEndTimestamp = currentPeriodEnd[_token]; - - if (rateLimitDurationEndTimestamp == 0) revert RateLimitNotInitialized(); // set new rate limit duration rateLimitDuration[_token] = _rateLimitDuration; From ea95dffbfa50e44205cc07f8cd84e15df232687d Mon Sep 17 00:00:00 2001 From: viraj124 Date: Wed, 28 Aug 2024 16:11:20 +0530 Subject: [PATCH 27/27] refactor: update tests --- .../integration-tests/tests/bridge_erc20.ts | 2 +- .../behaviors/erc20GatewayV4.behavior.test.ts | 50 ++----------------- .../test/erc20GatewayV4.test.ts | 8 --- 3 files changed, 4 insertions(+), 56 deletions(-) diff --git a/packages/integration-tests/tests/bridge_erc20.ts b/packages/integration-tests/tests/bridge_erc20.ts index a40ff9a9..225a9718 100644 --- a/packages/integration-tests/tests/bridge_erc20.ts +++ b/packages/integration-tests/tests/bridge_erc20.ts @@ -160,7 +160,7 @@ describe('Bridging ERC20 tokens', async function () { // initializing rate limit params for the token await env.eth.fuelERC20Gateway .connect(env.eth.deployer) - .initializeRateLimit( + .resetRateLimitAmount( eth_testTokenAddress, RATE_LIMIT_AMOUNT.toString(), RATE_LIMIT_DURATION diff --git a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts index 74190388..1f269d85 100644 --- a/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts +++ b/packages/solidity-contracts/test/behaviors/erc20GatewayV4.behavior.test.ts @@ -1005,7 +1005,7 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { ).connect(user); }); - it('reverts when rate limit is reset without initializing it first', async () => { + it('emits event when rate limit is set', async () => { const { erc20Gateway, signers: [deployer, user], @@ -1014,37 +1014,6 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { const rateLimitAmount = RATE_LIMIT_AMOUNT / 10 ** (STANDARD_TOKEN_DECIMALS - decimals); - await expect( - erc20Gateway - .connect(deployer) - .resetRateLimitAmount( - token.getAddress(), - rateLimitAmount.toString(), - RATE_LIMIT_DURATION - ) - ).to.be.revertedWithCustomError( - erc20Gateway, - 'RateLimitNotInitialized' - ); - }); - - it('reverts when rate limit is initialized again', async () => { - const { - erc20Gateway, - signers: [deployer, user], - } = env; - - const rateLimitAmount = - RATE_LIMIT_AMOUNT / 10 ** (STANDARD_TOKEN_DECIMALS - decimals); - - await erc20Gateway - .connect(deployer) - .initializeRateLimit( - token.getAddress(), - rateLimitAmount.toString(), - RATE_LIMIT_DURATION - ); - const tx = erc20Gateway .connect(deployer) .resetRateLimitAmount( @@ -1056,19 +1025,6 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { await expect(tx) .to.emit(erc20Gateway, 'RateLimitUpdated') .withArgs(token.getAddress(), rateLimitAmount.toString()); - - await expect( - erc20Gateway - .connect(deployer) - .initializeRateLimit( - token.getAddress(), - rateLimitAmount.toString(), - RATE_LIMIT_DURATION - ) - ).to.be.revertedWithCustomError( - erc20Gateway, - 'RateLimitAlreadySet' - ); }); it('does not update rate limit vars when it is not initialized', async () => { @@ -1146,7 +1102,7 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { await erc20Gateway .connect(deployer) - .initializeRateLimit( + .resetRateLimitAmount( token.getAddress(), rateLimitAmount.toString(), RATE_LIMIT_DURATION @@ -1278,7 +1234,7 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise) { await erc20Gateway .connect(deployer) - .initializeRateLimit( + .resetRateLimitAmount( token.getAddress(), amount.toString(), RATE_LIMIT_DURATION diff --git a/packages/solidity-contracts/test/erc20GatewayV4.test.ts b/packages/solidity-contracts/test/erc20GatewayV4.test.ts index 905e78fa..d0400ebe 100644 --- a/packages/solidity-contracts/test/erc20GatewayV4.test.ts +++ b/packages/solidity-contracts/test/erc20GatewayV4.test.ts @@ -38,14 +38,6 @@ describe('erc20GatewayV4', () => { await erc20Gateway.connect(deployer).setAssetIssuerId(assetIssuerId); - await erc20Gateway - .connect(deployer) - .initializeRateLimit( - token.getAddress(), - RATE_LIMIT_AMOUNT.toString(), - RATE_LIMIT_DURATION - ); - return { fuelMessagePortal, erc20Gateway,