Skip to content

Commit

Permalink
feat(exo-gateway): add oracleInfo
Browse files Browse the repository at this point in the history
ExocoreNetwork/exocore#159 adds a new `oracleInfo` parameter to the
`IAssets.sol` precompile when registering tokens. This PR follows that
change.

Previously, multiple tokens could be registered with a single call to
ExocoreGateway. However, that resulted in too many variables (and too
much gas) for Solidity to handle within a single function. To that end,
and keeping in mind that addition of new tokens isn't likely to be a
frequent occurrence, the function's capabilities have been tempered to
support only a single token within the transaction.

As a side effect, this fixes ExocoreNetwork#80
  • Loading branch information
MaxMustermann2 committed Aug 31, 2024
1 parent 0a2fa70 commit 37ec728
Show file tree
Hide file tree
Showing 19 changed files with 356 additions and 442 deletions.
40 changes: 27 additions & 13 deletions script/3_Setup.s.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
pragma solidity ^0.8.19;

import "../src/interfaces/IClientChainGateway.sol";
import {GatewayStorage} from "../src/storage/GatewayStorage.sol";

import "../src/interfaces/IClientChainGateway.sol";
import "../src/interfaces/IExocoreGateway.sol";
import "../src/interfaces/IVault.sol";

import {NonShortCircuitEndpointV2Mock} from "../test/mocks/NonShortCircuitEndpointV2Mock.sol";

import {BaseScript} from "./BaseScript.sol";
Expand Down Expand Up @@ -86,41 +88,53 @@ contract SetupScript is BaseScript {
exocoreGateway.registerOrUpdateClientChain(
clientChainId, address(clientGateway).toBytes32(), 20, "ClientChain", "EVM compatible network", "secp256k1"
);
vm.stopBroadcast();

// 3. adding tokens to the whtielist of both Exocore and client chain gateway to enable restaking

// first we read decimals from client chain ERC20 token contract to prepare for token data
vm.selectFork(clientChain);
bytes32[] memory whitelistTokensBytes32 = new bytes32[](2);
uint8[] memory decimals = new uint8[](2);
uint256[] memory tvlLimits = new uint256[](2);
string[] memory names = new string[](2);
string[] memory metaData = new string[](2);
string[] memory metaDatas = new string[](2);
string[] memory oracleInfos = new string[](2);

// this stands for LST restaking for restakeToken
whitelistTokensBytes32[0] = bytes32(bytes20(address(restakeToken)));
decimals[0] = restakeToken.decimals();
tvlLimits[0] = 1e10 ether;
names[0] = "RestakeToken";
metaData[0] = "ERC20 LST token";
metaDatas[0] = "ERC20 LST token";
oracleInfos[0] = "{'a': 'b'}";

// this stands for Native Restaking for ETH
whitelistTokensBytes32[1] = bytes32(bytes20(VIRTUAL_STAKED_ETH_ADDRESS));
decimals[1] = 18;
tvlLimits[1] = 1e8 ether;
names[1] = "StakedETH";
metaData[1] = "natively staked ETH on Ethereum";
vm.stopBroadcast();
metaDatas[1] = "natively staked ETH on Ethereum";
oracleInfos[1] = "{'b': 'a'}";

// second add whitelist tokens and their meta data on Exocore side to enable LST Restaking and Native Restaking,
// and this would also add token addresses to client chain gateway's whitelist
vm.selectFork(exocore);
uint256 messageLength = TOKEN_ADDRESS_BYTES_LENGTH * whitelistTokensBytes32.length + 2;
uint256 nativeFee = exocoreGateway.quote(clientChainId, new bytes(messageLength));
exocoreGateway.addOrUpdateWhitelistTokens{value: nativeFee}(
clientChainId, whitelistTokensBytes32, decimals, tvlLimits, names, metaData
);
uint256 nativeFee;
for (uint256 i = 0; i < whitelistTokensBytes32.length; i++) {
nativeFee = exocoreGateway.quote(
clientChainId,
abi.encodePacked(
GatewayStorage.Action.REQUEST_ADD_WHITELIST_TOKEN, abi.encodePacked(whitelistTokensBytes32[i])
)
);
exocoreGateway.addOrUpdateWhitelistToken{value: nativeFee}(
clientChainId,
whitelistTokensBytes32[i],
decimals[i],
tvlLimits[i],
names[i],
metaDatas[i],
oracleInfos[i]
);
}
vm.stopBroadcast();
}

Expand Down
8 changes: 4 additions & 4 deletions src/core/Bootstrap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,10 @@ contract Bootstrap is
whitelistTokens.push(token);
isWhitelistedToken[token] = true;

// deploy the corresponding vault if not deployed before
if (address(tokenToVault[token]) == address(0)) {
_deployVault(token);
}
// tokens cannot be removed from the whitelist. hence, if the token is not in the
// whitelist, it means that it is missing a vault. we do not need to check for a
// pre-existing vault.
_deployVault(token);

emit WhitelistTokenAdded(token);
}
Expand Down
4 changes: 2 additions & 2 deletions src/core/ClientChainGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ contract ClientChainGateway is
revert Errors.ZeroAddress();
}

_whiteListFunctionSelectors[Action.REQUEST_ADD_WHITELIST_TOKENS] =
this.afterReceiveAddWhitelistTokensRequest.selector;
_whiteListFunctionSelectors[Action.REQUEST_ADD_WHITELIST_TOKEN] =
this.afterReceiveAddWhitelistTokenRequest.selector;

bootstrapped = true;

Expand Down
40 changes: 17 additions & 23 deletions src/core/ClientGatewayLzReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -290,40 +290,34 @@ abstract contract ClientGatewayLzReceiver is PausableUpgradeable, OAppReceiverUp
}
}

/// @notice Called after an add-whitelist-tokens response is received.
/// @notice Called after an add-whitelist-token response is received.
/// @param requestPayload The request payload.
// Though `_deployVault` would make external call to newly created `Vault` contract and initialize it,
// `Vault` contract belongs to Exocore and we could make sure its implementation does not have dangerous behavior
// like reentrancy.
// slither-disable-next-line reentrancy-no-eth
function afterReceiveAddWhitelistTokensRequest(bytes calldata requestPayload)
function afterReceiveAddWhitelistTokenRequest(bytes calldata requestPayload)
public
onlyCalledFromThis
whenNotPaused
{
uint8 count = uint8(requestPayload[0]);
uint256 expectedLength = count * TOKEN_ADDRESS_BYTES_LENGTH + 1;
if (requestPayload.length != expectedLength) {
revert InvalidAddWhitelistTokensRequest(expectedLength, requestPayload.length);
address token = address(bytes20(abi.decode(requestPayload, (bytes32))));
if (token == address(0)) {
revert Errors.ZeroAddress();
}

for (uint256 i = 0; i < count; ++i) {
uint256 start = i * TOKEN_ADDRESS_BYTES_LENGTH + 1;
uint256 end = start + TOKEN_ADDRESS_BYTES_LENGTH;
address token = address(bytes20(requestPayload[start:end]));

if (!isWhitelistedToken[token]) {
isWhitelistedToken[token] = true;
whitelistTokens.push(token);

// deploy the corresponding vault if not deployed before
if (token != VIRTUAL_STAKED_ETH_ADDRESS && address(tokenToVault[token]) == address(0)) {
_deployVault(token);
}

emit WhitelistTokenAdded(token);
}
if (isWhitelistedToken[token]) {
// grave error, should never happen
revert Errors.ClientChainGatewayAlreadyWhitelisted(token);
}
isWhitelistedToken[token] = true;
whitelistTokens.push(token);
// deploy the corresponding vault if not deployed before
if (token != VIRTUAL_STAKED_ETH_ADDRESS) {
// since it is no longer possible to remove a token from the whitelist, the check for existing vault
// is not necessary.
_deployVault(token);
}
emit WhitelistTokenAdded(token);
}

}
103 changes: 39 additions & 64 deletions src/core/ExocoreGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -177,51 +177,52 @@ contract ExocoreGateway is
/// @notice If we want to activate client chain's native restaking, we should add the corresponding virtual
/// token address to the whitelist, bytes32(bytes20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) for Ethereum
/// native restaking for example.
function addOrUpdateWhitelistTokens(
function addOrUpdateWhitelistToken(
uint32 clientChainId,
bytes32[] calldata tokens,
uint8[] calldata decimals,
uint256[] calldata tvlLimits,
string[] calldata names,
string[] calldata metaData
bytes32 token,
uint8 decimals,
uint256 tvlLimit,
string calldata name,
string calldata metaData,
string calldata oracleInfo
) external payable onlyOwner whenNotPaused nonReentrant {
// The registration of the client chain is left for the precompile to validate.
_validateWhitelistTokensInput(tokens, decimals, tvlLimits, names, metaData);

bool success;
bool updated;
for (uint256 i = 0; 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]
);
require(token != bytes32(0), "ExocoreGateway: token cannot be zero address");
require(tvlLimit > 0, "ExocoreGateway: tvl limit should not be zero");
require(bytes(name).length != 0, "ExocoreGateway: name cannot be empty");
require(bytes(metaData).length != 0, "ExocoreGateway: meta data cannot be empty");
require(bytes(oracleInfo).length != 0, "ExocoreGateway: oracleInfo cannot be empty");

(bool success, bool updated) = ASSETS_CONTRACT.registerOrUpdateTokens(
clientChainId,
abi.encodePacked(token), // convert to bytes from bytes32
decimals,
tvlLimit,
name,
metaData,
oracleInfo
);

if (success) {
if (!updated) {
emit WhitelistTokenAdded(clientChainId, tokens[i]);
} else {
emit WhitelistTokenUpdated(clientChainId, tokens[i]);
if (success) {
if (!updated) {
if (msg.value == 0) {
revert Errors.ZeroValue();
}
emit WhitelistTokenAdded(clientChainId, token);
_sendInterchainMsg(
clientChainId,
Action.REQUEST_ADD_WHITELIST_TOKEN,
abi.encodePacked(token), // convert for decoding it on the receiving end
false
);
} else {
if (!updated) {
revert AddWhitelistTokenFailed(tokens[i]);
} else {
revert UpdateWhitelistTokenFailed(tokens[i]);
if (msg.value != 0) {
revert Errors.NonZeroValue();
}
emit WhitelistTokenUpdated(clientChainId, token);
}
}

if (!updated) {
_sendInterchainMsg(
clientChainId,
Action.REQUEST_ADD_WHITELIST_TOKENS,
abi.encodePacked(uint8(tokens.length), tokens),
false
);
} else {
// if the precompile call didn't succeed, we don't know if it is an update.
revert AddOrUpdateWhitelistTokenFailed(token);
}
}

Expand Down Expand Up @@ -258,32 +259,6 @@ contract ExocoreGateway is
}
}

/// @dev Validates the input for whitelist tokens.
/// @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(
bytes32[] calldata tokens,
uint8[] calldata decimals,
uint256[] calldata tvlLimits,
string[] calldata names,
string[] calldata metaData
) internal pure {
uint256 expectedLength = tokens.length;
if (expectedLength > type(uint8).max) {
revert WhitelistTokensListTooLong();
}

if (
decimals.length != expectedLength || tvlLimits.length != expectedLength || names.length != expectedLength
|| metaData.length != expectedLength
) {
revert InvalidWhitelistTokensInput();
}
}

/// @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
Expand Down
30 changes: 18 additions & 12 deletions src/interfaces/IExocoreGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,27 @@ interface IExocoreGateway is IOAppReceiver, IOAppCore {
string calldata signatureType
) external;

/// @notice Adds a list of whitelisted tokens to the client chain.
/// @notice Add a single whitelisted token 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.
/// @param token The token address to be whitelisted.
/// @param decimals The decimals of the token.
/// @param tvlLimit The TVL limit of the token.
/// @param name The name of the token.
/// @param metaData The meta information of the token.
/// @param oracleInfo The oracle information of the token.
/// @dev The chain must be registered before adding tokens.
function addOrUpdateWhitelistTokens(
/// @dev This function is payable because it sends a message to the client chain if
/// the token is not already registered.
/// @dev Previously, we tried to use this function for multiple tokens, but that
/// results in too many local variables (stack too deep).
function addOrUpdateWhitelistToken(
uint32 clientChainId,
bytes32[] calldata tokens,
uint8[] calldata decimals,
uint256[] calldata tvlLimits,
string[] calldata names,
string[] calldata metaData
bytes32 token,
uint8 decimals,
uint256 tvlLimit,
string calldata name,
string calldata metaData,
string calldata oracleInfo
) external payable;

}
4 changes: 3 additions & 1 deletion src/interfaces/precompiles/IAssets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ interface IAssets {
/// maxSupply if there is no limit
/// @param name is the name of the token
/// @param metaData is the arbitrary metadata of the token
/// @param oracleInfo is the oracle information of the token
/// @return success if the token registration is successful
/// @return updated whether the token was added or updated
function registerOrUpdateTokens(
Expand All @@ -73,7 +74,8 @@ interface IAssets {
uint8 decimals,
uint256 tvlLimit,
string calldata name,
string calldata metaData
string calldata metaData,
string calldata oracleInfo
) external returns (bool success, bool updated);

/// QUERIES
Expand Down
7 changes: 3 additions & 4 deletions src/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@ library Errors {
/// @dev Thrown when passed-in amount is zero
error ZeroAmount();

/// @dev Thrown when the passed-in value is not zero
/// @dev Thrown when the passed-in msg.value is not zero but should be
error NonZeroValue();

/// @dev Thrown wehn the passed-in value is zero
/// @dev This is used when the value in question is not an amount
/// @dev Thrown when the passed-in msg.value is zero but should not be
error ZeroValue();

/// @dev Index out of array bounds
Expand Down Expand Up @@ -124,7 +123,7 @@ library Errors {
error ClientChainGatewayAddWhitelistTooManyTokens();

/// @dev ClientChainGateway: token should not be whitelisted before
error ClientChainGatewayAlreadyWhitelisted();
error ClientChainGatewayAlreadyWhitelisted(address token);

//////////////////////////////////////
// ClientGatewayLzReceiver Errors //
Expand Down
8 changes: 2 additions & 6 deletions src/storage/ExocoreGatewayStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,9 @@ contract ExocoreGatewayStorage is GatewayStorage {
/// @param clientChainId The LayerZero chain ID of the client chain.
error RegisterClientChainToExocoreFailed(uint32 clientChainId);

/// @notice Thrown when a whitelist token addition fails
/// @notice Thrown when a whitelist token addition or update fails
/// @param token The address of the token.
error AddWhitelistTokenFailed(bytes32 token);

/// @notice Thrown when a whitelist token update fails
/// @param token The address of the token.
error UpdateWhitelistTokenFailed(bytes32 token);
error AddOrUpdateWhitelistTokenFailed(bytes32 token);

/// @notice Thrown when the whitelist tokens input is invalid.
error InvalidWhitelistTokensInput();
Expand Down
2 changes: 1 addition & 1 deletion src/storage/GatewayStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ contract GatewayStorage {
REQUEST_UNDELEGATE_FROM,
REQUEST_DEPOSIT_THEN_DELEGATE_TO,
REQUEST_MARK_BOOTSTRAP,
REQUEST_ADD_WHITELIST_TOKENS,
REQUEST_ADD_WHITELIST_TOKEN,
REQUEST_ASSOCIATE_OPERATOR,
REQUEST_DISSOCIATE_OPERATOR,
RESPOND
Expand Down
Loading

0 comments on commit 37ec728

Please sign in to comment.