Skip to content

Commit

Permalink
Merge branch 'main' into deploy/upgradeTest
Browse files Browse the repository at this point in the history
  • Loading branch information
viraj124 authored Oct 2, 2024
2 parents 794e62c + 786a784 commit 5b8bfc6
Show file tree
Hide file tree
Showing 14 changed files with 202 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .changeset/healthy-apricots-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fuel-bridge/solidity-contracts': minor
---

update comment
5 changes: 5 additions & 0 deletions .changeset/late-pears-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fuel-bridge/solidity-contracts': minor
---

add zero address check for time to finalise
5 changes: 5 additions & 0 deletions .changeset/smooth-carrots-visit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fuel-bridge/solidity-contracts': minor
---

add rate limit disabling/re-enabling
5 changes: 5 additions & 0 deletions .changeset/tough-bugs-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fuel-bridge/solidity-contracts': minor
---

update naming convention for immutable var
5 changes: 5 additions & 0 deletions .changeset/violet-cups-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fuel-bridge/solidity-contracts': patch
---

Swap symbol and name in the metadata payload
8 changes: 8 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,14 @@ Additionally, it is a de-facto practice in the space to use 18 decimals to repre

DApps that enable bridge operations should observe this limitation and truncate the amounts accordingly to avoid reverted transactions.

This essentially means that if a token with `decimals > 9` is bridged to Fuel, then the L2 token minted will have 9 decimals. However, if a token with `decimals <= 9` is bridged to Fuel, then the L2 token minted will have the same number of decimals as the L1 token.

| Token | L1 Token Decimals | L2 Token Decimals |
| ----- | :---------------: | ----------------: |
| USDC | 6 | 6 |
| ETH | 18 | 9 |
| FBTC | 8 | 8 |

## Incompatibilities, risks and SPoF

> SPoF stands for Single Points of Failure
Expand Down
17 changes: 17 additions & 0 deletions packages/integration-tests/tests/bridge_erc20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ describe('Bridging ERC20 tokens', async function () {
RATE_LIMIT_DURATION
);

await env.eth.fuelERC20Gateway
.connect(env.eth.deployer)
.updateRateLimitStatus(eth_testTokenAddress, true);

const { value: expectedGatewayContractId } = await fuel_bridge.functions
.bridged_token_gateway()
.addContracts([fuel_bridge, fuel_bridgeImpl])
Expand Down Expand Up @@ -352,6 +356,19 @@ describe('Bridging ERC20 tokens', async function () {

const txResult = await tx.waitForResult();
expect(txResult.status).to.equal('success');

const fuel_name = (
await fuel_bridge.functions.name({ bits: fuel_testAssetId }).dryRun()
).value;
const fuel_symbol = (
await fuel_bridge.functions.symbol({ bits: fuel_testAssetId }).dryRun()
).value;

const eth_name = await eth_testToken.name();
const eth_symbol = await eth_testToken.symbol();

expect(fuel_name).to.equal(eth_name);
expect(fuel_symbol).to.equal(eth_symbol);
});
});

Expand Down
4 changes: 4 additions & 0 deletions packages/integration-tests/tests/bridge_mainnet_tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ describe('Bridge mainnet tokens', function () {
rateLimitAmount.toString(),
RATE_LIMIT_DURATION
);

await env.eth.fuelERC20Gateway
.connect(env.eth.deployer)
.updateRateLimitStatus(tokenAddress, true);
}
);

Expand Down
4 changes: 2 additions & 2 deletions packages/integration-tests/tests/transfer_eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ describe('Transferring ETH', async function () {
it('Rate limit parameters are updated when the initial duration is over', async () => {
const deployer = await env.eth.deployer;

rateLimitDuration = await env.eth.fuelMessagePortal.rateLimitDuration();
rateLimitDuration = await env.eth.fuelMessagePortal.RATE_LIMIT_DURATION();

// fast forward time
await hardhatSkipTime(
Expand Down Expand Up @@ -484,7 +484,7 @@ describe('Transferring ETH', async function () {
});

it('Rate limit parameters are updated when new limit is set after the initial duration', async () => {
rateLimitDuration = await env.eth.fuelMessagePortal.rateLimitDuration();
rateLimitDuration = await env.eth.fuelMessagePortal.RATE_LIMIT_DURATION();

const deployer = await env.eth.deployer;
const newRateLimit = `40`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ contract FuelChainState is Initializable, PausableUpgradeable, AccessControlUpgr
////////////
// Errors //
////////////

error TimeToFinalizeTooLarge();
error CannotRecommit();
error CommitCooldownTooLarge();
error FinalizationIsGtCooldown();
error InvalidTimeToFinalize();
error TimeToFinalizeTooLarge();
error UnknownBlock();
error CannotRecommit();

/////////////
// Storage //
Expand All @@ -74,6 +74,10 @@ contract FuelChainState is Initializable, PausableUpgradeable, AccessControlUpgr
/// @dev assumes 1 block per second in the L2 chain
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(uint256 timeToFinalize, uint256 blocksPerCommitInterval, uint32 commitCooldown) {
if (timeToFinalize == 0) {
revert InvalidTimeToFinalize();
}

if (timeToFinalize > commitCooldown) {
revert FinalizationIsGtCooldown();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract FuelMessagePortalV3 is FuelMessagePortalV2 {
bytes32 public constant SET_RATE_LIMITER_ROLE = keccak256("SET_RATE_LIMITER_ROLE");

/// @notice Duration after which rate limit resets.
uint256 public immutable rateLimitDuration;
uint256 public immutable RATE_LIMIT_DURATION;

/// @notice Flag to indicate whether withdrawals are paused or not.
bool public withdrawalsPaused;
Expand All @@ -41,7 +41,7 @@ contract FuelMessagePortalV3 is FuelMessagePortalV2 {
uint256 public limitAmount;

constructor(uint256 _depositLimitGlobal, uint256 _rateLimitDuration) FuelMessagePortalV2(_depositLimitGlobal) {
rateLimitDuration = _rateLimitDuration;
RATE_LIMIT_DURATION = _rateLimitDuration;
_disableInitializers();
}

Expand Down Expand Up @@ -85,7 +85,7 @@ contract FuelMessagePortalV3 is FuelMessagePortalV2 {
// if period has expired then currentPeriodAmount is zero
if (currentPeriodEnd < block.timestamp) {
unchecked {
currentPeriodEnd = block.timestamp + rateLimitDuration;
currentPeriodEnd = block.timestamp + RATE_LIMIT_DURATION;
}

currentPeriodAmount = 0;
Expand Down Expand Up @@ -221,7 +221,7 @@ contract FuelMessagePortalV3 is FuelMessagePortalV2 {

if (currentPeriodEnd < block.timestamp) {
unchecked {
currentPeriodEnd = block.timestamp + rateLimitDuration;
currentPeriodEnd = block.timestamp + RATE_LIMIT_DURATION;
}
currentPeriodAmountTemp = _withdrawnAmount;
} else {
Expand All @@ -246,7 +246,7 @@ contract FuelMessagePortalV3 is FuelMessagePortalV2 {
_grantRole(SET_RATE_LIMITER_ROLE, msg.sender);

// initializing rate limit var
currentPeriodEnd = block.timestamp + rateLimitDuration;
currentPeriodEnd = block.timestamp + RATE_LIMIT_DURATION;
limitAmount = _limitAmount;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ contract FuelERC20GatewayV4 is
/// @dev Emitted when rate limit is reset
event RateLimitUpdated(address indexed tokenAddress, uint256 amount);

/// @dev Emitted when rate limit is enabled/disabled
event RateLimitStatusUpdated(address indexed tokenAddress, bool status);

enum MessageType {
DEPOSIT_TO_ADDRESS,
DEPOSIT_TO_CONTRACT,
Expand Down Expand Up @@ -77,7 +80,7 @@ contract FuelERC20GatewayV4 is
mapping(address => uint256) internal _depositLimits;
mapping(address => uint256) internal _decimalsCache;

/// @notice Amounts already withdrawn this period for each token.
/// @notice Tracks rate limit duration for each token.
mapping(address => uint256) public rateLimitDuration;

/// @notice Amounts already withdrawn this period for each token.
Expand All @@ -89,7 +92,12 @@ contract FuelERC20GatewayV4 is
/// @notice The eth withdrawal limit amount for each token.
mapping(address => uint256) public limitAmount;

/// @notice disabling initialization
/// @notice Flag to indicate rate limit status for each token, whether disabled or enabled.
// if `true` it is enabled
// if `false` it is disabled
mapping(address => bool) public rateLimitStatus;

/// @notice disabling initialization
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
Expand Down Expand Up @@ -155,13 +163,17 @@ contract FuelERC20GatewayV4 is
* Fuel's implementation is inspired by the Linea Bridge dessign (https://github.com/Consensys/linea-contracts/blob/main/contracts/messageService/lib/RateLimiter.sol)
* Only point of difference from the linea implementation is that when currentPeriodEnd >= block.timestamp then if the new rate limit amount is less than the currentPeriodAmount, then currentPeriodAmount is not updated this makes sure that if rate limit is first reduced & then increased within the rate limit duration then any extra amount can't be withdrawn
*/
function resetRateLimitAmount(address _token, uint256 _amount, uint256 _rateLimitDuration) external onlyRole(SET_RATE_LIMITER_ROLE) {
function resetRateLimitAmount(
address _token,
uint256 _amount,
uint256 _rateLimitDuration
) external onlyRole(SET_RATE_LIMITER_ROLE) {
// avoid multiple SLOADS
uint256 rateLimitDurationEndTimestamp = currentPeriodEnd[_token];

// set new rate limit duration
rateLimitDuration[_token] = _rateLimitDuration;

// if period has expired then currentPeriodAmount is zero
if (rateLimitDurationEndTimestamp < block.timestamp) {
unchecked {
Expand All @@ -176,6 +188,17 @@ contract FuelERC20GatewayV4 is
emit RateLimitUpdated(_token, _amount);
}

/**
* @notice updates rate limit status by disabling/re-enabling rate limit.
* @param _token The token address to update rate limit status for.
* @param _rateLimitStatus bool flag to disable or re-enable rate limit.
*/
function updateRateLimitStatus(address _token, bool _rateLimitStatus) external onlyRole(SET_RATE_LIMITER_ROLE) {
rateLimitStatus[_token] = _rateLimitStatus;

emit RateLimitStatusUpdated(_token, _rateLimitStatus);
}

//////////////////////
// Public Functions //
//////////////////////
Expand Down Expand Up @@ -258,8 +281,8 @@ contract FuelERC20GatewayV4 is
abi.encode(
tokenAddress,
uint256(0), // token_id = 0 for all erc20 deposits
IERC20MetadataUpgradeable(tokenAddress).symbol(),
IERC20MetadataUpgradeable(tokenAddress).name()
IERC20MetadataUpgradeable(tokenAddress).name(),
IERC20MetadataUpgradeable(tokenAddress).symbol()
)
);
sendMessage(CommonPredicates.CONTRACT_MESSAGE_PREDICATE, metadataMessage);
Expand Down Expand Up @@ -287,8 +310,9 @@ contract FuelERC20GatewayV4 is
uint8 decimals = _getTokenDecimals(tokenAddress);
uint256 amount = _adjustWithdrawalDecimals(decimals, l2BurntAmount);

// rate limit check only if rate limit is initialized
if (currentPeriodEnd[tokenAddress] != 0) _addWithdrawnAmount(tokenAddress, amount);
// rate limit check is only performed when it is enabled
// if the rate limit has not been initialised then the tx will revert with `RateLimitExceeded()`
if (rateLimitStatus[tokenAddress]) _addWithdrawnAmount(tokenAddress, amount);

//reduce deposit balance and transfer tokens (math will underflow if amount is larger than allowed)
_deposits[tokenAddress] = _deposits[tokenAddress] - l2BurntAmount;
Expand Down Expand Up @@ -404,12 +428,12 @@ contract FuelERC20GatewayV4 is

if (currentPeriodEnd[_token] < block.timestamp) {
unchecked {
currentPeriodEnd[_token] = block.timestamp + rateLimitDuration[_token];
currentPeriodEnd[_token] = block.timestamp + rateLimitDuration[_token];
}
currentPeriodAmountTemp = _withdrawnAmount;
} else {
unchecked {
currentPeriodAmountTemp = currentPeriodAmount[_token] + _withdrawnAmount;
currentPeriodAmountTemp = currentPeriodAmount[_token] + _withdrawnAmount;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1039,7 +1039,7 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise<Env>) {
const rateLimitAmount =
RATE_LIMIT_AMOUNT / 10 ** (STANDARD_TOKEN_DECIMALS - decimals);

const tx = erc20Gateway
let tx = erc20Gateway
.connect(deployer)
.resetRateLimitAmount(
token.getAddress(),
Expand All @@ -1050,6 +1050,14 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise<Env>) {
await expect(tx)
.to.emit(erc20Gateway, 'RateLimitUpdated')
.withArgs(token.getAddress(), rateLimitAmount.toString());

tx = erc20Gateway
.connect(deployer)
.updateRateLimitStatus(token, true);

await expect(tx)
.to.emit(erc20Gateway, 'RateLimitStatusUpdated')
.withArgs(token.getAddress(), true);
});

it('does not update rate limit vars when it is not initialized', async () => {
Expand Down Expand Up @@ -1133,6 +1141,13 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise<Env>) {
RATE_LIMIT_DURATION
);

await erc20Gateway
.connect(deployer)
.updateRateLimitStatus(token, true);

const status = await erc20Gateway.rateLimitStatus(token);
expect(status).to.be.true;

const amount = parseUnits(
random(0.01, 1, true).toFixed(decimals),
Number(decimals)
Expand Down Expand Up @@ -1238,6 +1253,10 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise<Env>) {
RATE_LIMIT_DURATION
);

await erc20Gateway
.connect(deployer)
.updateRateLimitStatus(token, true);

const amount = parseUnits('10', Number(decimals));
const recipient = randomBytes32();

Expand Down Expand Up @@ -1273,6 +1292,7 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise<Env>) {

const currentWithdrawnAmountBeforeSettingLimit =
await erc20Gateway.currentPeriodAmount(token.getAddress());

rateLimitAmount =
RATE_LIMIT_AMOUNT /
5 /
Expand All @@ -1289,6 +1309,10 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise<Env>) {
const currentWithdrawnAmountAfterSettingLimit =
await erc20Gateway.currentPeriodAmount(token.getAddress());

expect(currentWithdrawnAmountAfterSettingLimit).to.be.equal(
withdrawAmount
);

expect(currentWithdrawnAmountAfterSettingLimit).to.be.equal(
currentWithdrawnAmountBeforeSettingLimit
);
Expand Down Expand Up @@ -1340,6 +1364,10 @@ export function behavesLikeErc20GatewayV4(fixture: () => Promise<Env>) {
RATE_LIMIT_DURATION
);

await erc20Gateway
.connect(deployer)
.updateRateLimitStatus(token, true);

const amount = parseUnits('10', Number(decimals));
const recipient = randomBytes32();

Expand Down
Loading

0 comments on commit 5b8bfc6

Please sign in to comment.