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: add sender when calling connected chain contracts from zetachain #350

Closed
wants to merge 12 commits into from
87 changes: 72 additions & 15 deletions v2/contracts/evm/GatewayEVM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.26;
import { RevertContext, RevertOptions, Revertable } from "../../contracts/Revert.sol";
import { ZetaConnectorBase } from "./ZetaConnectorBase.sol";
import { IERC20Custody } from "./interfaces/IERC20Custody.sol";
import { IGatewayEVM } from "./interfaces/IGatewayEVM.sol";
import { Callable, IGatewayEVM, MessageContext } from "./interfaces/IGatewayEVM.sol";

import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
Expand Down Expand Up @@ -71,17 +71,6 @@ contract GatewayEVM is
/// @param newImplementation Address of the new implementation.
function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) { }

/// @dev Internal function to execute a call to a destination address.
/// @param destination Address to call.
/// @param data Calldata to pass to the call.
/// @return The result of the call.
function _execute(address destination, bytes calldata data) internal returns (bytes memory) {
(bool success, bytes memory result) = destination.call{ value: msg.value }(data);
if (!success) revert ExecutionFailed();

return result;
}

/// @notice Pause contract.
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
Expand Down Expand Up @@ -115,12 +104,14 @@ contract GatewayEVM is
emit Reverted(destination, address(0), msg.value, data, revertContext);
}

/// @notice Executes a call to a destination address without ERC20 tokens.
/// @notice Executes an authenticated call to a destination address without ERC20 tokens.
/// @dev This function can only be called by the TSS address and it is payable.
/// @param messageContext Message context containing sender.
/// @param destination Address to call.
/// @param data Calldata to pass to the call.
/// @return The result of the call.
function execute(
MessageContext calldata messageContext,
address destination,
bytes calldata data
)
Expand All @@ -132,7 +123,31 @@ contract GatewayEVM is
returns (bytes memory)
{
if (destination == address(0)) revert ZeroAddress();
bytes memory result = _execute(destination, data);
bytes memory result;
result = _executeAuthenticatedCall(messageContext, destination, data);

emit Executed(destination, msg.value, data);

return result;
}

/// @notice Executes an arbitrary call to a destination address without ERC20 tokens.
/// @dev This function can only be called by the TSS address and it is payable.
/// @param destination Address to call.
/// @param data Calldata to pass to the call.
/// @return The result of the call.
function execute(
address destination,
bytes calldata data
)
external
payable
onlyRole(TSS_ROLE)
whenNotPaused
returns (bytes memory)
{
if (destination == address(0)) revert ZeroAddress();
bytes memory result = _executeArbitraryCall(destination, data);

emit Executed(destination, msg.value, data);

Expand Down Expand Up @@ -163,7 +178,7 @@ contract GatewayEVM is
if (!resetApproval(token, to)) revert ApprovalFailed();
if (!IERC20(token).approve(to, amount)) revert ApprovalFailed();
// Execute the call on the target contract
_execute(to, data);
_executeArbitraryCall(to, data);

// Reset approval
if (!resetApproval(token, to)) revert ApprovalFailed();
Expand Down Expand Up @@ -385,4 +400,46 @@ contract GatewayEVM is
IERC20(token).safeTransfer(custody, amount);
}
}

/// @dev Private function to execute an arbitrary call to a destination address.
/// @param destination Address to call.
/// @param data Calldata to pass to the call.
/// @return The result of the call.
function _executeArbitraryCall(address destination, bytes calldata data) private returns (bytes memory) {
revertIfAuthenticatedCall(data);
(bool success, bytes memory result) = destination.call{ value: msg.value }(data);
if (!success) revert ExecutionFailed();

return result;
}

/// @dev Private function to execute an authenticated call to a destination address.
/// @param messageContext Message context containing sender and arbitrary call flag.
/// @param destination Address to call.
/// @param data Calldata to pass to the call.
/// @return The result of the call.
function _executeAuthenticatedCall(
MessageContext calldata messageContext,
address destination,
bytes calldata data
)
private
returns (bytes memory)
{
return Callable(destination).onCall(messageContext, data);
}

// @dev prevent calling onCall function reserved for authenticated calls
function revertIfAuthenticatedCall(bytes calldata data) private pure {
if (data.length >= 4) {
bytes4 functionSelector;
assembly {
functionSelector := calldataload(data.offset)
}

if (functionSelector == Callable.onCall.selector) {
revert NotAllowedToCallOnCall();
}
}
}
}
29 changes: 29 additions & 0 deletions v2/contracts/evm/interfaces/IGatewayEVM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ interface IGatewayEVMErrors {

/// @notice Error when trying to transfer not whitelisted token to custody.
error NotWhitelistedInCustody();

/// @notice Error when trying to call onCall method using arbitrary call.
error NotAllowedToCallOnCall();
}

/// @title IGatewayEVM
Expand Down Expand Up @@ -111,6 +114,21 @@ interface IGatewayEVM is IGatewayEVMErrors, IGatewayEVMEvents {
/// @return The result of the contract call.
function execute(address destination, bytes calldata data) external payable returns (bytes memory);

/// @notice Executes a call to a destination address without ERC20 tokens.
/// @dev This function can only be called by the TSS address and it is payable.
/// @param messageContext Message context containing sender and arbitrary call flag.
/// @param destination Address to call.
/// @param data Calldata to pass to the call.
/// @return The result of the call.
function execute(
MessageContext calldata messageContext,
address destination,
bytes calldata data
)
external
payable
returns (bytes memory);

/// @notice Executes a revertable call to a contract using ERC20 tokens.
/// @param token The address of the ERC20 token.
/// @param to The address of the contract to call.
Expand Down Expand Up @@ -171,3 +189,14 @@ interface IGatewayEVM is IGatewayEVMErrors, IGatewayEVMEvents {
/// @param revertOptions Revert options.
function call(address receiver, bytes calldata payload, RevertOptions calldata revertOptions) external;
}

/// @notice Message context passed to execute function.
/// @param sender Sender from omnichain contract.
struct MessageContext {
address sender;
}

/// @notice Interface implemented by contracts receiving authenticated calls.
interface Callable {
function onCall(MessageContext calldata context, bytes calldata message) external returns (bytes memory);
}
133 changes: 126 additions & 7 deletions v2/contracts/zevm/GatewayZEVM.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import { IGatewayZEVM } from "./interfaces/IGatewayZEVM.sol";
import { CallOptions, IGatewayZEVM } from "./interfaces/IGatewayZEVM.sol";

import { RevertContext, RevertOptions } from "../../contracts/Revert.sol";
import "./interfaces/IWZETA.sol";
Expand Down Expand Up @@ -151,7 +151,7 @@ contract GatewayZEVM is
gasFee,
IZRC20(zrc20).PROTOCOL_FLAT_FEE(),
"",
IZRC20(zrc20).GAS_LIMIT(),
CallOptions({ gasLimit: IZRC20(zrc20).GAS_LIMIT(), isArbitraryCall: true }),
revertOptions
);
}
Expand Down Expand Up @@ -188,7 +188,44 @@ contract GatewayZEVM is
gasFee,
IZRC20(zrc20).PROTOCOL_FLAT_FEE(),
message,
gasLimit,
CallOptions({ gasLimit: gasLimit, isArbitraryCall: true }),
revertOptions
);
}

/// @notice Withdraw ZRC20 tokens and call a smart contract on an external chain.
/// @param receiver The receiver address on the external chain.
/// @param amount The amount of tokens to withdraw.
/// @param zrc20 The address of the ZRC20 token.
/// @param message The calldata to pass to the contract call.
/// @param callOptions Call options including gas limit and arbirtrary call flag.
/// @param revertOptions Revert options.
function withdrawAndCall(
bytes memory receiver,
uint256 amount,
address zrc20,
bytes calldata message,
CallOptions calldata callOptions,
RevertOptions calldata revertOptions
)
external
nonReentrant
whenNotPaused
{
if (receiver.length == 0) revert ZeroAddress();
if (amount == 0) revert InsufficientZRC20Amount();

uint256 gasFee = _withdrawZRC20WithGasLimit(amount, zrc20, callOptions.gasLimit);
emit Withdrawn(
msg.sender,
0,
receiver,
zrc20,
amount,
gasFee,
IZRC20(zrc20).PROTOCOL_FLAT_FEE(),
message,
callOptions,
revertOptions
);
}
Expand All @@ -211,7 +248,18 @@ contract GatewayZEVM is
if (amount == 0) revert InsufficientZetaAmount();

_transferZETA(amount, FUNGIBLE_MODULE_ADDRESS);
emit Withdrawn(msg.sender, chainId, receiver, address(zetaToken), amount, 0, 0, "", 0, revertOptions);
emit Withdrawn(
msg.sender,
chainId,
receiver,
address(zetaToken),
amount,
0,
0,
"",
CallOptions({ gasLimit: 0, isArbitraryCall: true }),
revertOptions
);
}

/// @notice Withdraw ZETA tokens and call a smart contract on an external chain.
Expand All @@ -235,7 +283,66 @@ contract GatewayZEVM is
if (amount == 0) revert InsufficientZetaAmount();

_transferZETA(amount, FUNGIBLE_MODULE_ADDRESS);
emit Withdrawn(msg.sender, chainId, receiver, address(zetaToken), amount, 0, 0, message, 0, revertOptions);
emit Withdrawn(
msg.sender,
chainId,
receiver,
address(zetaToken),
amount,
0,
0,
message,
CallOptions({ gasLimit: 0, isArbitraryCall: true }),
revertOptions
);
}

/// @notice Withdraw ZETA tokens and call a smart contract on an external chain.
/// @param receiver The receiver address on the external chain.
/// @param amount The amount of tokens to withdraw.
/// @param chainId Chain id of the external chain.
/// @param message The calldata to pass to the contract call.
/// @param callOptions Call options including gas limit and arbirtrary call flag.
/// @param revertOptions Revert options.
function withdrawAndCall(
bytes memory receiver,
uint256 amount,
uint256 chainId,
bytes calldata message,
CallOptions calldata callOptions,
RevertOptions calldata revertOptions
)
external
nonReentrant
whenNotPaused
{
if (receiver.length == 0) revert ZeroAddress();
if (amount == 0) revert InsufficientZetaAmount();

_transferZETA(amount, FUNGIBLE_MODULE_ADDRESS);
emit Withdrawn(
msg.sender, chainId, receiver, address(zetaToken), amount, 0, 0, message, callOptions, revertOptions
);
}

/// @notice Call a smart contract on an external chain without asset transfer.
/// @param receiver The receiver address on the external chain.
/// @param zrc20 Address of zrc20 to pay fees.
/// @param message The calldata to pass to the contract call.
/// @param callOptions Call options including gas limit and arbirtrary call flag.
/// @param revertOptions Revert options.
function call(
bytes memory receiver,
address zrc20,
bytes calldata message,
CallOptions calldata callOptions,
RevertOptions calldata revertOptions
)
external
nonReentrant
whenNotPaused
{
_call(receiver, zrc20, message, callOptions, revertOptions);
}

/// @notice Call a smart contract on an external chain without asset transfer.
Expand All @@ -254,16 +361,28 @@ contract GatewayZEVM is
external
nonReentrant
whenNotPaused
{
_call(receiver, zrc20, message, CallOptions({ gasLimit: gasLimit, isArbitraryCall: true }), revertOptions);
}

function _call(
bytes memory receiver,
address zrc20,
bytes calldata message,
CallOptions memory callOptions,
RevertOptions memory revertOptions
)
internal
{
if (receiver.length == 0) revert ZeroAddress();
if (message.length == 0) revert EmptyMessage();

(address gasZRC20, uint256 gasFee) = IZRC20(zrc20).withdrawGasFeeWithGasLimit(gasLimit);
(address gasZRC20, uint256 gasFee) = IZRC20(zrc20).withdrawGasFeeWithGasLimit(callOptions.gasLimit);
if (!IZRC20(gasZRC20).transferFrom(msg.sender, FUNGIBLE_MODULE_ADDRESS, gasFee)) {
revert GasFeeTransferFailed();
}

emit Called(msg.sender, zrc20, receiver, message, gasLimit, revertOptions);
emit Called(msg.sender, zrc20, receiver, message, callOptions, revertOptions);
}

/// @notice Deposit foreign coins into ZRC20.
Expand Down
Loading
Loading