diff --git a/.gitattributes b/.gitattributes index 19f0650d7e..c17ad86ad0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16,7 +16,7 @@ # ignore ifacemaker files *_generated.go linguist-generated contrib/opbot/generated/* linguist-generated -*.contractinfo.json linguist-generated=true +*.contractinfo.json linguist-generated # svg should be treated as a binary https://git.io/JE2VK @@ -24,3 +24,6 @@ contrib/opbot/generated/* linguist-generated *.sol linguist-language=Solidity .vscode/*.json linguist-language=jsonc + +# foundry deploy data +packages/**/deployments/*.json linguist-generated diff --git a/docs/bridge/CHANGELOG.md b/docs/bridge/CHANGELOG.md index b99c54635a..ccf73ae349 100644 --- a/docs/bridge/CHANGELOG.md +++ b/docs/bridge/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.5.13](https://github.com/synapsecns/sanguine/compare/@synapsecns/bridge-docs@0.5.12...@synapsecns/bridge-docs@0.5.13) (2024-12-08) + +**Note:** Version bump only for package @synapsecns/bridge-docs + + + + + +## [0.5.12](https://github.com/synapsecns/sanguine/compare/@synapsecns/bridge-docs@0.5.11...@synapsecns/bridge-docs@0.5.12) (2024-12-06) + +**Note:** Version bump only for package @synapsecns/bridge-docs + + + + + ## [0.5.11](https://github.com/synapsecns/sanguine/compare/@synapsecns/bridge-docs@0.5.10...@synapsecns/bridge-docs@0.5.11) (2024-12-02) **Note:** Version bump only for package @synapsecns/bridge-docs diff --git a/docs/bridge/docs/01-About/04-SYN.md b/docs/bridge/docs/01-About/04-SYN.md new file mode 100644 index 0000000000..09bbd9239c --- /dev/null +++ b/docs/bridge/docs/01-About/04-SYN.md @@ -0,0 +1,24 @@ +--- +title: $SYN Token +--- + +# $SYN Token + +$SYN is the governance token for the Synapse Protocol. There are no unlocks, all future $SYN emissions are goverened by the [DAO](/docs/About/DAO). + +Liquidity for the [$SYN](https://coinmarketcap.com/currencies/synapse-2/) token can be found here: + +| Venue | Link | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Coinbase | `https://www.coinbase.com/price/synapse` [↗](https://www.coinbase.com/price/synapse) | +| Sushi | `https://www.sushi.com/ethereum/pool/v2/0x4a86c01d67965f8cb3d0aaa2c655705e64097c31` [↗](https://www.sushi.com/ethereum/pool/v2/0x4a86c01d67965f8cb3d0aaa2c655705e64097c31) | +| Revolut | `https://www.revolut.com/crypto/price/syn` [↗](https://www.revolut.com/crypto/price/syn) | +| Binance (Spot) | `https://www.binance.com/en/trade/SYN_USDT?type=spot` [↗](https://www.binance.com/en/trade/SYN_USDT?type=spot) | +| Binance (Perpetuals) | `https://www.binance.com/en/futures/SYNUSDT` [↗](https://www.binance.com/en/futures/SYNUSDT) | +| Bybit (SYN/USDT) | `https://www.bybit.com/trade/usdt/SYNUSDT` [↗](https://www.bybit.com/trade/usdt/SYNUSDT) | +| HTX | `https://www.htx.com/price/syn/` [↗](https://www.htx.com/price/syn/) | +| Kraken | `https://www.kraken.com/prices/synapse` [↗](https://www.kraken.com/prices/synapse) | +| KuCoin | `https://www.kucoin.com/price/SYN` [↗](https://www.kucoin.com/price/SYN) | +
+ +All $SYN token addresses can be found [here](/docs/Contracts/SYN). diff --git a/docs/bridge/docs/05-Contracts/09-SYN.md b/docs/bridge/docs/05-Contracts/09-SYN.md new file mode 100644 index 0000000000..e5a8d3e78c --- /dev/null +++ b/docs/bridge/docs/05-Contracts/09-SYN.md @@ -0,0 +1,32 @@ +--- +title: $SYN Token +--- + +:::note This list may be incomplete + +The canonical list is hosted within the SynapseCNS on [Github](https://github.com/synapsecns/synapse-contracts). + +::: + +# $SYN + +| Chain | Address | +| --------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| Arbitrum | `0x080f6aed32fc474dd5717105dba5ea57268f46eb` [↗](https://arbiscan.io/address/0x080f6aed32fc474dd5717105dba5ea57268f46eb) | +| Aurora | `0xd80d8688b02B3FD3afb81cDb124F188BB5aD0445` [↗](https://explorer.mainnet.aurora.dev/address/0xd80d8688b02B3FD3afb81cDb124F188BB5aD0445) | +| Avalanche | `0x1f1E7c893855525b303f99bDF5c3c05Be09ca251` [↗](https://snowscan.xyz/address/0x1f1E7c893855525b303f99bDF5c3c05Be09ca251) | +| Base | `0x432036208d2717394d2614d6697c46DF3Ed69540` [↗](https://basescan.org/address/0x432036208d2717394d2614d6697c46DF3Ed69540) | +| Blast | `0x9592f08387134e218327E6E8423400eb845EdE0E` [↗](https://blastscan.io/address/0x9592f08387134e218327E6E8423400eb845EdE0E) | +| Boba | `0xb554A55358fF0382Fb21F0a478C3546d1106Be8c` [↗](https://blockexplorer.boba.network/address/0xb554A55358fF0382Fb21F0a478C3546d1106Be8c) | +| BSC | `0xa4080f1778e69467e905b8d6f72f6e441f9e9484` [↗](https://bscscan.com/address/0xa4080f1778e69467e905b8d6f72f6e441f9e9484) | +| Canto | `0x555982d2E211745b96736665e19D9308B615F78e` [↗](https://canto.dex.guru/address/0x555982d2E211745b96736665e19D9308B615F78e) | +| Cronos | `0xFD0F80899983b8D46152aa1717D76cba71a31616` [↗](https://cronos.org/explorer/address/0xFD0F80899983b8D46152aa1717D76cba71a31616) | +| DFK Chain | `0xB6b5C854a8f71939556d4f3a2e5829F7FcC1bf2A` [↗](https://dfkchain.com/address/0xB6b5C854a8f71939556d4f3a2e5829F7FcC1bf2A) | +| Ethereum | `0x0f2D719407FdBeFF09D87557AbB7232601FD9F29` [↗](https://etherscan.io/address/0x0f2D719407FdBeFF09D87557AbB7232601FD9F29) | +| Fantom | `0xE55e19Fb4F2D85af758950957714292DAC1e25B2` [↗](https://ftmscan.com/address/0xE55e19Fb4F2D85af758950957714292DAC1e25B2) | +| Harmony | `0xE55e19Fb4F2D85af758950957714292DAC1e25B2` [↗](https://explorer.harmony.one/address/0xE55e19Fb4F2D85af758950957714292DAC1e25B2) | +| Metis | `0x67c10c397dd0ba417329543c1a40eb48aaa7cd00` [↗](https://andromeda-explorer.metis.io/address/0x67c10c397dd0ba417329543c1a40eb48aaa7cd00) | +| Moonbeam | `0xF44938b0125A6662f9536281aD2CD6c499F22004` [↗](https://moonbeam.moonscan.io/address/0xF44938b0125A6662f9536281aD2CD6c499F22004) | +| Moonriver | `0xd80d8688b02B3FD3afb81cDb124F188BB5aD0445` [↗](https://moonriver.moonscan.io/address/0xd80d8688b02B3FD3afb81cDb124F188BB5aD0445) | +| Optimism | `0x5A5fFf6F753d7C11A56A52FE47a177a87e431655` [↗](https://optimistic.etherscan.io/address/0x5A5fFf6F753d7C11A56A52FE47a177a87e431655) | +| Polygon | `0xf8f9efc0db77d8881500bb06ff5d6abc3070e695` [↗](https://polygonscan.com/address/0xf8f9efc0db77d8881500bb06ff5d6abc3070e695) | diff --git a/docs/bridge/package.json b/docs/bridge/package.json index e908b9b17a..de0c5ebf15 100644 --- a/docs/bridge/package.json +++ b/docs/bridge/package.json @@ -1,6 +1,6 @@ { "name": "@synapsecns/bridge-docs", - "version": "0.5.11", + "version": "0.5.13", "private": true, "scripts": { "docusaurus": "docusaurus", diff --git a/packages/contracts-rfq/.solhintignore b/packages/contracts-rfq/.solhintignore index f2e4fd2a54..30d665167e 100644 --- a/packages/contracts-rfq/.solhintignore +++ b/packages/contracts-rfq/.solhintignore @@ -3,4 +3,4 @@ contracts/interfaces/IFastBridge.sol contracts/legacy/**/*.sol script/FastBridge.s.sol test/FastBridge.t.sol -test/FastBridgeMock.sol +test/mocks/FastBridgeMock.sol diff --git a/packages/contracts-rfq/CHANGELOG.md b/packages/contracts-rfq/CHANGELOG.md index 1f89070995..3c258f337c 100644 --- a/packages/contracts-rfq/CHANGELOG.md +++ b/packages/contracts-rfq/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.14.7](https://github.com/synapsecns/sanguine/compare/@synapsecns/contracts-rfq@0.14.6...@synapsecns/contracts-rfq@0.14.7) (2024-12-05) + +**Note:** Version bump only for package @synapsecns/contracts-rfq + + + + + ## [0.14.6](https://github.com/synapsecns/sanguine/compare/@synapsecns/contracts-rfq@0.14.5...@synapsecns/contracts-rfq@0.14.6) (2024-11-28) diff --git a/packages/contracts-rfq/contracts/AdminV2.sol b/packages/contracts-rfq/contracts/AdminV2.sol index 14ad93a3bc..0ec8a6eed0 100644 --- a/packages/contracts-rfq/contracts/AdminV2.sol +++ b/packages/contracts-rfq/contracts/AdminV2.sol @@ -18,6 +18,16 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors { using SafeERC20 for IERC20; + /// @notice Struct for storing information about a prover. + /// @param id The ID of the prover: its position in `_allProvers` plus one, + /// or zero if the prover has never been added. + /// @param activeFromTimestamp The timestamp at which the prover becomes active, + /// or zero if the prover has never been added or is no longer active. + struct ProverInfo { + uint16 id; + uint240 activeFromTimestamp; + } + /// @notice The address reserved for the native gas token (ETH on Ethereum and most L2s, AVAX on Avalanche, etc.). address public constant NATIVE_GAS_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; @@ -25,10 +35,6 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors { /// @dev Only addresses with this role can post FastBridge quotes to the API. bytes32 public constant QUOTER_ROLE = keccak256("QUOTER_ROLE"); - /// @notice The role identifier for the Prover's on-chain authentication in FastBridge. - /// @dev Only addresses with this role can provide proofs that a FastBridge request has been relayed. - bytes32 public constant PROVER_ROLE = keccak256("PROVER_ROLE"); - /// @notice The role identifier for the Guard's on-chain authentication in FastBridge. /// @dev Only addresses with this role can dispute submitted relay proofs during the dispute period. bytes32 public constant GUARD_ROLE = keccak256("GUARD_ROLE"); @@ -51,6 +57,11 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors { /// @notice The default cancel delay set during contract deployment. uint256 public constant DEFAULT_CANCEL_DELAY = 1 days; + /// @notice The minimum dispute penalty time that can be set by the governor. + uint256 public constant MIN_DISPUTE_PENALTY_TIME = 1 minutes; + /// @notice The default dispute penalty time set during contract deployment. + uint256 public constant DEFAULT_DISPUTE_PENALTY_TIME = 30 minutes; + /// @notice The protocol fee rate taken on the origin amount deposited in the origin chain. uint256 public protocolFeeRate; @@ -66,6 +77,14 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors { /// This is exposed for conveniece for off-chain indexers that need to know the deployment block. uint256 public deployBlock; + /// @notice The timeout period that is used to temporarily disactivate a disputed prover. + uint256 public disputePenaltyTime; + + /// @notice A list of all provers ever added to the contract. Can hold up to 2^16-1 provers. + address[] private _allProvers; + /// @notice A mapping of provers to their information: id and activeFromTimestamp. + mapping(address => ProverInfo) private _proverInfos; + /// @notice This variable is deprecated and should not be used. /// @dev Use ZapNative V2 requests instead. uint256 public immutable chainGasAmount = 0; @@ -74,6 +93,35 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors { _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin); _setCancelDelay(DEFAULT_CANCEL_DELAY); _setDeployBlock(block.number); + _setDisputePenaltyTime(DEFAULT_DISPUTE_PENALTY_TIME); + } + + /// @inheritdoc IAdminV2 + function addProver(address prover) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (getActiveProverID(prover) != 0) revert ProverAlreadyActive(); + ProverInfo storage $ = _proverInfos[prover]; + // Add the prover to the list of all provers and record its id (its position + 1), + // if this has not already been done. + if ($.id == 0) { + _allProvers.push(prover); + uint256 id = _allProvers.length; + if (id > type(uint16).max) revert ProverCapacityExceeded(); + // Note: this is a storage write. + $.id = uint16(id); + } + // Update the activeFrom timestamp. + // Note: this is a storage write. + $.activeFromTimestamp = uint240(block.timestamp); + emit ProverAdded(prover); + } + + /// @inheritdoc IAdminV2 + function removeProver(address prover) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (getActiveProverID(prover) == 0) revert ProverNotActive(); + // We never remove provers from the list of all provers to preserve their IDs, + // so we just need to reset the activeFrom timestamp. + _proverInfos[prover].activeFromTimestamp = 0; + emit ProverRemoved(prover); } /// @inheritdoc IAdminV2 @@ -86,6 +134,11 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors { _setDeployBlock(blockNumber); } + /// @inheritdoc IAdminV2 + function setDisputePenaltyTime(uint256 newDisputePenaltyTime) external onlyRole(GOVERNOR_ROLE) { + _setDisputePenaltyTime(newDisputePenaltyTime); + } + /// @inheritdoc IAdminV2 function setProtocolFeeRate(uint256 newFeeRate) external onlyRole(GOVERNOR_ROLE) { if (newFeeRate > FEE_RATE_MAX) revert FeeRateAboveMax(); @@ -110,6 +163,66 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors { } } + /// @inheritdoc IAdminV2 + function getProvers() external view returns (address[] memory provers) { + uint256 length = _allProvers.length; + // Calculate the number of active provers. + uint256 activeProversCount = 0; + for (uint256 i = 0; i < length; i++) { + if (getActiveProverID(_allProvers[i]) != 0) { + activeProversCount++; + } + } + // Do the second pass to populate the provers array. + provers = new address[](activeProversCount); + uint256 activeProversIndex = 0; + for (uint256 i = 0; i < length; i++) { + address prover = _allProvers[i]; + if (getActiveProverID(prover) != 0) { + provers[activeProversIndex++] = prover; + } + } + } + + /// @inheritdoc IAdminV2 + function getProverInfo(address prover) external view returns (uint16 proverID, uint256 activeFromTimestamp) { + proverID = _proverInfos[prover].id; + activeFromTimestamp = _proverInfos[prover].activeFromTimestamp; + } + + /// @inheritdoc IAdminV2 + function getProverInfoByID(uint16 proverID) external view returns (address prover, uint256 activeFromTimestamp) { + if (proverID == 0 || proverID > _allProvers.length) return (address(0), 0); + prover = _allProvers[proverID - 1]; + activeFromTimestamp = _proverInfos[prover].activeFromTimestamp; + } + + /// @inheritdoc IAdminV2 + function getActiveProverID(address prover) public view returns (uint16) { + // Aggregate the read operations from the same storage slot. + uint16 id = _proverInfos[prover].id; + uint256 activeFromTimestamp = _proverInfos[prover].activeFromTimestamp; + // Return zero if the prover has never been added or is no longer active. + if (activeFromTimestamp == 0 || activeFromTimestamp > block.timestamp) return 0; + return id; + } + + /// @notice Internal logic to apply the dispute penalty time to a given prover. Will make the prover inactive + /// for `disputePenaltyTime` seconds. No-op if the prover ID does not exist or prover is already inactive. + function _applyDisputePenaltyTime(uint16 proverID) internal { + // Check that the prover exists. + if (proverID == 0 || proverID > _allProvers.length) return; + address prover = _allProvers[proverID - 1]; + ProverInfo storage $ = _proverInfos[prover]; + // No-op if the prover is already inactive. + if ($.activeFromTimestamp == 0) return; + uint256 newActiveFromTimestamp = block.timestamp + disputePenaltyTime; + // Update the activeFrom timestamp. + // Note: this is a storage write. + $.activeFromTimestamp = uint240(newActiveFromTimestamp); + emit DisputePenaltyTimeApplied(prover, newActiveFromTimestamp); + } + /// @notice Internal logic to set the cancel delay. Security checks are performed outside of this function. /// @dev This function is marked as private to prevent child contracts from calling it directly. function _setCancelDelay(uint256 newCancelDelay) private { @@ -125,4 +238,13 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors { deployBlock = blockNumber; emit DeployBlockSet(blockNumber); } + + /// @notice Internal logic to set the dispute penalty time. Security checks are performed outside of this function. + /// @dev This function is marked as private to prevent child contracts from calling it directly. + function _setDisputePenaltyTime(uint256 newDisputePenaltyTime) private { + if (newDisputePenaltyTime < MIN_DISPUTE_PENALTY_TIME) revert DisputePenaltyTimeBelowMin(); + uint256 oldDisputePenaltyTime = disputePenaltyTime; + disputePenaltyTime = newDisputePenaltyTime; + emit DisputePenaltyTimeUpdated(oldDisputePenaltyTime, newDisputePenaltyTime); + } } diff --git a/packages/contracts-rfq/contracts/FastBridgeV2.sol b/packages/contracts-rfq/contracts/FastBridgeV2.sol index 836c26262a..706859c113 100644 --- a/packages/contracts-rfq/contracts/FastBridgeV2.sol +++ b/packages/contracts-rfq/contracts/FastBridgeV2.sol @@ -104,9 +104,10 @@ contract FastBridgeV2 is AdminV2, MulticallTarget, IFastBridgeV2, IFastBridgeV2E function dispute(bytes32 transactionId) external onlyRole(GUARD_ROLE) { // Aggregate the read operations from the same storage slot. BridgeTxDetails storage $ = bridgeTxDetails[transactionId]; + uint16 proverID = $.proverID; address disputedRelayer = $.proofRelayer; BridgeStatus status = $.status; - uint56 proofBlockTimestamp = $.proofBlockTimestamp; + uint40 proofBlockTimestamp = $.proofBlockTimestamp; // Can only dispute a RELAYER_PROVED transaction within the dispute period. if (status != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect(); @@ -114,9 +115,14 @@ contract FastBridgeV2 is AdminV2, MulticallTarget, IFastBridgeV2, IFastBridgeV2E revert DisputePeriodPassed(); } + // Apply the timeout penalty to the prover that submitted the proof. + // Note: this is a no-op if the prover has already been removed. + _applyDisputePenaltyTime(proverID); + // Update status to REQUESTED and delete the disputed proof details. // Note: these are storage writes. $.status = BridgeStatus.REQUESTED; + $.proverID = 0; $.proofRelayer = address(0); $.proofBlockTimestamp = 0; @@ -343,7 +349,9 @@ contract FastBridgeV2 is AdminV2, MulticallTarget, IFastBridgeV2, IFastBridgeV2E } /// @inheritdoc IFastBridgeV2 - function proveV2(bytes32 transactionId, bytes32 destTxHash, address relayer) public onlyRole(PROVER_ROLE) { + function proveV2(bytes32 transactionId, bytes32 destTxHash, address relayer) public { + uint16 proverID = getActiveProverID(msg.sender); + if (proverID == 0) revert ProverNotActive(); // Can only prove a REQUESTED transaction. BridgeTxDetails storage $ = bridgeTxDetails[transactionId]; if ($.status != BridgeStatus.REQUESTED) revert StatusIncorrect(); @@ -351,7 +359,8 @@ contract FastBridgeV2 is AdminV2, MulticallTarget, IFastBridgeV2, IFastBridgeV2E // Update status to RELAYER_PROVED and store the proof details. // Note: these are storage writes. $.status = BridgeStatus.RELAYER_PROVED; - $.proofBlockTimestamp = uint56(block.timestamp); + $.proverID = proverID; + $.proofBlockTimestamp = uint40(block.timestamp); $.proofRelayer = relayer; emit BridgeProofProvided(transactionId, relayer, destTxHash); @@ -367,7 +376,7 @@ contract FastBridgeV2 is AdminV2, MulticallTarget, IFastBridgeV2, IFastBridgeV2E BridgeTxDetails storage $ = bridgeTxDetails[transactionId]; address proofRelayer = $.proofRelayer; BridgeStatus status = $.status; - uint56 proofBlockTimestamp = $.proofBlockTimestamp; + uint40 proofBlockTimestamp = $.proofBlockTimestamp; // Can only claim a RELAYER_PROVED transaction after the dispute period. if (status != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect(); @@ -468,14 +477,14 @@ contract FastBridgeV2 is AdminV2, MulticallTarget, IFastBridgeV2, IFastBridgeV2E } /// @notice Calculates the time elapsed since a proof was submitted. - /// @dev The proof.timestamp stores block timestamps as uint56 for gas optimization. - /// _timeSince(proof) handles timestamp rollover when block.timestamp > type(uint56).max but - /// proof.timestamp < type(uint56).max via an unchecked statement. + /// @dev The proof.timestamp stores block timestamps as uint40 for gas optimization. + /// _timeSince(proof) handles timestamp rollover when block.timestamp > type(uint40).max but + /// proof.timestamp < type(uint40).max via an unchecked statement. /// @param proofBlockTimestamp The block timestamp when the proof was submitted. /// @return delta The time elapsed since proof submission. - function _timeSince(uint56 proofBlockTimestamp) internal view returns (uint256 delta) { + function _timeSince(uint40 proofBlockTimestamp) internal view returns (uint256 delta) { unchecked { - delta = uint56(block.timestamp) - proofBlockTimestamp; + delta = uint40(block.timestamp) - proofBlockTimestamp; } } diff --git a/packages/contracts-rfq/contracts/interfaces/IAdminV2.sol b/packages/contracts-rfq/contracts/interfaces/IAdminV2.sol index 0720cce4ac..728193110a 100644 --- a/packages/contracts-rfq/contracts/interfaces/IAdminV2.sol +++ b/packages/contracts-rfq/contracts/interfaces/IAdminV2.sol @@ -4,9 +4,20 @@ pragma solidity ^0.8.4; interface IAdminV2 { event CancelDelayUpdated(uint256 oldCancelDelay, uint256 newCancelDelay); event DeployBlockSet(uint256 blockNumber); + event DisputePenaltyTimeUpdated(uint256 oldDisputePenaltyTime, uint256 newDisputePenaltyTime); event FeeRateUpdated(uint256 oldFeeRate, uint256 newFeeRate); event FeesSwept(address token, address recipient, uint256 amount); + event ProverAdded(address prover); + event ProverRemoved(address prover); + event DisputePenaltyTimeApplied(address prover, uint256 inactiveUntilTimestamp); + + /// @notice Allows the role admin to add a new prover to the contract. + function addProver(address prover) external; + + /// @notice Allows the role admin to remove a prover from the contract. + function removeProver(address prover) external; + /// @notice Allows the governor to set the cancel delay. The cancel delay is the time period after the transaction /// deadline during which a transaction can be permissionlessly cancelled if it hasn't been proven by any Relayer. function setCancelDelay(uint256 newCancelDelay) external; @@ -16,6 +27,11 @@ interface IAdminV2 { /// block number rather than the chain's native block number. function setDeployBlock(uint256 blockNumber) external; + /// @notice Allows the governor to set the dispute penalty time. The dispute penalty time is the time period used to + /// temporarily deactivate a prover if its proof is disputed: prover will be inactive for the dispute penalty time + /// after the dispute is submitted. + function setDisputePenaltyTime(uint256 newDisputePenaltyTime) external; + /// @notice Allows the governor to set the protocol fee rate. The protocol fee is taken from the origin /// amount and is only applied to completed and claimed transactions. /// @dev The protocol fee is abstracted away from the relayers; they always operate using the amounts after fees. @@ -24,4 +40,20 @@ interface IAdminV2 { /// @notice Allows the governor to withdraw the accumulated protocol fees from the contract. function sweepProtocolFees(address token, address recipient) external; + + /// @notice Returns the ID of the active prover, or zero if the prover is not currently active. + function getActiveProverID(address prover) external view returns (uint16); + + /// @notice Returns the information about the prover with the provided address. + /// @return proverID The ID of the prover if it has been added before, or zero otherwise. + /// @return activeFromTimestamp The timestamp when the prover becomes active, or zero if the prover isn't active. + function getProverInfo(address prover) external view returns (uint16 proverID, uint256 activeFromTimestamp); + + /// @notice Returns the information about the prover with the provided ID. + /// @return prover The address of the prover with the provided ID, or zero the ID does not exist. + /// @return activeFromTimestamp The timestamp when the prover becomes active, or zero if the prover isn't active. + function getProverInfoByID(uint16 proverID) external view returns (address prover, uint256 activeFromTimestamp); + + /// @notice Returns the list of the active provers. + function getProvers() external view returns (address[] memory); } diff --git a/packages/contracts-rfq/contracts/interfaces/IAdminV2Errors.sol b/packages/contracts-rfq/contracts/interfaces/IAdminV2Errors.sol index 445087c87c..7fede46ec3 100644 --- a/packages/contracts-rfq/contracts/interfaces/IAdminV2Errors.sol +++ b/packages/contracts-rfq/contracts/interfaces/IAdminV2Errors.sol @@ -4,4 +4,8 @@ pragma solidity ^0.8.4; interface IAdminV2Errors { error CancelDelayBelowMin(); error FeeRateAboveMax(); + error ProverAlreadyActive(); + error ProverCapacityExceeded(); + error ProverNotActive(); + error DisputePenaltyTimeBelowMin(); } diff --git a/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol b/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol index 141b3808a7..e42a923960 100644 --- a/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol +++ b/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol @@ -15,7 +15,8 @@ interface IFastBridgeV2 is IFastBridge { struct BridgeTxDetails { BridgeStatus status; uint32 destChainId; - uint56 proofBlockTimestamp; + uint16 proverID; + uint40 proofBlockTimestamp; address proofRelayer; } diff --git a/packages/contracts-rfq/foundry.toml b/packages/contracts-rfq/foundry.toml index 48f7927e13..e44a7f6a0a 100644 --- a/packages/contracts-rfq/foundry.toml +++ b/packages/contracts-rfq/foundry.toml @@ -7,6 +7,7 @@ src = 'contracts' out = 'out' libs = ["lib", "node_modules"] ffi = true +gas_limit = 9223372036854775807 fs_permissions = [ { access = "read", path = "./" }, { access = "read-write", path = "./.deployments" } diff --git a/packages/contracts-rfq/package.json b/packages/contracts-rfq/package.json index 29c224390d..17fe284464 100644 --- a/packages/contracts-rfq/package.json +++ b/packages/contracts-rfq/package.json @@ -1,7 +1,7 @@ { "name": "@synapsecns/contracts-rfq", "license": "MIT", - "version": "0.14.6", + "version": "0.14.7", "description": "FastBridge contracts.", "private": true, "files": [ @@ -26,7 +26,7 @@ "lint": "forge fmt && npm run solhint", "lint:check": "forge fmt --check && npm run solhint:check", "ci:lint": "npm run lint:check", - "build:go": "./flatten.sh contracts/*.sol test/*.sol", + "build:go": "./flatten.sh contracts/*.sol test/harnesses/*.sol test/mocks/*.sol", "solhint": "solhint '{contracts,script,test}/**/*.sol' --fix --noPrompt --max-warnings 3", "solhint:check": "solhint '{contracts,script,test}/**/*.sol' --max-warnings 3" } diff --git a/packages/contracts-rfq/test/FastBridge.t.sol b/packages/contracts-rfq/test/FastBridge.t.sol index 0e7804e49a..7fad47dc27 100644 --- a/packages/contracts-rfq/test/FastBridge.t.sol +++ b/packages/contracts-rfq/test/FastBridge.t.sol @@ -10,7 +10,7 @@ import "../contracts/interfaces/IFastBridge.sol"; import "../contracts/libs/Errors.sol"; import "../contracts/libs/UniversalToken.sol"; -import "./MockERC20.sol"; +import "./mocks/MockERC20.sol"; import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; diff --git a/packages/contracts-rfq/test/FastBridgeV2.Management.t.sol b/packages/contracts-rfq/test/FastBridgeV2.Management.t.sol index 4c61883aa1..51610b1029 100644 --- a/packages/contracts-rfq/test/FastBridgeV2.Management.t.sol +++ b/packages/contracts-rfq/test/FastBridgeV2.Management.t.sol @@ -2,23 +2,32 @@ pragma solidity ^0.8.20; import {IAdmin} from "../contracts/interfaces/IAdmin.sol"; -import {IAdminV2Errors} from "../contracts/interfaces/IAdminV2Errors.sol"; import {FastBridgeV2, FastBridgeV2Test} from "./FastBridgeV2.t.sol"; // solhint-disable func-name-mixedcase, ordering -contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors { +contract FastBridgeV2ManagementTest is FastBridgeV2Test { uint256 public constant FEE_RATE_MAX = 1e4; // 1% bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE"); uint256 public constant MIN_CANCEL_DELAY = 1 hours; uint256 public constant DEFAULT_CANCEL_DELAY = 1 days; + uint256 public constant MIN_DISPUTE_PENALTY_TIME = 1 minutes; + uint256 public constant DEFAULT_DISPUTE_PENALTY_TIME = 30 minutes; + address public admin = makeAddr("Admin"); address public governorA = makeAddr("Governor A"); + address public proverA = makeAddr("Prover A"); + address public proverB = makeAddr("Prover B"); + + event ProverAdded(address prover); + event ProverRemoved(address prover); + event CancelDelayUpdated(uint256 oldCancelDelay, uint256 newCancelDelay); event DeployBlockSet(uint256 blockNumber); + event DisputePenaltyTimeUpdated(uint256 oldDisputePenaltyTime, uint256 newDisputePenaltyTime); event FeeRateUpdated(uint256 oldFeeRate, uint256 newFeeRate); event FeesSwept(address token, address recipient, uint256 amount); @@ -37,6 +46,16 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors { cheatCollectedProtocolFees(ETH_ADDRESS, 200); } + function addProver(address caller, address prover) public { + vm.prank(caller); + fastBridge.addProver(prover); + } + + function removeProver(address caller, address prover) public { + vm.prank(caller); + fastBridge.removeProver(prover); + } + function setGovernor(address caller, address newGovernor) public { vm.prank(caller); fastBridge.grantRole(GOVERNOR_ROLE, newGovernor); @@ -52,6 +71,11 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors { fastBridge.setDeployBlock(blockNumber); } + function setDisputePenaltyTime(address caller, uint256 newDisputePenaltyTime) public { + vm.prank(caller); + fastBridge.setDisputePenaltyTime(newDisputePenaltyTime); + } + function setProtocolFeeRate(address caller, uint256 newFeeRate) public { vm.prank(caller); fastBridge.setProtocolFeeRate(newFeeRate); @@ -77,9 +101,157 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors { function test_defaultValues() public view { assertEq(fastBridge.cancelDelay(), DEFAULT_CANCEL_DELAY); assertEq(fastBridge.deployBlock(), block.number); + assertEq(fastBridge.disputePenaltyTime(), DEFAULT_DISPUTE_PENALTY_TIME); assertEq(fastBridge.protocolFeeRate(), 0); } + // ════════════════════════════════════════════════ ADD PROVER ═════════════════════════════════════════════════════ + + function checkProverInfo(address prover, uint16 proverID, uint256 activeFromTimestamp) public view { + (uint16 id, uint256 ts) = fastBridge.getProverInfo(prover); + assertEq(id, proverID); + assertEq(ts, activeFromTimestamp); + address p; + (p, ts) = fastBridge.getProverInfoByID(proverID); + if (proverID != 0) { + assertEq(p, prover); + assertEq(ts, activeFromTimestamp); + } else { + assertEq(p, address(0)); + assertEq(ts, 0); + } + } + + function test_addProver() public { + uint256 proverAtime = block.timestamp; + vm.expectEmit(address(fastBridge)); + emit ProverAdded(proverA); + addProver(admin, proverA); + assertEq(fastBridge.getActiveProverID(proverA), 1); + assertEq(fastBridge.getActiveProverID(proverB), 0); + address[] memory provers = fastBridge.getProvers(); + assertEq(provers.length, 1); + assertEq(provers[0], proverA); + checkProverInfo(proverA, 1, proverAtime); + checkProverInfo(proverB, 0, 0); + } + + function test_addProver_twice() public { + uint256 proverAtime = block.timestamp; + test_addProver(); + skip(1 hours); + uint256 proverBtime = block.timestamp; + vm.expectEmit(address(fastBridge)); + emit ProverAdded(proverB); + addProver(admin, proverB); + assertEq(fastBridge.getActiveProverID(proverA), 1); + assertEq(fastBridge.getActiveProverID(proverB), 2); + address[] memory provers = fastBridge.getProvers(); + assertEq(provers.length, 2); + assertEq(provers[0], proverA); + assertEq(provers[1], proverB); + checkProverInfo(proverA, 1, proverAtime); + checkProverInfo(proverB, 2, proverBtime); + } + + function test_addProver_twice_afterRemoval() public { + test_removeProver_twice(); + // Add B back + skip(1 hours); + uint256 proverBtime = block.timestamp; + vm.expectEmit(address(fastBridge)); + emit ProverAdded(proverB); + addProver(admin, proverB); + assertEq(fastBridge.getActiveProverID(proverA), 0); + assertEq(fastBridge.getActiveProverID(proverB), 2); + address[] memory provers = fastBridge.getProvers(); + assertEq(provers.length, 1); + assertEq(provers[0], proverB); + checkProverInfo(proverA, 1, 0); + checkProverInfo(proverB, 2, proverBtime); + // Add A back + skip(1 hours); + uint256 proverAtime = block.timestamp; + vm.expectEmit(address(fastBridge)); + emit ProverAdded(proverA); + addProver(admin, proverA); + assertEq(fastBridge.getActiveProverID(proverA), 1); + assertEq(fastBridge.getActiveProverID(proverB), 2); + provers = fastBridge.getProvers(); + assertEq(provers.length, 2); + assertEq(provers[0], proverA); + assertEq(provers[1], proverB); + checkProverInfo(proverA, 1, proverAtime); + checkProverInfo(proverB, 2, proverBtime); + } + + function test_addProver_revertNotAdmin(address caller) public { + vm.assume(caller != admin); + expectUnauthorized(caller, fastBridge.DEFAULT_ADMIN_ROLE()); + addProver(caller, proverA); + } + + function test_addProver_revertAlreadyActive() public { + test_addProver(); + vm.expectRevert(ProverAlreadyActive.selector); + addProver(admin, proverA); + } + + function test_addProver_revertTooManyProvers() public { + for (uint256 i = 0; i < type(uint16).max; i++) { + addProver(admin, address(uint160(i))); + } + vm.expectRevert(ProverCapacityExceeded.selector); + addProver(admin, proverA); + } + + // ═══════════════════════════════════════════════ REMOVE PROVER ═══════════════════════════════════════════════════ + + function test_removeProver() public { + test_addProver_twice(); + uint256 proverBtime = block.timestamp; + vm.expectEmit(address(fastBridge)); + emit ProverRemoved(proverA); + removeProver(admin, proverA); + assertEq(fastBridge.getActiveProverID(proverA), 0); + assertEq(fastBridge.getActiveProverID(proverB), 2); + address[] memory provers = fastBridge.getProvers(); + assertEq(provers.length, 1); + assertEq(provers[0], proverB); + checkProverInfo(proverA, 1, 0); + checkProverInfo(proverB, 2, proverBtime); + } + + function test_removeProver_twice() public { + test_removeProver(); + vm.expectEmit(address(fastBridge)); + emit ProverRemoved(proverB); + removeProver(admin, proverB); + assertEq(fastBridge.getActiveProverID(proverA), 0); + assertEq(fastBridge.getActiveProverID(proverB), 0); + address[] memory provers = fastBridge.getProvers(); + assertEq(provers.length, 0); + checkProverInfo(proverA, 1, 0); + checkProverInfo(proverB, 2, 0); + } + + function test_removeProver_revertNotAdmin(address caller) public { + vm.assume(caller != admin); + expectUnauthorized(caller, fastBridge.DEFAULT_ADMIN_ROLE()); + removeProver(caller, proverA); + } + + function test_removeProver_revertNeverBeenActive() public { + vm.expectRevert(ProverNotActive.selector); + removeProver(admin, proverA); + } + + function test_removeProver_revertNotActive() public { + test_removeProver(); + vm.expectRevert(ProverNotActive.selector); + removeProver(admin, proverA); + } + // ═════════════════════════════════════════════ SET CANCEL DELAY ══════════════════════════════════════════════════ function test_setCancelDelay() public { @@ -98,7 +270,7 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors { } function test_setCancelDelay_revertBelowMin() public { - vm.expectRevert(IAdminV2Errors.CancelDelayBelowMin.selector); + vm.expectRevert(CancelDelayBelowMin.selector); setCancelDelay(governor, MIN_CANCEL_DELAY - 1); } @@ -123,6 +295,34 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors { setDeployBlock(caller, 123_456); } + // ═════════════════════════════════════════ SET DISPUTE PENALTY TIME ══════════════════════════════════════════════ + + function test_setDisputePenaltyTime() public { + vm.expectEmit(address(fastBridge)); + emit DisputePenaltyTimeUpdated(DEFAULT_DISPUTE_PENALTY_TIME, 1 days); + setDisputePenaltyTime(governor, 1 days); + assertEq(fastBridge.disputePenaltyTime(), 1 days); + } + + function test_setDisputePenaltyTime_twice() public { + test_setDisputePenaltyTime(); + vm.expectEmit(address(fastBridge)); + emit DisputePenaltyTimeUpdated(1 days, 2 days); + setDisputePenaltyTime(governor, 2 days); + assertEq(fastBridge.disputePenaltyTime(), 2 days); + } + + function test_setDisputePenaltyTime_revertBelowMin() public { + vm.expectRevert(DisputePenaltyTimeBelowMin.selector); + setDisputePenaltyTime(governor, MIN_DISPUTE_PENALTY_TIME - 1); + } + + function test_setDisputePenaltyTime_revertNotGovernor(address caller) public { + vm.assume(caller != governor); + expectUnauthorized(caller, fastBridge.GOVERNOR_ROLE()); + setDisputePenaltyTime(caller, 1 days); + } + // ═══════════════════════════════════════════ SET PROTOCOL FEE RATE ═══════════════════════════════════════════════ function test_setProtocolFeeRate() public { @@ -141,7 +341,7 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors { } function test_setProtocolFeeRate_revert_tooHigh() public { - vm.expectRevert(IAdminV2Errors.FeeRateAboveMax.selector); + vm.expectRevert(FeeRateAboveMax.selector); setProtocolFeeRate(governor, FEE_RATE_MAX + 1); } diff --git a/packages/contracts-rfq/test/FastBridgeV2.Src.Base.t.sol b/packages/contracts-rfq/test/FastBridgeV2.Src.Base.t.sol index 54853731ef..969fbc3220 100644 --- a/packages/contracts-rfq/test/FastBridgeV2.Src.Base.t.sol +++ b/packages/contracts-rfq/test/FastBridgeV2.Src.Base.t.sol @@ -9,8 +9,9 @@ import {FastBridgeV2, FastBridgeV2Test, IFastBridge, IFastBridgeV2} from "./Fast abstract contract FastBridgeV2SrcBaseTest is FastBridgeV2Test { uint256 public constant MIN_DEADLINE = 30 minutes; uint256 public constant CLAIM_DELAY = 30 minutes; - // Use a value different from the default to ensure it's being set correctly. + // Use values different from the default to ensure it's being set correctly. uint256 public constant PERMISSIONLESS_CANCEL_DELAY = 13.37 hours; + uint256 public constant DISPUTE_PENALTY_TIME = 4.2 minutes; uint256 public constant LEFTOVER_BALANCE = 10 ether; uint256 public constant INITIAL_PROTOCOL_FEES_TOKEN = 456_789; @@ -26,13 +27,15 @@ abstract contract FastBridgeV2SrcBaseTest is FastBridgeV2Test { } function configureFastBridge() public virtual override { - fastBridge.grantRole(fastBridge.PROVER_ROLE(), relayerA); - fastBridge.grantRole(fastBridge.PROVER_ROLE(), relayerB); + fastBridge.addProver(relayerA); + fastBridge.addProver(relayerB); + fastBridge.grantRole(fastBridge.GUARD_ROLE(), guard); fastBridge.grantRole(fastBridge.CANCELER_ROLE(), canceler); fastBridge.grantRole(fastBridge.GOVERNOR_ROLE(), address(this)); fastBridge.setCancelDelay(PERMISSIONLESS_CANCEL_DELAY); + fastBridge.setDisputePenaltyTime(DISPUTE_PENALTY_TIME); } function mintTokens() public virtual override { diff --git a/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol b/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol index 5b3db7e3a9..14f7cb184c 100644 --- a/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol +++ b/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol @@ -32,6 +32,8 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { event BridgeQuoteDetails(bytes32 indexed transactionId, bytes quoteId); + event DisputePenaltyTimeApplied(address prover, uint256 inactiveUntilTimestamp); + address public claimTo = makeAddr("Claim To"); function expectBridgeRequested(IFastBridgeV2.BridgeTransactionV2 memory bridgeTx, bytes32 txId) public { @@ -92,14 +94,26 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { }); } + function expectDisputePenaltyTimeApplied(address prover) public { + uint256 inactiveUntilTimestamp = block.timestamp + DISPUTE_PENALTY_TIME; + vm.expectEmit(address(fastBridge)); + emit DisputePenaltyTimeApplied(prover, inactiveUntilTimestamp); + } + // ══════════════════════════════════════════════════ BRIDGE ═══════════════════════════════════════════════════════ function checkStatusAndProofAfterBridge(bytes32 txId) public view { assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REQUESTED); - (IFastBridgeV2.BridgeStatus status, uint32 destChainId, uint256 proofBlockTimestamp, address proofRelayer) = - fastBridge.bridgeTxDetails(txId); + ( + IFastBridgeV2.BridgeStatus status, + uint32 destChainId, + uint16 proverID, + uint256 proofBlockTimestamp, + address proofRelayer + ) = fastBridge.bridgeTxDetails(txId); assertEq(status, IFastBridgeV2.BridgeStatus.REQUESTED); assertEq(destChainId, DST_CHAIN_ID); + assertEq(proverID, 0); assertEq(proofBlockTimestamp, 0); assertEq(proofRelayer, address(0)); (proofBlockTimestamp, proofRelayer) = fastBridge.bridgeProofs(txId); @@ -282,12 +296,18 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { // ═══════════════════════════════════════════════════ PROVE ═══════════════════════════════════════════════════════ - function checkStatusAndProofAfterProve(bytes32 txId, address relayer) public view { + function checkStatusAndProofAfterProve(bytes32 txId, uint16 expectedProverID, address relayer) public view { assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_PROVED); - (IFastBridgeV2.BridgeStatus status, uint32 destChainId, uint256 proofBlockTimestamp, address proofRelayer) = - fastBridge.bridgeTxDetails(txId); + ( + IFastBridgeV2.BridgeStatus status, + uint32 destChainId, + uint16 proverID, + uint256 proofBlockTimestamp, + address proofRelayer + ) = fastBridge.bridgeTxDetails(txId); assertEq(status, IFastBridgeV2.BridgeStatus.RELAYER_PROVED); assertEq(destChainId, DST_CHAIN_ID); + assertEq(proverID, expectedProverID); assertEq(proofBlockTimestamp, block.timestamp); assertEq(proofRelayer, relayer); (proofBlockTimestamp, proofRelayer) = fastBridge.bridgeProofs(txId); @@ -300,7 +320,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bridge({caller: userA, msgValue: 0, params: tokenParams}); expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"}); prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"}); - checkStatusAndProofAfterProve(txId, relayerA); + checkStatusAndProofAfterProve(txId, 1, relayerA); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); } @@ -312,7 +332,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"}); prove({caller: relayerA, bridgeTx: ethTx, destTxHash: hex"01"}); - checkStatusAndProofAfterProve(txId, relayerA); + checkStatusAndProofAfterProve(txId, 1, relayerA); assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount); assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); } @@ -346,13 +366,25 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"}); } - function test_prove_revert_callerNotRelayer(address caller) public { + function test_prove_revert_callerNotProver(address caller) public { vm.assume(caller != relayerA && caller != relayerB); bridge({caller: userA, msgValue: 0, params: tokenParams}); - expectUnauthorized(caller, fastBridge.PROVER_ROLE()); + vm.expectRevert(ProverNotActive.selector); prove({caller: caller, bridgeTx: tokenTx, destTxHash: hex"01"}); } + function test_prove_revert_disputePenaltyTime() public { + bytes32 txId = getTxId(tokenTx); + bridge({caller: userA, msgValue: 0, params: tokenParams}); + prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"}); + dispute({caller: guard, txId: txId}); + vm.expectRevert(ProverNotActive.selector); + prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"02"}); + skip(DISPUTE_PENALTY_TIME - 1); + vm.expectRevert(ProverNotActive.selector); + prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"02"}); + } + // ════════════════════════════════════════ PROVE OTHER RELAYER ════════════════════════════════════════════ function test_proveOther_token() public { @@ -360,7 +392,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bridge({caller: userA, msgValue: 0, params: tokenParams}); expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"}); prove({caller: relayerB, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); - checkStatusAndProofAfterProve(txId, relayerA); + checkStatusAndProofAfterProve(txId, 2, relayerA); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); } @@ -372,7 +404,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"}); prove({caller: relayerB, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); - checkStatusAndProofAfterProve(txId, relayerA); + checkStatusAndProofAfterProve(txId, 2, relayerA); assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount); assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); } @@ -383,7 +415,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bridge({caller: userA, msgValue: 0, params: tokenParams}); expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"}); prove({caller: relayerA, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); - checkStatusAndProofAfterProve(txId, relayerA); + checkStatusAndProofAfterProve(txId, 1, relayerA); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); } @@ -395,7 +427,19 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bridge({caller: userA, msgValue: 0, params: tokenParams}); expectBridgeProofProvided({txId: txId, relayer: address(0x1234), destTxHash: hex"01"}); prove({caller: relayerA, transactionId: txId, destTxHash: hex"01", relayer: address(0x1234)}); - checkStatusAndProofAfterProve(txId, address(0x1234)); + checkStatusAndProofAfterProve(txId, 1, address(0x1234)); + assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); + assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); + } + + function test_proveOther_afterDispute() public { + bytes32 txId = getTxId(tokenTx); + bridge({caller: userA, msgValue: 0, params: tokenParams}); + prove({caller: relayerA, relayer: relayerB, transactionId: txId, destTxHash: hex"01"}); + dispute({caller: guard, txId: txId}); + expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"02"}); + prove({caller: relayerB, relayer: relayerA, transactionId: txId, destTxHash: hex"02"}); + checkStatusAndProofAfterProve(txId, 2, relayerA); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); } @@ -406,17 +450,19 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"}); prove({caller: relayerB, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); - checkStatusAndProofAfterProve(txId, relayerA); + checkStatusAndProofAfterProve(txId, 2, relayerA); expectBridgeProofDisputed(txId, relayerA); dispute(guard, txId); + skip(DISPUTE_PENALTY_TIME); expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"02"}); prove({caller: relayerB, transactionId: txId, destTxHash: hex"02", relayer: relayerA}); - checkStatusAndProofAfterProve(txId, relayerA); + checkStatusAndProofAfterProve(txId, 2, relayerA); expectBridgeProofDisputed(txId, relayerA); dispute(guard, txId); + skip(DISPUTE_PENALTY_TIME); expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"03"}); prove({caller: relayerB, transactionId: txId, destTxHash: hex"03", relayer: relayerA}); - checkStatusAndProofAfterProve(txId, relayerA); + checkStatusAndProofAfterProve(txId, 2, relayerA); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); } @@ -428,7 +474,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(10 days); expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"}); prove({caller: relayerA, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); - checkStatusAndProofAfterProve(txId, relayerA); + checkStatusAndProofAfterProve(txId, 1, relayerA); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); } @@ -464,18 +510,44 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bytes32 txId = getTxId(tokenTx); vm.assume(caller != relayerA && caller != relayerB); bridge({caller: userA, msgValue: 0, params: tokenParams}); - expectUnauthorized(caller, fastBridge.PROVER_ROLE()); + vm.expectRevert(ProverNotActive.selector); prove({caller: caller, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); } + function test_proveOther_revert_disputePenaltyTime() public { + bytes32 txId = getTxId(tokenTx); + bridge({caller: userA, msgValue: 0, params: tokenParams}); + prove({caller: relayerA, relayer: relayerB, transactionId: txId, destTxHash: hex"01"}); + dispute({caller: guard, txId: txId}); + vm.expectRevert(ProverNotActive.selector); + prove({caller: relayerA, relayer: relayerB, transactionId: txId, destTxHash: hex"02"}); + skip(DISPUTE_PENALTY_TIME - 1); + vm.expectRevert(ProverNotActive.selector); + prove({caller: relayerA, relayer: relayerB, transactionId: txId, destTxHash: hex"02"}); + } + // ═══════════════════════════════════════════════════ CLAIM ═══════════════════════════════════════════════════════ - function checkStatusAndProofAfterClaim(bytes32 txId, address relayer, uint256 expectedProofTS) public view { + function checkStatusAndProofAfterClaim( + bytes32 txId, + uint16 expectedProverID, + address relayer, + uint256 expectedProofTS + ) + public + view + { assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED); - (IFastBridgeV2.BridgeStatus status, uint32 destChainId, uint256 proofBlockTimestamp, address proofRelayer) = - fastBridge.bridgeTxDetails(txId); + ( + IFastBridgeV2.BridgeStatus status, + uint32 destChainId, + uint16 proverID, + uint256 proofBlockTimestamp, + address proofRelayer + ) = fastBridge.bridgeTxDetails(txId); assertEq(status, IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED); assertEq(destChainId, DST_CHAIN_ID); + assertEq(proverID, expectedProverID); assertEq(proofBlockTimestamp, expectedProofTS); assertEq(proofRelayer, relayer); (proofBlockTimestamp, proofRelayer) = fastBridge.bridgeProofs(txId); @@ -499,7 +571,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { assertTrue(fastBridge.canClaim(txId, relayerA)); expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: relayerA, bridgeTx: tokenTx, to: relayerA}); - checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); + checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS); checkTokenBalancesAfterClaim(relayerA); } @@ -512,7 +584,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(CLAIM_DELAY + 1); expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: caller, bridgeTx: tokenTx}); - checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); + checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS); checkTokenBalancesAfterClaim(relayerA); } @@ -525,7 +597,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(CLAIM_DELAY + 1); expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: caller, bridgeTx: tokenTx, to: address(0)}); - checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); + checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS); checkTokenBalancesAfterClaim(relayerA); } @@ -537,7 +609,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(CLAIM_DELAY + 1); expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: claimTo}); claim({caller: relayerA, bridgeTx: tokenTx, to: claimTo}); - checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); + checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS); assertEq(srcToken.balanceOf(relayerA), 0); checkTokenBalancesAfterClaim(claimTo); } @@ -550,7 +622,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(CLAIM_DELAY + 30 days); expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: relayerA, bridgeTx: tokenTx, to: relayerA}); - checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); + checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS); checkTokenBalancesAfterClaim(relayerA); } @@ -571,7 +643,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { assertTrue(fastBridge.canClaim(txId, relayerA)); expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: relayerA, bridgeTx: ethTx, to: relayerA}); - checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); + checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS); checkEthBalancesAfterClaim(relayerA); } @@ -585,7 +657,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(CLAIM_DELAY + 1); expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: caller, bridgeTx: ethTx}); - checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); + checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS); checkEthBalancesAfterClaim(relayerA); } @@ -599,7 +671,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(CLAIM_DELAY + 1); expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: caller, bridgeTx: ethTx, to: address(0)}); - checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); + checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS); checkEthBalancesAfterClaim(relayerA); } @@ -612,7 +684,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(CLAIM_DELAY + 1); expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: claimTo}); claim({caller: relayerA, bridgeTx: ethTx, to: claimTo}); - checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); + checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS); checkEthBalancesAfterClaim(claimTo); } @@ -625,7 +697,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(CLAIM_DELAY + 30 days); expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: relayerA, bridgeTx: ethTx, to: relayerA}); - checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); + checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS); checkEthBalancesAfterClaim(relayerA); } @@ -704,15 +776,59 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { checkStatusAndProofAfterBridge(txId); } + function checkProver(address prover, uint16 expectedProverID, uint256 expectedActiveFromTimestamp) public view { + (uint16 proverID, uint256 activeFromTimestamp) = fastBridge.getProverInfo(prover); + assertEq(proverID, expectedProverID); + assertEq(activeFromTimestamp, expectedActiveFromTimestamp); + address p; + (p, activeFromTimestamp) = fastBridge.getProverInfoByID(expectedProverID); + assertEq(p, prover); + assertEq(activeFromTimestamp, expectedActiveFromTimestamp); + } + function test_dispute_token() public { bytes32 txId = getTxId(tokenTx); bridge({caller: userA, msgValue: 0, params: tokenParams}); prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"}); + expectDisputePenaltyTimeApplied({prover: relayerA}); expectBridgeProofDisputed({txId: txId, relayer: relayerA}); dispute({caller: guard, txId: txId}); checkStatusAndProofAfterDispute(txId); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); + // Check disputed prover + assertEq(fastBridge.getActiveProverID(relayerA), 0); + checkProver(relayerA, 1, block.timestamp + DISPUTE_PENALTY_TIME); + } + + function test_dispute_token_provedOther() public { + bytes32 txId = getTxId(tokenTx); + bridge({caller: userA, msgValue: 0, params: tokenParams}); + prove({caller: relayerA, relayer: relayerB, transactionId: txId, destTxHash: hex"01"}); + expectDisputePenaltyTimeApplied({prover: relayerA}); + expectBridgeProofDisputed({txId: txId, relayer: relayerB}); + dispute({caller: guard, txId: txId}); + checkStatusAndProofAfterDispute(txId); + assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); + assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); + // Check disputed prover + assertEq(fastBridge.getActiveProverID(relayerA), 0); + checkProver(relayerA, 1, block.timestamp + DISPUTE_PENALTY_TIME); + } + + function test_dispute_token_proverAlreadyRemoved() public { + bytes32 txId = getTxId(tokenTx); + bridge({caller: userA, msgValue: 0, params: tokenParams}); + prove({caller: relayerA, relayer: relayerB, transactionId: txId, destTxHash: hex"01"}); + fastBridge.removeProver(relayerA); + expectBridgeProofDisputed({txId: txId, relayer: relayerB}); + dispute({caller: guard, txId: txId}); + checkStatusAndProofAfterDispute(txId); + assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); + assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); + // Check disputed prover + assertEq(fastBridge.getActiveProverID(relayerA), 0); + checkProver(relayerA, 1, 0); } function test_dispute_token_justBeforeDeadline() public { @@ -720,36 +836,79 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bridge({caller: userA, msgValue: 0, params: tokenParams}); prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"}); skip(CLAIM_DELAY); + expectDisputePenaltyTimeApplied({prover: relayerA}); expectBridgeProofDisputed({txId: txId, relayer: relayerA}); dispute({caller: guard, txId: txId}); checkStatusAndProofAfterDispute(txId); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); + // Check disputed prover + assertEq(fastBridge.getActiveProverID(relayerA), 0); + checkProver(relayerA, 1, block.timestamp + DISPUTE_PENALTY_TIME); } function test_dispute_eth() public { bridge({caller: userA, msgValue: 0, params: tokenParams}); bytes32 txId = getTxId(ethTx); bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); - prove({caller: relayerA, bridgeTx: ethTx, destTxHash: hex"01"}); + prove({caller: relayerB, bridgeTx: ethTx, destTxHash: hex"01"}); + expectDisputePenaltyTimeApplied({prover: relayerB}); + expectBridgeProofDisputed({txId: txId, relayer: relayerB}); + dispute({caller: guard, txId: txId}); + checkStatusAndProofAfterDispute(txId); + assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); + assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount); + // Check disputed prover + assertEq(fastBridge.getActiveProverID(relayerB), 0); + checkProver(relayerB, 2, block.timestamp + DISPUTE_PENALTY_TIME); + } + + function test_dispute_eth_provedOther() public { + bridge({caller: userA, msgValue: 0, params: tokenParams}); + bytes32 txId = getTxId(ethTx); + bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); + prove({caller: relayerB, relayer: relayerA, transactionId: txId, destTxHash: hex"01"}); + expectDisputePenaltyTimeApplied({prover: relayerB}); expectBridgeProofDisputed({txId: txId, relayer: relayerA}); dispute({caller: guard, txId: txId}); checkStatusAndProofAfterDispute(txId); assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount); + // Check disputed prover + assertEq(fastBridge.getActiveProverID(relayerB), 0); + checkProver(relayerB, 2, block.timestamp + DISPUTE_PENALTY_TIME); + } + + function test_dispute_eth_proverAlreadyRemoved() public { + bridge({caller: userA, msgValue: 0, params: tokenParams}); + bytes32 txId = getTxId(ethTx); + bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); + prove({caller: relayerB, relayer: relayerA, transactionId: txId, destTxHash: hex"01"}); + fastBridge.removeProver(relayerB); + dispute({caller: guard, txId: txId}); + checkStatusAndProofAfterDispute(txId); + assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); + assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount); + // Check disputed prover + assertEq(fastBridge.getActiveProverID(relayerB), 0); + checkProver(relayerB, 2, 0); } function test_dispute_eth_justBeforeDeadline() public { bridge({caller: userA, msgValue: 0, params: tokenParams}); bytes32 txId = getTxId(ethTx); bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); - prove({caller: relayerA, bridgeTx: ethTx, destTxHash: hex"01"}); + prove({caller: relayerB, bridgeTx: ethTx, destTxHash: hex"01"}); skip(CLAIM_DELAY); - expectBridgeProofDisputed({txId: txId, relayer: relayerA}); + expectDisputePenaltyTimeApplied({prover: relayerB}); + expectBridgeProofDisputed({txId: txId, relayer: relayerB}); dispute({caller: guard, txId: txId}); checkStatusAndProofAfterDispute(txId); assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount); + // Check disputed prover + assertEq(fastBridge.getActiveProverID(relayerB), 0); + checkProver(relayerB, 2, block.timestamp + DISPUTE_PENALTY_TIME); } function test_dispute_revert_afterDeadline() public { @@ -806,10 +965,16 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { function checkStatusAndProofAfterCancel(bytes32 txId) public view { assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED); - (IFastBridgeV2.BridgeStatus status, uint32 destChainId, uint256 proofBlockTimestamp, address proofRelayer) = - fastBridge.bridgeTxDetails(txId); + ( + IFastBridgeV2.BridgeStatus status, + uint32 destChainId, + uint16 proverID, + uint256 proofBlockTimestamp, + address proofRelayer + ) = fastBridge.bridgeTxDetails(txId); assertEq(status, IFastBridgeV2.BridgeStatus.REFUNDED); assertEq(destChainId, DST_CHAIN_ID); + assertEq(proverID, 0); assertEq(proofBlockTimestamp, 0); assertEq(proofRelayer, address(0)); (proofBlockTimestamp, proofRelayer) = fastBridge.bridgeProofs(txId); diff --git a/packages/contracts-rfq/test/FastBridgeV2.t.sol b/packages/contracts-rfq/test/FastBridgeV2.t.sol index 5a99259ad2..d4fab0a1ae 100644 --- a/packages/contracts-rfq/test/FastBridgeV2.t.sol +++ b/packages/contracts-rfq/test/FastBridgeV2.t.sol @@ -9,16 +9,17 @@ import {IFastBridge} from "../contracts/interfaces/IFastBridge.sol"; import {IFastBridgeV2} from "../contracts/interfaces/IFastBridgeV2.sol"; import {FastBridgeV2} from "../contracts/FastBridgeV2.sol"; +import {IAdminV2Errors} from "../contracts/interfaces/IAdminV2Errors.sol"; import {IFastBridgeV2Errors} from "../contracts/interfaces/IFastBridgeV2Errors.sol"; -import {MockERC20} from "./MockERC20.sol"; +import {MockERC20} from "./mocks/MockERC20.sol"; import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; import {Test} from "forge-std/Test.sol"; import {StdStorage, stdStorage} from "forge-std/Test.sol"; // solhint-disable no-empty-blocks, max-states-count, ordering -abstract contract FastBridgeV2Test is Test, IFastBridgeV2Errors { +abstract contract FastBridgeV2Test is Test, IAdminV2Errors, IFastBridgeV2Errors { using stdStorage for StdStorage; uint32 public constant SRC_CHAIN_ID = 1337; diff --git a/packages/contracts-rfq/test/UniversalTokenLibHarness.sol b/packages/contracts-rfq/test/harnesses/UniversalTokenLibHarness.sol similarity index 93% rename from packages/contracts-rfq/test/UniversalTokenLibHarness.sol rename to packages/contracts-rfq/test/harnesses/UniversalTokenLibHarness.sol index 5e628b5fa2..0b2d2bc234 100644 --- a/packages/contracts-rfq/test/UniversalTokenLibHarness.sol +++ b/packages/contracts-rfq/test/harnesses/UniversalTokenLibHarness.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {UniversalTokenLib} from "../contracts/libs/UniversalToken.sol"; +import {UniversalTokenLib} from "../../contracts/libs/UniversalToken.sol"; // solhint-disable no-empty-blocks, ordering contract UniversalTokenLibHarness { diff --git a/packages/contracts-rfq/test/integration/FastBridgeV2.MulticallTarget.t.sol b/packages/contracts-rfq/test/integration/FastBridgeV2.MulticallTarget.t.sol index 4a23e98169..794d0c9210 100644 --- a/packages/contracts-rfq/test/integration/FastBridgeV2.MulticallTarget.t.sol +++ b/packages/contracts-rfq/test/integration/FastBridgeV2.MulticallTarget.t.sol @@ -8,9 +8,9 @@ import {IFastBridge, MulticallTargetIntegrationTest} from "./MulticallTarget.t.s contract FastBridgeV2MulticallTargetTest is MulticallTargetIntegrationTest { function deployAndConfigureFastBridge() public override returns (address) { - FastBridgeV2 fastBridge = new FastBridgeV2(address(this)); - fastBridge.grantRole(fastBridge.PROVER_ROLE(), relayer); - return address(fastBridge); + FastBridgeV2 fb = new FastBridgeV2(address(this)); + fb.addProver(relayer); + return address(fb); } function getEncodedBridgeTx(IFastBridge.BridgeTransaction memory bridgeTx) diff --git a/packages/contracts-rfq/test/integration/MulticallTarget.t.sol b/packages/contracts-rfq/test/integration/MulticallTarget.t.sol index c844086c46..0e43522346 100644 --- a/packages/contracts-rfq/test/integration/MulticallTarget.t.sol +++ b/packages/contracts-rfq/test/integration/MulticallTarget.t.sol @@ -6,7 +6,7 @@ import {IFastBridgeV2} from "../../contracts/interfaces/IFastBridgeV2.sol"; import {IMulticallTarget} from "../../contracts/interfaces/IMulticallTarget.sol"; import {DisputePeriodNotPassed} from "../../contracts/libs/Errors.sol"; -import {MockERC20} from "../MockERC20.sol"; +import {MockERC20} from "../mocks/MockERC20.sol"; import {Test} from "forge-std/Test.sol"; diff --git a/packages/contracts-rfq/test/integration/TokenZapV1.t.sol b/packages/contracts-rfq/test/integration/TokenZapV1.t.sol index fec2ee9042..12b5f9f49a 100644 --- a/packages/contracts-rfq/test/integration/TokenZapV1.t.sol +++ b/packages/contracts-rfq/test/integration/TokenZapV1.t.sol @@ -6,7 +6,7 @@ import {BridgeTransactionV2Lib} from "../../contracts/libs/BridgeTransactionV2.s import {ZapDataV1} from "../../contracts/libs/ZapDataV1.sol"; import {TokenZapV1} from "../../contracts/zaps/TokenZapV1.sol"; -import {MockERC20} from "../MockERC20.sol"; +import {MockERC20} from "../mocks/MockERC20.sol"; import {VaultManyArguments} from "../mocks/VaultManyArguments.sol"; import {Test} from "forge-std/Test.sol"; @@ -45,7 +45,7 @@ abstract contract TokenZapV1IntegrationTest is Test { function setUp() public virtual { fastBridge = new FastBridgeV2(address(this)); - fastBridge.grantRole(fastBridge.PROVER_ROLE(), relayer); + fastBridge.addProver(relayer); srcToken = new MockERC20("SRC", 18); dstToken = new MockERC20("DST", 18); diff --git a/packages/contracts-rfq/test/UniversalTokenLib.t.sol b/packages/contracts-rfq/test/libs/UniversalTokenLib.t.sol similarity index 96% rename from packages/contracts-rfq/test/UniversalTokenLib.t.sol rename to packages/contracts-rfq/test/libs/UniversalTokenLib.t.sol index ce46c08b19..8e900e9918 100644 --- a/packages/contracts-rfq/test/UniversalTokenLib.t.sol +++ b/packages/contracts-rfq/test/libs/UniversalTokenLib.t.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TokenNotContract} from "../contracts/libs/Errors.sol"; +import {TokenNotContract} from "../../contracts/libs/Errors.sol"; -import {MockERC20} from "./MockERC20.sol"; -import {MockRevertingRecipient} from "./MockRevertingRecipient.sol"; -import {UniversalTokenLibHarness} from "./UniversalTokenLibHarness.sol"; +import {UniversalTokenLibHarness} from "../harnesses/UniversalTokenLibHarness.sol"; +import {MockERC20} from "../mocks/MockERC20.sol"; +import {MockRevertingRecipient} from "../mocks/MockRevertingRecipient.sol"; import {Test} from "forge-std/Test.sol"; diff --git a/packages/contracts-rfq/test/FastBridgeMock.sol b/packages/contracts-rfq/test/mocks/FastBridgeMock.sol similarity index 96% rename from packages/contracts-rfq/test/FastBridgeMock.sol rename to packages/contracts-rfq/test/mocks/FastBridgeMock.sol index 68026550f5..d2f7bcb3c1 100644 --- a/packages/contracts-rfq/test/FastBridgeMock.sol +++ b/packages/contracts-rfq/test/mocks/FastBridgeMock.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {Admin} from "../contracts/Admin.sol"; +import {Admin} from "../../contracts/Admin.sol"; -import {FastBridge} from "../contracts/FastBridge.sol"; -import {IFastBridge} from "../contracts/interfaces/IFastBridge.sol"; +import {FastBridge} from "../../contracts/FastBridge.sol"; +import {IFastBridge} from "../../contracts/interfaces/IFastBridge.sol"; contract FastBridgeMock is IFastBridge, Admin { // @dev the block the contract was deployed at diff --git a/packages/contracts-rfq/test/MockERC20.sol b/packages/contracts-rfq/test/mocks/MockERC20.sol similarity index 100% rename from packages/contracts-rfq/test/MockERC20.sol rename to packages/contracts-rfq/test/mocks/MockERC20.sol diff --git a/packages/contracts-rfq/test/MockRevertingRecipient.sol b/packages/contracts-rfq/test/mocks/MockRevertingRecipient.sol similarity index 100% rename from packages/contracts-rfq/test/MockRevertingRecipient.sol rename to packages/contracts-rfq/test/mocks/MockRevertingRecipient.sol diff --git a/packages/contracts-rfq/test/zaps/TokenZapV1.GasBench.t.sol b/packages/contracts-rfq/test/zaps/TokenZapV1.GasBench.t.sol index 3726b7d7b0..5352a5e4fb 100644 --- a/packages/contracts-rfq/test/zaps/TokenZapV1.GasBench.t.sol +++ b/packages/contracts-rfq/test/zaps/TokenZapV1.GasBench.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.24; import {TokenZapV1} from "../../contracts/zaps/TokenZapV1.sol"; -import {MockERC20} from "../MockERC20.sol"; +import {MockERC20} from "../mocks/MockERC20.sol"; import {SimpleVaultMock} from "../mocks/SimpleVaultMock.sol"; import {Test} from "forge-std/Test.sol"; diff --git a/packages/contracts-rfq/test/zaps/TokenZapV1.t.sol b/packages/contracts-rfq/test/zaps/TokenZapV1.t.sol index 68afd42331..e081831372 100644 --- a/packages/contracts-rfq/test/zaps/TokenZapV1.t.sol +++ b/packages/contracts-rfq/test/zaps/TokenZapV1.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.24; import {ZapDataV1} from "../../contracts/libs/ZapDataV1.sol"; import {TokenZapV1} from "../../contracts/zaps/TokenZapV1.sol"; -import {MockERC20} from "../MockERC20.sol"; +import {MockERC20} from "../mocks/MockERC20.sol"; import {NonPayableRecipient} from "../mocks/NonPayableRecipient.sol"; import {RecipientMock} from "../mocks/RecipientMock.sol"; import {VaultManyArguments} from "../mocks/VaultManyArguments.sol"; diff --git a/packages/synapse-interface/CHANGELOG.md b/packages/synapse-interface/CHANGELOG.md index 12d15839cd..d68d4a38b3 100644 --- a/packages/synapse-interface/CHANGELOG.md +++ b/packages/synapse-interface/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.40.23](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-interface@0.40.22...@synapsecns/synapse-interface@0.40.23) (2024-12-06) + +**Note:** Version bump only for package @synapsecns/synapse-interface + + + + + ## [0.40.22](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-interface@0.40.21...@synapsecns/synapse-interface@0.40.22) (2024-12-02) diff --git a/packages/synapse-interface/components/layouts/LandingPageWrapper/index.tsx b/packages/synapse-interface/components/layouts/LandingPageWrapper/index.tsx index b8b2ec294c..aef82543a5 100644 --- a/packages/synapse-interface/components/layouts/LandingPageWrapper/index.tsx +++ b/packages/synapse-interface/components/layouts/LandingPageWrapper/index.tsx @@ -211,9 +211,9 @@ function MoreInfoButtons() { description="See preliminary analytics of the bridge" /> ) diff --git a/packages/synapse-interface/components/toast/ToastContent.tsx b/packages/synapse-interface/components/toast/ToastContent.tsx index 361cfee462..3d138fddd1 100644 --- a/packages/synapse-interface/components/toast/ToastContent.tsx +++ b/packages/synapse-interface/components/toast/ToastContent.tsx @@ -1,3 +1,4 @@ +import React from 'react' import toast from 'react-hot-toast' import { XIcon, diff --git a/packages/synapse-interface/components/toast/index.tsx b/packages/synapse-interface/components/toast/index.tsx index 0ef7675d2c..83d4bde824 100644 --- a/packages/synapse-interface/components/toast/index.tsx +++ b/packages/synapse-interface/components/toast/index.tsx @@ -1,7 +1,9 @@ +// @ts-nocheck +import React from 'react' import toast, { Toaster, ToastBar } from 'react-hot-toast' import ToastContent from './ToastContent' -export default function CustomToaster() { +const CustomToaster: React.FC = () => { return ( ) } + +export default CustomToaster diff --git a/packages/synapse-interface/constants/routes.ts b/packages/synapse-interface/constants/routes.ts index b33db9f19e..1381e2a87c 100644 --- a/packages/synapse-interface/constants/routes.ts +++ b/packages/synapse-interface/constants/routes.ts @@ -6,8 +6,8 @@ import { POOL_PATH, LANDING_PATH, BRIDGE_PATH, - INTERCHAIN_LINK, SOLANA_BRIDGE_LINK, + SYN_TOKEN_LINK, } from './urls' export interface RouteObject { @@ -53,9 +53,9 @@ export const NAVIGATION: RouteObject = { text: 'Explorer', match: null, }, - Contracts: { - path: INTERCHAIN_LINK, - text: 'Interchain Network', + SYN: { + path: SYN_TOKEN_LINK, + text: '$SYN', match: null, }, Solana: { diff --git a/packages/synapse-interface/constants/urls/index.tsx b/packages/synapse-interface/constants/urls/index.tsx index a3cac309ad..5c1c5df241 100644 --- a/packages/synapse-interface/constants/urls/index.tsx +++ b/packages/synapse-interface/constants/urls/index.tsx @@ -25,6 +25,7 @@ export const LANDING_PATH = '/landing' export const EXPLORER_KAPPA = 'https://explorer.synapseprotocol.com/tx/' export const EXPLORER_PATH = 'https://explorer.synapseprotocol.com/' export const INTERCHAIN_LINK = 'https://interchain.synapseprotocol.com/' +export const SYN_TOKEN_LINK = 'https://docs.synapseprotocol.com/docs/About/SYN' export const SOLANA_BRIDGE_LINK = 'https://solana.synapseprotocol.com/' export const TERMS_OF_SERVICE_PATH = 'https://explorer.synapseprotocol.com/terms' diff --git a/packages/synapse-interface/messages/ar.json b/packages/synapse-interface/messages/ar.json index 33849d6878..09f07631d4 100644 --- a/packages/synapse-interface/messages/ar.json +++ b/packages/synapse-interface/messages/ar.json @@ -344,6 +344,7 @@ "Telegram": "تليجرام", "Functions": "الوظائف", "Developers": "المطورون", + "$SYN": "$SYN", "Support": "الدعم" }, "ReturnToMonke": { diff --git a/packages/synapse-interface/messages/en-US.json b/packages/synapse-interface/messages/en-US.json index ce150f5e79..8452406b49 100644 --- a/packages/synapse-interface/messages/en-US.json +++ b/packages/synapse-interface/messages/en-US.json @@ -344,6 +344,7 @@ "Telegram": "Telegram", "Functions": "Functions", "Developers": "Developers", + "$SYN": "$SYN", "Support": "Support" }, "ReturnToMonke": { diff --git a/packages/synapse-interface/messages/es.json b/packages/synapse-interface/messages/es.json index d35b19a8e8..5c121dfbe6 100644 --- a/packages/synapse-interface/messages/es.json +++ b/packages/synapse-interface/messages/es.json @@ -344,6 +344,7 @@ "Telegram": "Telegram", "Functions": "Funciones", "Developers": "Desarrolladores", + "$SYN": "$SYN", "Support": "Soporte" }, "ReturnToMonke": { diff --git a/packages/synapse-interface/messages/fr.json b/packages/synapse-interface/messages/fr.json index 3298973c04..5f4608d38e 100644 --- a/packages/synapse-interface/messages/fr.json +++ b/packages/synapse-interface/messages/fr.json @@ -344,6 +344,7 @@ "Telegram": "Telegram", "Functions": "Fonctions", "Developers": "Développeurs", + "$SYN": "$SYN", "Support": "Support" }, "ReturnToMonke": { diff --git a/packages/synapse-interface/messages/jp.json b/packages/synapse-interface/messages/jp.json index 8dff093e1d..ee05d2a385 100644 --- a/packages/synapse-interface/messages/jp.json +++ b/packages/synapse-interface/messages/jp.json @@ -344,6 +344,7 @@ "Telegram": "Telegram", "Functions": "機能", "Developers": "開発者", + "$SYN": "$SYN", "Support": "サポート" }, "ReturnToMonke": { diff --git a/packages/synapse-interface/messages/tr.json b/packages/synapse-interface/messages/tr.json index a6ac858168..d51a91667a 100644 --- a/packages/synapse-interface/messages/tr.json +++ b/packages/synapse-interface/messages/tr.json @@ -344,6 +344,7 @@ "Telegram": "Telegram", "Functions": "Fonksiyonlar", "Developers": "Geliştiriciler", + "$SYN": "$SYN", "Support": "Destek" }, "ReturnToMonke": { diff --git a/packages/synapse-interface/messages/zh-CN.json b/packages/synapse-interface/messages/zh-CN.json index fc4d1e5d93..d6a864354b 100644 --- a/packages/synapse-interface/messages/zh-CN.json +++ b/packages/synapse-interface/messages/zh-CN.json @@ -344,6 +344,7 @@ "Telegram": "Telegram", "Functions": "功能", "Developers": "开发者", + "$SYN": "$SYN", "Support": "支持" }, "ReturnToMonke": { diff --git a/packages/synapse-interface/package.json b/packages/synapse-interface/package.json index 27a5660b73..d2e2ddb88e 100644 --- a/packages/synapse-interface/package.json +++ b/packages/synapse-interface/package.json @@ -1,6 +1,6 @@ { "name": "@synapsecns/synapse-interface", - "version": "0.40.22", + "version": "0.40.23", "private": true, "engines": { "node": ">=18.18.0" diff --git a/packages/synapse-interface/pages/_app.tsx b/packages/synapse-interface/pages/_app.tsx index 74e50e8d27..83acb1d8e5 100644 --- a/packages/synapse-interface/pages/_app.tsx +++ b/packages/synapse-interface/pages/_app.tsx @@ -52,6 +52,7 @@ function App({ Component, pageProps }: AppProps) { timeZone="UTC" messages={pageProps.messages} > + {/* @ts-ignore */}