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.addWhitelistToken{value: nativeFee}(
clientChainId,
whitelistTokensBytes32[i],
decimals[i],
tvlLimits[i],
names[i],
metaDatas[i],
oracleInfos[i]
);
}
vm.stopBroadcast();
}

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

// do not deploy the vault for the virtual token address representing natively staked ETH
// deploy the corresponding vault if not deployed before
if (token != VIRTUAL_STAKED_ETH_ADDRESS && address(tokenToVault[token]) == address(0)) {
// 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. however, we still do ensure that the vault is not deployed
// for restaking natively staked ETH.
if (token != VIRTUAL_STAKED_ETH_ADDRESS) {
_deployVault(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
41 changes: 17 additions & 24 deletions src/core/ClientGatewayLzReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -290,41 +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);

// do not deploy the vault for the virtual token address representing natively staked ETH
// 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);
// since tokens cannot be removed from the whitelist, it is not possible for a vault
// to already exist. however, we should still ensure that a vault is not deployed for
// restaking native staked eth
if (token != VIRTUAL_STAKED_ETH_ADDRESS) {
_deployVault(token);
}
emit WhitelistTokenAdded(token);
MaxMustermann2 marked this conversation as resolved.
Show resolved Hide resolved
}

}
114 changes: 50 additions & 64 deletions src/core/ExocoreGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -177,51 +177,63 @@ 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 addWhitelistToken(
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]
);

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 (msg.value == 0) {
MaxMustermann2 marked this conversation as resolved.
Show resolved Hide resolved
revert Errors.ZeroValue();
}

if (!updated) {
require(clientChainId != 0, "ExocoreGateway: client chain id cannot be zero");
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 = ASSETS_CONTRACT.registerToken(
clientChainId,
abi.encodePacked(token), // convert to bytes from bytes32
decimals,
tvlLimit,
name,
metaData,
oracleInfo
);
if (success) {
emit WhitelistTokenAdded(clientChainId, token);
_sendInterchainMsg(
clientChainId,
Action.REQUEST_ADD_WHITELIST_TOKENS,
abi.encodePacked(uint8(tokens.length), tokens),
Action.REQUEST_ADD_WHITELIST_TOKEN,
abi.encodePacked(token), // convert for decoding it on the receiving end
false
);
} else {
revert AddWhitelistTokenFailed(clientChainId, token);
}
}

/// @inheritdoc IExocoreGateway
function updateWhitelistToken(uint32 clientChainId, bytes32 token, uint256 tvlLimit, string calldata metaData)
external
onlyOwner
whenNotPaused
nonReentrant
{
require(clientChainId != 0, "ExocoreGateway: client chain id cannot be zero");
require(token != bytes32(0), "ExocoreGateway: token cannot be zero address");
// setting tvlLimit to 0 is allowed as a way to disable the token
// empty metaData indicates that the token's metadata should not be updated
bool success = ASSETS_CONTRACT.updateToken(clientChainId, abi.encodePacked(token), tvlLimit, metaData);
if (success) {
emit WhitelistTokenUpdated(clientChainId, token);
} else {
revert UpdateWhitelistTokenFailed(clientChainId, token);
}
}

Expand Down Expand Up @@ -258,32 +270,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
39 changes: 27 additions & 12 deletions src/interfaces/IExocoreGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,36 @@ 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.
/// @dev Previously, we tried to use this function for multiple tokens, but that
/// results in too many local variables (stack too deep).
function addWhitelistToken(
MaxMustermann2 marked this conversation as resolved.
Show resolved Hide resolved
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;

/// @notice Updates the parameters for a whitelisted token on the client chain.
/// @param clientChainId The LayerZero chain id of the client chain.
/// @param token The token address to be updated.
/// @param tvlLimit The new TVL limit of the token.
/// @param metaData The new meta information of the token.
/// @dev The token must exist in the whitelist before updating.
/// @dev Since this function does not send a cross chain message, it is not payable.
function updateWhitelistToken(uint32 clientChainId, bytes32 token, uint256 tvlLimit, string calldata metaData)
external;
MaxMustermann2 marked this conversation as resolved.
Show resolved Hide resolved

}
29 changes: 22 additions & 7 deletions src/interfaces/precompiles/IAssets.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,41 @@ interface IAssets {
string calldata signatureType
) external returns (bool success, bool updated);

/// @dev register or update token addresses to exocore
/// @dev register a token to allow deposits / staking, etc.
/// @dev note that there is no way to delete a token. If a token is to be removed,
/// the TVL limit should be set to 0.
/// @param clientChainID is the identifier of the token's home chain (LZ or otherwise)
/// @param clientChainId is the identifier of the token's home chain (LZ or otherwise)
/// @param token is the address of the token on the home chain
/// @param decimals is the number of decimals of the token
/// @param tvlLimit is the number of tokens that can be deposited in the system. Set to
/// 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(
uint32 clientChainID,
function registerToken(
uint32 clientChainId,
bytes calldata token,
uint8 decimals,
uint256 tvlLimit,
string calldata name,
string calldata metaData
) external returns (bool success, bool updated);
string calldata metaData,
string calldata oracleInfo
) external returns (bool success);

/// @dev update a token to allow deposits / staking, etc.
/// @param clientChainId is the identifier of the token's home chain (LZ or otherwise)
/// @param token is the address of the token on the home chain
/// @param tvlLimit is the number of tokens that can be deposited in the system. Set to
/// maxSupply if there is no limit
/// @param metaData is the arbitrary metadata of the token
/// @return success if the token update is successful
/// @dev The token must previously be registered before updating
/// @dev Pass a tvlLimit of 0 to disable any deposits of the token
/// @dev Pass en empty metadata to keep the existing metadata
function updateToken(uint32 clientChainId, bytes calldata token, uint256 tvlLimit, string calldata metaData)
external
returns (bool success);

/// QUERIES
/// @dev Returns the chain indices of the client chains.
Expand Down
Loading
Loading