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

update executor options for solana client controller #120

Merged
merged 7 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
52 changes: 42 additions & 10 deletions src/core/ExocoreGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,11 @@ contract ExocoreGateway is
}
emit LSTTransfer(isDeposit, success, bytes32(token), bytes32(staker), amount);

response = isDeposit ? bytes("") : abi.encodePacked(lzNonce, success);
if (_isSolana(srcChainId)) {
response = isDeposit ? bytes("") : abi.encodePacked(lzNonce, success, bytes32(token), bytes32(staker));
} else {
response = isDeposit ? bytes("") : abi.encodePacked(lzNonce, success);
}
}

/// @notice Handles NST transfer from a client chain.
Expand Down Expand Up @@ -419,7 +423,13 @@ contract ExocoreGateway is
}
emit RewardOperation(isSubmitReward, success, bytes32(token), bytes32(avsOrWithdrawer), amount);

response = isSubmitReward ? bytes("") : abi.encodePacked(lzNonce, success);
if (_isSolana(srcChainId)) {
response = isSubmitReward
? bytes("")
: abi.encodePacked(lzNonce, success, bytes32(token), bytes32(avsOrWithdrawer));
} else {
response = isSubmitReward ? bytes("") : abi.encodePacked(lzNonce, success);
}
}

/// @notice Handles delegation request from a client chain.
Expand Down Expand Up @@ -515,9 +525,9 @@ contract ExocoreGateway is
whenNotPaused
{
bytes memory payload = abi.encodePacked(act, actionArgs);
bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(
DESTINATION_GAS_LIMIT, DESTINATION_MSG_VALUE
).addExecutorOrderedExecutionOption();

bytes memory options = _buildOptions(srcChainId, act);

MessagingFee memory fee = _quote(srcChainId, payload, options, false);

address refundAddress = payByApp ? address(this) : msg.sender;
Expand All @@ -527,14 +537,36 @@ contract ExocoreGateway is
}

/// @inheritdoc IExocoreGateway
function quote(uint32 srcChainid, bytes calldata _message) public view returns (uint256 nativeFee) {
bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(
DESTINATION_GAS_LIMIT, DESTINATION_MSG_VALUE
).addExecutorOrderedExecutionOption();
MessagingFee memory fee = _quote(srcChainid, _message, options, false);
function quote(uint32 srcChainId, bytes calldata _message) public view returns (uint256 nativeFee) {
Action act = Action(uint8(_message[0]));

bytes memory options = _buildOptions(srcChainId, act);

MessagingFee memory fee = _quote(srcChainId, _message, options, false);
return fee.nativeFee;
}

/// @dev Builds options for interchain messages based on chain and action
/// @param srcChainId The source chain ID
/// @param act The action being performed
/// @return options The built options
function _buildOptions(uint32 srcChainId, Action act) private pure returns (bytes memory) {
bytes memory options = OptionsBuilder.newOptions();

if (!_isSolana(srcChainId)) {
// currently, LZ does not support ordered execution for Solana
options = options.addExecutorOrderedExecutionOption();
}

uint128 value = _isSolana(srcChainId) && act == Action.REQUEST_ADD_WHITELIST_TOKEN
? SOLANA_WHITELIST_TOKEN_MSG_VALUE
: DESTINATION_MSG_VALUE;

options = options.addExecutorLzReceiveOption(DESTINATION_GAS_LIMIT, value);

return options;
}

/// @inheritdoc OAppReceiverUpgradeable
function nextNonce(uint32 srcEid, bytes32 sender)
public
Expand Down
19 changes: 19 additions & 0 deletions src/storage/ExocoreGatewayStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ contract ExocoreGatewayStorage is GatewayStorage {
/// @dev The msg.value for all the destination chains.
uint128 internal constant DESTINATION_MSG_VALUE = 0;

/// constants used for solana mainnet chainId
/// @dev the solana mainnet chain id
uint32 internal constant SOLANA_MAINNET_CHAIN_ID = 30_168;

/// constants used for solana devnet chainId
/// @dev the solana devnet chain id
uint32 internal constant SOLANA_DEVNET_CHAIN_ID = 40_168;

/// @dev the msg.value for send addTokenWhiteList message
uint128 internal constant SOLANA_WHITELIST_TOKEN_MSG_VALUE = 3_000_000;

/// @notice Emitted when a precompile call fails.
/// @param precompile Address of the precompile contract.
/// @param nonce The LayerZero nonce
Expand Down Expand Up @@ -128,4 +139,12 @@ contract ExocoreGatewayStorage is GatewayStorage {
}
}

/**
* @dev return true if chain is either Solana devnet or Solana mainnet
* @param srcChainId remote Chain Id
*/
function _isSolana(uint32 srcChainId) internal pure returns (bool) {
return srcChainId == SOLANA_DEVNET_CHAIN_ID || srcChainId == SOLANA_MAINNET_CHAIN_ID;
}

}
56 changes: 53 additions & 3 deletions test/foundry/unit/ExocoreGateway.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,17 @@ contract SetUp is Test {

ExocoreGateway exocoreGateway;
ClientChainGateway clientGateway;
ClientChainGateway solanaClientGateway;

NonShortCircuitEndpointV2Mock exocoreLzEndpoint;
NonShortCircuitEndpointV2Mock clientLzEndpoint;
NonShortCircuitEndpointV2Mock solanaClientLzEndpoint;

ERC20 restakeToken;

uint16 exocoreChainId = 1;
uint16 clientChainId = 2;
uint16 solanaClientChainId = 40_168;

struct Player {
uint256 privateKey;
Expand All @@ -64,6 +69,7 @@ contract SetUp is Test {
deployer = Player({privateKey: uint256(0xb), addr: vm.addr(uint256(0xb))});
withdrawer = Player({privateKey: uint256(0xc), addr: vm.addr(uint256(0xb))});
clientGateway = ClientChainGateway(payable(address(0xd)));
solanaClientGateway = ClientChainGateway(payable(address(0xe)));

// bind precompile mock contracts code to constant precompile address
bytes memory AssetsMockCode = vm.getDeployedCode("AssetsMock.sol");
Expand All @@ -88,6 +94,7 @@ contract SetUp is Test {

exocoreLzEndpoint = new NonShortCircuitEndpointV2Mock(exocoreChainId, exocoreValidatorSet.addr);
clientLzEndpoint = new NonShortCircuitEndpointV2Mock(clientChainId, exocoreValidatorSet.addr);
solanaClientLzEndpoint = new NonShortCircuitEndpointV2Mock(solanaClientChainId, exocoreValidatorSet.addr);

ProxyAdmin proxyAdmin = new ProxyAdmin();
ExocoreGateway exocoreGatewayLogic = new ExocoreGateway(address(exocoreLzEndpoint));
Expand All @@ -108,6 +115,17 @@ contract SetUp is Test {
"EVM compatible client chain",
"secp256k1"
);

exocoreLzEndpoint.setDestLzEndpoint(address(solanaClientGateway), address(clientLzEndpoint));
exocoreGateway.registerOrUpdateClientChain(
solanaClientChainId,
address(solanaClientGateway).toBytes32(),
20,
"solanaClientChain",
"Non-EVM compatible client chain",
"ed25519"
);

vm.stopPrank();

// transfer some gas fee to exocore gateway as it has to pay for the relay fee to layerzero endpoint when
Expand All @@ -116,14 +134,24 @@ contract SetUp is Test {
}

function generateUID(uint64 nonce, bool fromClientChainToExocore) internal view returns (bytes32 uid) {
uid = generateUID(nonce, fromClientChainToExocore, false);
}

function generateUID(uint64 nonce, bool fromClientChainToExocore, bool isSolanaClient)
internal
view
returns (bytes32 uid)
{
if (fromClientChainToExocore) {
uid = GUID.generate(
nonce, clientChainId, address(clientGateway), exocoreChainId, address(exocoreGateway).toBytes32()
);
} else {
uid = GUID.generate(
nonce, exocoreChainId, address(exocoreGateway), clientChainId, address(clientGateway).toBytes32()
);
uint16 targetChainId = isSolanaClient ? solanaClientChainId : clientChainId;
bytes32 targetGateway =
isSolanaClient ? address(solanaClientGateway).toBytes32() : address(clientGateway).toBytes32();

return GUID.generate(nonce, exocoreChainId, address(exocoreGateway), targetChainId, targetGateway);
}
}

Expand Down Expand Up @@ -526,6 +554,7 @@ contract AddWhitelistTokens is SetUp {

uint256 MESSAGE_LENGTH = 1 + 32 + 16; // action + token address as bytes32 + uint128 tvl limit
uint256 nativeFee;
uint256 nativeFeeForSolana;

error IncorrectNativeFee(uint256 amount);

Expand All @@ -534,6 +563,9 @@ contract AddWhitelistTokens is SetUp {
function setUp() public virtual override {
super.setUp();
nativeFee = exocoreGateway.quote(clientChainId, new bytes(MESSAGE_LENGTH));
bytes memory message = new bytes(MESSAGE_LENGTH);
message[0] = bytes1(abi.encodePacked(Action.REQUEST_ADD_WHITELIST_TOKEN));
nativeFeeForSolana = exocoreGateway.quote(solanaClientChainId, message);
}

function test_RevertWhen_CallerNotOwner() public {
Expand Down Expand Up @@ -587,6 +619,24 @@ contract AddWhitelistTokens is SetUp {
vm.stopPrank();
}

function test_Success_AddWhiteListTokenOnSolana() public {
vm.startPrank(exocoreValidatorSet.addr);
vm.expectEmit(address(exocoreGateway));
emit WhitelistTokenAdded(solanaClientChainId, bytes32(bytes20(address(restakeToken))));
vm.expectEmit(address(exocoreGateway));
emit MessageSent(Action.REQUEST_ADD_WHITELIST_TOKEN, generateUID(1, false, true), 1, nativeFeeForSolana);
exocoreGateway.addWhitelistToken{value: nativeFeeForSolana}(
solanaClientChainId,
bytes32(bytes20(address(restakeToken))),
9,
"RestakeToken",
"Spl LST token",
"oracleInfo",
5000 * 1e9
);
vm.stopPrank();
}

}

contract UpdateWhitelistTokens is SetUp {
Expand Down
Loading