Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(precompiles): chain as source of truth #66

Merged
merged 6 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
8 changes: 3 additions & 5 deletions docs/contracts-owner-manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ While for `Vault` and `ExoCapsule`, they are deployed with upgradeable beacon pr

After all contracts are deployed, before the protocol starts to work, there are a few works left to be done by contract owner to enable restaking. One of the most important tasks is to register the client chain id and meta info to Exocore to mark this client chain as valid. This is done by the contract caller calling `ExocoreGateway.registerOrUpdateClientChain` to write `clientChainId`, `addressLength`, `name`, `signatureType` and other meta data to Exocore native module to finish registration. This operation would also call `ExocoreGateway.setPeer` to enable LayerZero messaging by setting remote `ClientChainGateway` as trusted peer to send/receive messages. After finishing registration, contract owner could call `ExocoreGateway.registerOrUpdateClientChain` again to update the meta data and set new peer, or contract owner could solely call `ExocoreGateway.setPeer` to change the address of remote peer contract.

## add tokens to whitelist
## add or update tokens to whitelist

Another important task before restaking being activated is to add tokens to whitelist to mark them as stake-able on both Exocore and client chain. This is done by contract owner calling `ExocoreGateway.addWhitelistTokens` to write token addresses, decimals, TVL limits and other metadata to Exocore, as well as sending a cross-chain message through layerzero to client chain to add these token addresses to the whitelist of `ClientChainGateway`.
Another important task before restaking being activated is to add tokens to whitelist to mark them as stake-able on both Exocore and client chain. This is done by contract owner calling `ExocoreGateway.addOrUpdateWhitelistTokens` to write token addresses, decimals, TVL limits and other metadata to Exocore, as well as sending a cross-chain message through layerzero to client chain to add these token addresses to the whitelist of `ClientChainGateway`.

Notice: contract owner must make sure the token data is correct like address, decimals and TVL limit, more importantly contract owner must ensure that for the same index, the data in different arrays like `tokens`, `decimals`, `tvlLimits` must point to the same token to be composed as complete token data.

## upgrade the meta data of whitelisted tokens

After adding tokens to whitelist, contract owner could call `ExocoreGateway.updateWhitelistedTokens` to update the meta data of already whitelisted tokens, and this function would not send a cross-chain message to client chain since the whitelist of `ClientChainGateway` only stores the token addresses.
After adding tokens to whitelist, contract owner could call `ExocoreGateway.addOrUpdateWhitelistTokens` to update the meta data of already whitelisted tokens, and this function would not send a cross-chain message to client chain since the whitelist of `ClientChainGateway` only stores the token addresses.
2 changes: 1 addition & 1 deletion script/3_Setup.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ contract SetupScript is BaseScript {
vm.selectFork(exocore);
uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokensBytes32.length + 2;
uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength));
exocoreGateway.addWhitelistTokens{value: nativeFee}(
exocoreGateway.addOrUpdateWhitelistTokens{value: nativeFee}(
clientChainId, whitelistTokensBytes32, decimals, tvlLimits, names, metaData
);
vm.stopBroadcast();
Expand Down
167 changes: 71 additions & 96 deletions src/core/ExocoreGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -142,20 +142,20 @@ contract ExocoreGateway is
) {
revert Errors.ZeroValue();
}
// signature type could be left as empty for current implementation
_registerClientChain(clientChainId, addressLength, name, metaInfo, signatureType);

bool updated = _registerOrUpdateClientChain(clientChainId, addressLength, name, metaInfo, signatureType);
// the peer is always set, regardless of `updated`
super.setPeer(clientChainId, peer);

if (!isRegisteredClientChain[clientChainId]) {
isRegisteredClientChain[clientChainId] = true;
emit ClientChainRegistered(clientChainId);
} else {
if (updated) {
emit ClientChainUpdated(clientChainId);
} else {
emit ClientChainRegistered(clientChainId);
}
}

/// @notice Sets a peer on the destination chain for this contract.
/// @dev This is the LayerZero peer.
/// @dev This is the LayerZero peer. This function is only here for the modifiers.
/// @param clientChainId The id of the client chain.
/// @param clientChainGateway The address of the peer as bytes32.
function setPeer(uint32 clientChainId, bytes32 clientChainGateway)
Expand All @@ -164,35 +164,60 @@ contract ExocoreGateway is
onlyOwner
whenNotPaused
{
if (!isRegisteredClientChain[clientChainId]) {
revert Errors.ExocoreGatewayNotRegisteredClientChainId();
MaxMustermann2 marked this conversation as resolved.
Show resolved Hide resolved
}

// The registration of the client chain is done here and nowhere else.
// Elsewhere, the precompile is responsible for the checks. The precompile
// is not called here at all, and hence, such a check must be made manually.
_validateClientChainIdRegistered(clientChainId);
super.setPeer(clientChainId, clientChainGateway);
}

/// @inheritdoc IExocoreGateway
function addWhitelistTokens(
function addOrUpdateWhitelistTokens(
uint32 clientChainId,
bytes32[] calldata tokens,
uint8[] calldata decimals,
uint256[] calldata tvlLimits,
string[] calldata names,
string[] calldata metaData
) external payable onlyOwner whenNotPaused nonReentrant {
_addOrUpdateWhitelistTokens(clientChainId, tokens, decimals, tvlLimits, names, metaData, true);
}
// The registration of the client chain is left for the precompile to validate.
_validateWhitelistTokensInput(tokens, decimals, tvlLimits, names, metaData);

/// @inheritdoc IExocoreGateway
function updateWhitelistedTokens(
uint32 clientChainId,
bytes32[] calldata tokens,
uint8[] calldata decimals,
uint256[] calldata tvlLimits,
string[] calldata names,
string[] calldata metaData
) external onlyOwner whenNotPaused {
_addOrUpdateWhitelistTokens(clientChainId, tokens, decimals, tvlLimits, names, metaData, false);
bool success;
bool updated;
for (uint256 i; i < tokens.length; i++) {
require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address");
require(tvlLimits[i] > 0, "ExocoreGateway: tvl limit should not be zero");
require(bytes(names[i]).length != 0, "ExocoreGateway: name cannot be empty");
require(bytes(metaData[i]).length != 0, "ExocoreGateway: meta data cannot be empty");

(success, updated) = ASSETS_CONTRACT.registerOrUpdateTokens(
clientChainId, abi.encodePacked(tokens[i]), decimals[i], tvlLimits[i], names[i], metaData[i]
);

if (success) {
if (!updated) {
emit WhitelistTokenAdded(clientChainId, tokens[i]);
} else {
emit WhitelistTokenUpdated(clientChainId, tokens[i]);
}
} else {
if (!updated) {
revert AddWhitelistTokenFailed(tokens[i]);
} else {
revert UpdateWhitelistTokenFailed(tokens[i]);
}
}
}

if (!updated) {
_sendInterchainMsg(
clientChainId,
Action.REQUEST_ADD_WHITELIST_TOKENS,
abi.encodePacked(uint8(tokens.length), tokens),
false
);
}
}

/**
Expand Down Expand Up @@ -228,86 +253,19 @@ contract ExocoreGateway is
}
}

/// @dev The internal version of addWhitelistTokens and updateWhitelistedTokens.
/// @param clientChainId Source client chain id
/// @param tokens List of token addresses
/// @param decimals List of token decimals (like 18)
/// @param tvlLimits List of TVL limits (like max supply)
/// @param names List of token names
/// @param metaData List of arbitrary meta data for each token
/// @param add Whether to add or update the tokens
/// @dev Validates that lengths are equal, <= 255, and that the chain is registered.
// Though this function would call precompiled contract, all precompiled contracts belong to Exocore
// and we could make sure its implementation does not have dangerous behavior like reentrancy.
// slither-disable-next-line reentrancy-no-eth
function _addOrUpdateWhitelistTokens(
uint32 clientChainId,
bytes32[] calldata tokens,
uint8[] calldata decimals,
uint256[] calldata tvlLimits,
string[] calldata names,
string[] calldata metaData,
bool add
) internal {
_validateWhitelistTokensInput(clientChainId, tokens, decimals, tvlLimits, names, metaData);

for (uint256 i; i < tokens.length; i++) {
require(tokens[i] != bytes32(0), "ExocoreGateway: token cannot be zero address");
if (!add) {
require(isWhitelistedToken[tokens[i]], "ExocoreGateway: token has not been added to whitelist before");
}
require(tvlLimits[i] > 0, "ExocoreGateway: tvl limit should not be zero");
require(bytes(names[i]).length != 0, "ExocoreGateway: name cannot be empty");
require(bytes(metaData[i]).length != 0, "ExocoreGateway: meta data cannot be empty");

bool success = ASSETS_CONTRACT.registerToken(
clientChainId, abi.encodePacked(tokens[i]), decimals[i], tvlLimits[i], names[i], metaData[i]
);

if (success) {
if (add) {
isWhitelistedToken[tokens[i]] = true;
emit WhitelistTokenAdded(clientChainId, tokens[i]);
} else {
emit WhitelistTokenUpdated(clientChainId, tokens[i]);
}
} else {
if (add) {
revert AddWhitelistTokenFailed(tokens[i]);
} else {
revert UpdateWhitelistTokenFailed(tokens[i]);
}
}
}
if (add) {
_sendInterchainMsg(
clientChainId,
Action.REQUEST_ADD_WHITELIST_TOKENS,
abi.encodePacked(uint8(tokens.length), tokens),
false
);
}
}

/// @dev Validates the input for whitelist tokens.
/// @param clientChainId The client chain id, which must have been previously registered.
/// @param tokens The list of token addresses, length must be <= 255.
/// @param decimals The list of token decimals, length must be equal to that of @param tokens.
/// @param tvlLimits The list of token TVL limits, length must be equal to that of @param tokens.
/// @param names The list of token names, length must be equal to that of @param tokens.
/// @param metaData The list of token meta data, length must be equal to that of @param tokens.
function _validateWhitelistTokensInput(
uint32 clientChainId,
bytes32[] calldata tokens,
uint8[] calldata decimals,
uint256[] calldata tvlLimits,
string[] calldata names,
string[] calldata metaData
) internal view {
if (!isRegisteredClientChain[clientChainId]) {
revert ClientChainIDNotRegisteredBefore(clientChainId);
}

) internal pure {
uint256 expectedLength = tokens.length;
if (expectedLength > type(uint8).max) {
revert WhitelistTokensListTooLong();
Expand All @@ -321,23 +279,40 @@ contract ExocoreGateway is
}
}

/// @dev The internal version of registerClientChain.
/// @dev Validates that the client chain id is registered.
/// @dev This is designed to be called only in the cases wherein the precompile isn't used.
/// @dev In all other situations, it is the responsibility of the precompile to perform such
/// checks.
/// @param clientChainId The client chain id.
function _validateClientChainIdRegistered(uint32 clientChainId) internal view {
(bool success, bool isRegistered) = ASSETS_CONTRACT.isRegisteredClientChain(clientChainId);
if (!success) {
revert Errors.ExocoreGatewayFailedToCheckClientChainId();
}
if (!isRegistered) {
revert Errors.ExocoreGatewayNotRegisteredClientChainId();
}
}

/// @dev The internal version of registerOrUpdateClientChain.
/// @param clientChainId The client chain id.
/// @param addressLength The length of the address type on the client chain.
/// @param name The name of the client chain.
/// @param metaInfo The arbitrary metadata for the client chain.
/// @param signatureType The signature type supported by the client chain.
function _registerClientChain(
function _registerOrUpdateClientChain(
uint32 clientChainId,
uint8 addressLength,
string calldata name,
string calldata metaInfo,
string calldata signatureType
) internal {
bool success = ASSETS_CONTRACT.registerClientChain(clientChainId, addressLength, name, metaInfo, signatureType);
) internal returns (bool) {
(bool success, bool updated) =
ASSETS_CONTRACT.registerOrUpdateClientChain(clientChainId, addressLength, name, metaInfo, signatureType);
if (!success) {
revert RegisterClientChainToExocoreFailed(clientChainId);
}
return updated;
}

/// @inheritdoc OAppReceiverUpgradeable
Expand Down
19 changes: 1 addition & 18 deletions src/interfaces/IExocoreGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ interface IExocoreGateway is IOAppReceiver, IOAppCore {
/// @param names The names of the tokens, in the same order as the tokens list.
/// @param metaData The meta information of the tokens, in the same order as the tokens list.
/// @dev The chain must be registered before adding tokens.
function addWhitelistTokens(
function addOrUpdateWhitelistTokens(
uint32 clientChainId,
bytes32[] calldata tokens,
uint8[] calldata decimals,
Expand All @@ -58,21 +58,4 @@ interface IExocoreGateway is IOAppReceiver, IOAppCore {
string[] calldata metaData
) external payable;

/// @notice Updates a list of whitelisted tokens to the client chain.
/// @param clientChainId The LayerZero chain id of the client chain.
/// @param tokens The list of token addresses to be whitelisted.
/// @param decimals The list of token decimals, in the same order as the tokens list.
/// @param tvlLimits The list of token TVL limits (typically max supply),in the same order as the tokens list.
/// @param names The names of the tokens, in the same order as the tokens list.
/// @param metaData The meta information of the tokens, in the same order as the tokens list.
/// @dev The chain must be registered before updating tokens, and the token as well.
function updateWhitelistedTokens(
uint32 clientChainId,
bytes32[] calldata tokens,
uint8[] calldata decimals,
uint256[] calldata tvlLimits,
string[] calldata names,
string[] calldata metaData
) external;

}
Loading
Loading