Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4874dcc
feat: initial commit
Hakob23 Aug 26, 2025
65bfce1
test: Adding unit tests
Hakob23 Aug 27, 2025
9214f0a
fix: linter sugested fixes
Hakob23 Aug 27, 2025
681cb70
refactor: introduce IFlyoverDiscoverySplit interface and update Flyov…
Hakob23 Aug 27, 2025
3684a51
chore: Natspec comments
Hakob23 Aug 27, 2025
2c740bf
feat: add resign functionality for Liquidity Providers in FlyoverDisc…
Hakob23 Aug 27, 2025
0d1264e
remove unnecessary comment
Hakob23 Aug 27, 2025
21fc675
Update contracts/FlyoverDiscovery.sol
Hakob23 Aug 27, 2025
b0c1112
Style: improve code readability and correct loop boundaries into strict
Hakob23 Aug 28, 2025
d56e04e
refactor: add reward percentage to collateral management deployment
Hakob23 Aug 28, 2025
df9c6e8
refactor: remove unused events and functions from IFlyoverDiscovery i…
Hakob23 Aug 29, 2025
79e3db9
refactor: streamline FlyoverDiscovery contract by removing unused sta…
Hakob23 Aug 29, 2025
82be529
refactor: remove legacy tests and unused variables from FlyoverDiscov…
Hakob23 Aug 29, 2025
0ee4900
refactor: change registration process by adding collateral management…
Hakob23 Aug 29, 2025
6a55bd9
test: update tests to reflect register flow chnages
Hakob23 Aug 29, 2025
a3c7035
test: add comprehensive tests for collateral allocation in FlyoverDis…
Hakob23 Aug 29, 2025
c017180
doc: update FlyoverDiscovery and IFlyoverDiscovery interfaces to inhe…
Hakob23 Aug 29, 2025
45b33ab
Update contracts/interfaces/IFlyoverDiscovery.sol
Hakob23 Sep 2, 2025
034ccd8
fix: emit registration event before _addCollateral to avoid unnecessa…
Hakob23 Sep 2, 2025
b297c78
fix: add validation to ensure collateralManagement is a contract duri…
Hakob23 Sep 2, 2025
13267f7
refactor: simplify isOperational function by removing unused provider…
Hakob23 Sep 2, 2025
90ba891
refactor: reorder conditions in isOperational function
Hakob23 Sep 2, 2025
974d625
refactor: streamline deployDiscoveryFixture by utilizing existing col…
Hakob23 Sep 2, 2025
78f61bc
test: update resign tests to utilize collateral management and improv…
Hakob23 Sep 2, 2025
b93fe5f
refactor: remove PoC versions of Flyover discovery contract
Hakob23 Sep 2, 2025
fceb959
refactor: update isOperational function to accept providerType and ad…
Hakob23 Sep 8, 2025
0c29d33
test: update address-dependant tests
Luisfc68 Sep 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ This method requests the Bridge contract on RSK a refund for the service.

### **isOperational**

function isOperational(address addr) external view returns (bool)
function isOperational(Flyover.ProviderType providerType, address addr) external view returns (bool)

Checks whether a liquidity provider can deliver a pegin service

Expand All @@ -132,10 +132,6 @@ Checks whether a liquidity provider can deliver a pegin service

Whether the liquidity provider is registered and has enough locked collateral

### **isOperationalForPegout**

function isOperationalForPegout(address addr) external view returns (bool)

Checks whether a liquidity provider can deliver a pegout service

#### Parametets
Expand Down
234 changes: 234 additions & 0 deletions contracts/FlyoverDiscovery.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/* solhint-disable comprehensive-interface */

import {
AccessControlDefaultAdminRulesUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol";
import {ICollateralManagement} from "./interfaces/ICollateralManagement.sol";
import {IFlyoverDiscovery} from "./interfaces/IFlyoverDiscovery.sol";
import {Flyover} from "./libraries/Flyover.sol";

/// @title FlyoverDiscovery
/// @notice Registry and discovery of Liquidity Providers (LPs) for Flyover
/// @dev Keeps LP metadata and consults `ICollateralManagement` to decide listing and operational status
contract FlyoverDiscovery is
AccessControlDefaultAdminRulesUpgradeable,
IFlyoverDiscovery
{

// ------------------------------------------------------------
// FlyoverDiscovery State Variables
// ------------------------------------------------------------

mapping(uint => Flyover.LiquidityProvider) private _liquidityProviders;
ICollateralManagement private _collateralManagement;
uint public lastProviderId;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now that we have a getter perhaps this should be private


// ------------------------------------------------------------
// FlyoverDiscovery Public Functions and Modifiers
// ------------------------------------------------------------

/// @notice Initializes the contract with admin configuration
/// @dev Uses OZ upgradeable admin rules. Must be called only once
/// @param owner The Default Admin and initial owner address
/// @param initialDelay The initial admin delay for `AccessControlDefaultAdminRulesUpgradeable`
/// @param collateralManagement The address of the `ICollateralManagement` contract
function initialize(
address owner,
uint48 initialDelay,
address collateralManagement
) external initializer {
if (collateralManagement.code.length == 0) revert Flyover.NoContract(collateralManagement);
__AccessControlDefaultAdminRules_init(initialDelay, owner);
_collateralManagement = ICollateralManagement(collateralManagement);
Comment thread
Luisfc68 marked this conversation as resolved.
}

/// @inheritdoc IFlyoverDiscovery
function register(
string calldata name,
string calldata apiBaseUrl,
bool status,
Flyover.ProviderType providerType
) external payable returns (uint) {
Comment thread
Luisfc68 marked this conversation as resolved.

_validateRegistration(name, apiBaseUrl, providerType, msg.sender, msg.value);

++lastProviderId;
_liquidityProviders[lastProviderId] = Flyover.LiquidityProvider({
id: lastProviderId,
providerAddress: msg.sender,
name: name,
apiBaseUrl: apiBaseUrl,
status: status,
providerType: providerType
});
emit Register(lastProviderId, msg.sender, msg.value);
_addCollateral(providerType, msg.sender, msg.value);
return (lastProviderId);
}

/// @inheritdoc IFlyoverDiscovery
function setProviderStatus(
uint providerId,
bool status
) external {
if (msg.sender != owner() && msg.sender != _liquidityProviders[providerId].providerAddress) {
revert NotAuthorized(msg.sender);
}
_liquidityProviders[providerId].status = status;
emit IFlyoverDiscovery.ProviderStatusSet(providerId, status);
}

/// @inheritdoc IFlyoverDiscovery
function updateProvider(string calldata name, string calldata apiBaseUrl) external {
if (bytes(name).length < 1 || bytes(apiBaseUrl).length < 1) revert InvalidProviderData(name, apiBaseUrl);
Flyover.LiquidityProvider storage lp;
address providerAddress = msg.sender;
for (uint i = 1; i < lastProviderId + 1; ++i) {
lp = _liquidityProviders[i];
if (providerAddress == lp.providerAddress) {
lp.name = name;
lp.apiBaseUrl = apiBaseUrl;
emit IFlyoverDiscovery.ProviderUpdate(providerAddress, lp.name, lp.apiBaseUrl);
return;
}
}
revert Flyover.ProviderNotRegistered(providerAddress);
}

/// @inheritdoc IFlyoverDiscovery
function getProviders() external view returns (Flyover.LiquidityProvider[] memory) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation is the same as in the legacy version so it is ok, however, now that _shouldBeListed involves an external call we might want to reduce the usage of that function to only once, this the proposal:

function getProviders() external view returns (Flyover.LiquidityProvider[] memory) {
        uint256[] memory matchingLps = new uint256[](lastProviderId);
        uint count = 0;
        Flyover.LiquidityProvider storage lp;
        for (uint i = 1; i <= lastProviderId; i++) {
            lp = _liquidityProviders[i];
            if (_shouldBeListed(lp)) {
                matchingLps[count] = lp.id;
                count++;
            }
        }
        Flyover.LiquidityProvider[] memory providers = new Flyover.LiquidityProvider[](count);
        for (uint i = 0; i < count; i++) {
            providers[i] = _liquidityProviders[matchingLps[i]];
        }
        return providers;
    }

    function _shouldBeListed(Flyover.LiquidityProvider storage lp) private view returns(bool){
        return lp.status && collateralManagement.isRegistered(lp.providerType, lp.providerAddress);
    }

(is not tested is just one proposal to implement the same but reducing the external calls, is taken from the FlyoverDiscoveryContract version used in the benchmarks with some modifications)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I maintain this comment from the previous review

uint count = 0;
Flyover.LiquidityProvider storage lp;
for (uint i = 1; i < lastProviderId + 1; ++i) {
if (_shouldBeListed(_liquidityProviders[i])) {
++count;
}
}
Flyover.LiquidityProvider[] memory providersToReturn = new Flyover.LiquidityProvider[](count);
count = 0;
for (uint i = 1; i < lastProviderId + 1; ++i) {
lp = _liquidityProviders[i];
if (_shouldBeListed(lp)) {
providersToReturn[count] = lp;
++count;
}
}
return providersToReturn;
}

/// @inheritdoc IFlyoverDiscovery
function getProvider(address providerAddress) external view returns (Flyover.LiquidityProvider memory) {
return _getProvider(providerAddress);
}

/// @inheritdoc IFlyoverDiscovery
function isOperational(Flyover.ProviderType providerType, address addr) external view returns (bool) {
return _getProvider(addr).status &&
_collateralManagement.isCollateralSufficient(providerType, addr);
}

// ------------------------------------------------------------
// Getter Functions
// ------------------------------------------------------------

/// @inheritdoc IFlyoverDiscovery
function getProvidersId() external view returns (uint) {
return lastProviderId;
}

// ------------------------------------------------------------
// FlyoverDiscovery Private Functions
// ------------------------------------------------------------

/// @notice Adds collateral to the collateral management contract based on provider type
/// @dev Distributes collateral between peg-in and peg-out based on provider type
/// @param providerType The type of provider (PegIn, PegOut, or Both)
/// @param providerAddress The address of the provider
/// @param collateralAmount The total amount of collateral to add
function _addCollateral(
Flyover.ProviderType providerType,
address providerAddress,
uint256 collateralAmount
) private {
if (providerType == Flyover.ProviderType.PegIn) {
_collateralManagement.addPegInCollateralTo{value: collateralAmount}(providerAddress);
} else if (providerType == Flyover.ProviderType.PegOut) {
_collateralManagement.addPegOutCollateralTo{value: collateralAmount}(providerAddress);
} else if (providerType == Flyover.ProviderType.Both) {
uint256 halfAmount = collateralAmount / 2;
uint256 remainder = collateralAmount % 2;
_collateralManagement.addPegInCollateralTo{value: halfAmount + remainder}(providerAddress);
_collateralManagement.addPegOutCollateralTo{value: halfAmount}(providerAddress);
}
}

/// @notice Checks if a liquidity provider should be listed in the public provider list
/// @dev A provider is listed if it is registered and has status enabled
/// @param lp The liquidity provider storage reference
/// @return True if the provider should be listed, false otherwise
function _shouldBeListed(Flyover.LiquidityProvider storage lp) private view returns(bool){
Comment thread
Hakob23 marked this conversation as resolved.
return _collateralManagement.isRegistered(lp.providerType, lp.providerAddress) && lp.status;
}
Comment on lines +172 to +174

Check notice

Code scanning / Slither

Calls inside a loop Low

FlyoverDiscovery._shouldBeListed(Flyover.LiquidityProvider) has external calls inside a loop: _collateralManagement.isRegistered(lp.providerType,lp.providerAddress) && lp.status
Calls stack containing the loop:
FlyoverDiscovery.getProviders()

/// @notice Validates registration parameters and requirements
/// @dev Checks EOA status, data validity, provider type, registration status, and collateral requirements
/// @param name The provider name to validate
/// @param apiBaseUrl The provider API URL to validate
/// @param providerType The provider type to validate
/// @param providerAddress The provider address to validate
/// @param collateralAmount The collateral amount to validate against minimum requirements
function _validateRegistration(
string memory name,
string memory apiBaseUrl,
Flyover.ProviderType providerType,
address providerAddress,
uint256 collateralAmount
) private view {
if (providerAddress != msg.sender || providerAddress.code.length != 0) revert NotEOA(providerAddress);

if (
bytes(name).length < 1 ||
bytes(apiBaseUrl).length < 1
) {
revert InvalidProviderData(name, apiBaseUrl);
}

if (providerType > type(Flyover.ProviderType).max) revert InvalidProviderType(providerType);

if (
_collateralManagement.getPegInCollateral(providerAddress) > 0 ||
_collateralManagement.getPegOutCollateral(providerAddress) > 0 ||
_collateralManagement.getResignationBlock(providerAddress) != 0
) {
revert AlreadyRegistered(providerAddress);
}

// Check minimum collateral requirement
uint256 minCollateral = _collateralManagement.getMinCollateral();
if (providerType == Flyover.ProviderType.Both) {
if (collateralAmount < minCollateral * 2) {
revert InsufficientCollateral(collateralAmount);
}
} else {
if (collateralAmount < minCollateral) {
revert InsufficientCollateral(collateralAmount);
}
}
}

/// @notice Retrieves a liquidity provider by address
/// @dev Searches through all registered providers to find a match
/// @param providerAddress The address of the provider to find
/// @return The liquidity provider record, reverts if not found
function _getProvider(address providerAddress) private view returns (Flyover.LiquidityProvider memory) {
for (uint i = 1; i < lastProviderId + 1; ++i) {
if (_liquidityProviders[i].providerAddress == providerAddress) {
return _liquidityProviders[i];
}
}
revert Flyover.ProviderNotRegistered(providerAddress);
}
}
2 changes: 1 addition & 1 deletion contracts/PegOutContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ contract PegOutContract is
_pegOutQuotes[quoteHash] = quote;
_pegOutRegistry[quoteHash].depositTimestamp = block.timestamp;

emit PegOutDeposit(quoteHash, msg.sender, msg.value, block.timestamp);
emit PegOutDeposit(quoteHash, msg.sender, block.timestamp, msg.value);

if (dustThreshold > msg.value - requiredAmount) {
return;
Expand Down
40 changes: 37 additions & 3 deletions contracts/interfaces/IFlyoverDiscovery.sol
Comment thread
Luisfc68 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,50 @@ interface IFlyoverDiscovery {
error AlreadyRegistered(address from);
error InsufficientCollateral(uint amount);

/// @notice Registers the caller as a Liquidity Provider
/// @dev Reverts if caller is not an EOA, already resigned, provides invalid data, invalid type, or lacks collateral
/// @param name Human-readable LP name
/// @param apiBaseUrl Base URL of the LP public API
/// @param status Initial status flag (enabled/disabled)
/// @param providerType The service type(s) the LP offers
/// @return id The newly assigned LP identifier
function register(
string memory name,
string memory apiBaseUrl,
string calldata name,
string calldata apiBaseUrl,
bool status,
Flyover.ProviderType providerType
) external payable returns (uint);

function updateProvider(string memory name,string memory apiBaseUrl) external;
/// @notice Updates the caller LP metadata
/// @dev Reverts if the caller is not registered or provides invalid fields
/// @param name New LP name
/// @param apiBaseUrl New LP API base URL
function updateProvider(string calldata name, string calldata apiBaseUrl) external;

/// @notice Updates a provider status flag
/// @dev Callable by the LP itself or the contract owner
/// @param providerId The provider identifier
/// @param status The new status value
function setProviderStatus(uint providerId, bool status) external;

/// @notice Lists LPs that should be visible to users
/// @dev A provider is listed if it has sufficient collateral for at least one side and `status` is true
/// @return providersToReturn Array of LP records to display
function getProviders() external view returns (Flyover.LiquidityProvider[] memory);

/// @notice Returns a single LP by address
/// @param providerAddress The LP address
/// @return provider LP record, reverts if not found
function getProvider(address providerAddress) external view returns (Flyover.LiquidityProvider memory);

/// @notice Checks if an LP can operate for a specific type of operation
/// @dev Ignores the first argument as compatibility stub with legacy signature
/// @param providerType The type of provider
/// @param addr The LP address
/// @return isOp True if registered and peg-in collateral >= min
function isOperational(Flyover.ProviderType providerType, address addr) external view returns (bool);

/// @notice Returns the last assigned provider id
/// @return lastId Last provider id
function getProvidersId() external view returns (uint);
}
Loading