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

feat(exo-gateway): add oracleInfo #86

Merged
merged 12 commits into from
Sep 5, 2024
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]
);
}
MaxMustermann2 marked this conversation as resolved.
Show resolved Hide resolved
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);
MaxMustermann2 marked this conversation as resolved.
Show resolved Hide resolved

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
adu-web3 marked this conversation as resolved.
Show resolved Hide resolved
revert Errors.ClientChainGatewayAlreadyWhitelisted(token);
}
isWhitelistedToken[token] = true;
whitelistTokens.push(token);
// deploy the corresponding vault, if required
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);
MaxMustermann2 marked this conversation as resolved.
Show resolved Hide resolved
}
}

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.
MaxMustermann2 marked this conversation as resolved.
Show resolved Hide resolved
/// @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
Loading