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: finalize nodev20 branch #388

Merged
merged 17 commits into from
Oct 22, 2024
7 changes: 6 additions & 1 deletion v2/contracts/Revert.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct RevertOptions {
/// @param revertMessage Arbitrary data sent back in onRevert.
struct RevertContext {
address asset;
uint256 amount;
uint64 amount;
bytes revertMessage;
}

Expand All @@ -32,3 +32,8 @@ interface Revertable {
/// @param revertContext Revert context to pass to onRevert.
function onRevert(RevertContext calldata revertContext) external;
}

interface INotSupportedMethods {
error CallOnRevertNotSupported();
error ZETANotSupported();
lumtis marked this conversation as resolved.
Show resolved Hide resolved
}
39 changes: 30 additions & 9 deletions v2/contracts/evm/ERC20Custody.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,30 @@ import { IGatewayEVM } from "./interfaces/IGatewayEVM.sol";

import { RevertContext } from "../../contracts/Revert.sol";

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/// @title ERC20Custody
/// @notice Holds the ERC20 tokens deposited on ZetaChain and includes functionality to call a contract.
/// @dev This contract does not call smart contracts directly, it passes through the Gateway contract.
contract ERC20Custody is IERC20Custody, ReentrancyGuard, AccessControl, Pausable {
contract ERC20Custody is
Initializable,
UUPSUpgradeable,
IERC20Custody,
ReentrancyGuardUpgradeable,
AccessControlUpgradeable,
PausableUpgradeable
{
using SafeERC20 for IERC20;

/// @notice Gateway contract.
IGatewayEVM public immutable gateway;
IGatewayEVM public gateway;
/// @notice Mapping of whitelisted tokens => true/false.
mapping(address => bool) public whitelisted;
/// @notice The address of the TSS (Threshold Signature Scheme) contract.
Expand All @@ -33,21 +43,32 @@ contract ERC20Custody is IERC20Custody, ReentrancyGuard, AccessControl, Pausable
/// @notice New role identifier for whitelister role.
bytes32 public constant WHITELISTER_ROLE = keccak256("WHITELISTER_ROLE");

/// @notice Constructor for ERC20Custody.
/// @notice Initializer for ERC20Custody.
/// @dev Set admin as default admin and pauser, and tssAddress as tss role.
constructor(address gateway_, address tssAddress_, address admin_) {
function initialize(address gateway_, address tssAddress_, address admin_) public initializer {
if (gateway_ == address(0) || tssAddress_ == address(0) || admin_ == address(0)) {
revert ZeroAddress();
}

__UUPSUpgradeable_init();
__ReentrancyGuard_init();
__AccessControl_init();
__Pausable_init();

gateway = IGatewayEVM(gateway_);
tssAddress = tssAddress_;
_grantRole(DEFAULT_ADMIN_ROLE, admin_);
_grantRole(PAUSER_ROLE, admin_);
_grantRole(PAUSER_ROLE, tssAddress_);
lumtis marked this conversation as resolved.
Show resolved Hide resolved
_grantRole(WITHDRAWER_ROLE, tssAddress_);
_grantRole(WHITELISTER_ROLE, admin_);
_grantRole(WHITELISTER_ROLE, tssAddress_);
}

/// @dev Authorizes the upgrade of the contract, sender must be owner.
/// @param newImplementation Address of the new implementation.
function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) { }

/// @notice Pause contract.
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
Expand All @@ -69,9 +90,9 @@ contract ERC20Custody is IERC20Custody, ReentrancyGuard, AccessControl, Pausable
_grantRole(WITHDRAWER_ROLE, newTSSAddress);
_grantRole(WHITELISTER_ROLE, newTSSAddress);

tssAddress = newTSSAddress;
emit UpdatedCustodyTSSAddress(tssAddress, newTSSAddress);

emit UpdatedCustodyTSSAddress(newTSSAddress);
tssAddress = newTSSAddress;
}

/// @notice Unpause contract.
Expand Down
45 changes: 28 additions & 17 deletions v2/contracts/evm/GatewayEVM.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 { RevertContext, RevertOptions, Revertable } from "../../contracts/Revert.sol";
import { INotSupportedMethods, RevertContext, RevertOptions, Revertable } from "../../contracts/Revert.sol";
import { ZetaConnectorBase } from "./ZetaConnectorBase.sol";
import { IERC20Custody } from "./interfaces/IERC20Custody.sol";
import { IGatewayEVM } from "./interfaces/IGatewayEVM.sol";
Expand All @@ -23,7 +23,8 @@ contract GatewayEVM is
UUPSUpgradeable,
IGatewayEVM,
ReentrancyGuardUpgradeable,
PausableUpgradeable
PausableUpgradeable,
INotSupportedMethods
{
using SafeERC20 for IERC20;

Expand Down Expand Up @@ -63,6 +64,7 @@ contract GatewayEVM is

_grantRole(DEFAULT_ADMIN_ROLE, admin_);
_grantRole(PAUSER_ROLE, admin_);
_grantRole(PAUSER_ROLE, tssAddress_);
tssAddress = tssAddress_;
_grantRole(TSS_ROLE, tssAddress_);

Expand All @@ -78,7 +80,7 @@ contract GatewayEVM is
/// @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) {
revertIfCallingOnRevert(data);
_revertIfCallingOnRevert(data);
(bool success, bytes memory result) = destination.call{ value: msg.value }(data);
if (!success) revert ExecutionFailed();

Expand All @@ -93,9 +95,9 @@ contract GatewayEVM is
_revokeRole(TSS_ROLE, tssAddress);
_grantRole(TSS_ROLE, newTSSAddress);

tssAddress = newTSSAddress;
emit UpdatedGatewayTSSAddress(tssAddress, newTSSAddress);

emit UpdatedGatewayTSSAddress(newTSSAddress);
tssAddress = newTSSAddress;
}

/// @notice Pause contract.
Expand Down Expand Up @@ -176,18 +178,18 @@ contract GatewayEVM is
if (amount == 0) revert InsufficientERC20Amount();
if (to == address(0)) revert ZeroAddress();
// Approve the target contract to spend the tokens
if (!resetApproval(token, to)) revert ApprovalFailed();
if (!_resetApproval(token, to)) revert ApprovalFailed();
if (!IERC20(token).approve(to, amount)) revert ApprovalFailed();
// Execute the call on the target contract
_execute(to, data);

// Reset approval
if (!resetApproval(token, to)) revert ApprovalFailed();
if (!_resetApproval(token, to)) revert ApprovalFailed();

// Transfer any remaining tokens back to the custody/connector contract
uint256 remainingBalance = IERC20(token).balanceOf(address(this));
if (remainingBalance > 0) {
transferToAssetHandler(token, remainingBalance);
_transferToAssetHandler(token, remainingBalance);
}

emit ExecutedWithERC20(token, to, amount, data);
Expand Down Expand Up @@ -233,8 +235,10 @@ contract GatewayEVM is
whenNotPaused
nonReentrant
{
if (revertOptions.callOnRevert) revert CallOnRevertNotSupported();
if (msg.value == 0) revert InsufficientETHAmount();
if (receiver == address(0)) revert ZeroAddress();
if (revertOptions.revertMessage.length > MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();

(bool deposited,) = tssAddress.call{ value: msg.value }("");

Expand All @@ -258,10 +262,12 @@ contract GatewayEVM is
whenNotPaused
nonReentrant
{
if (revertOptions.callOnRevert) revert CallOnRevertNotSupported();
if (amount == 0) revert InsufficientERC20Amount();
if (receiver == address(0)) revert ZeroAddress();
if (revertOptions.revertMessage.length > MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();

transferFromToAssetHandler(msg.sender, asset, amount);
_transferFromToAssetHandler(msg.sender, asset, amount);

emit Deposited(msg.sender, receiver, amount, asset, "", revertOptions);
}
Expand All @@ -280,9 +286,10 @@ contract GatewayEVM is
whenNotPaused
nonReentrant
{
if (revertOptions.callOnRevert) revert CallOnRevertNotSupported();
if (msg.value == 0) revert InsufficientETHAmount();
if (receiver == address(0)) revert ZeroAddress();
if (payload.length + revertOptions.revertMessage.length >= MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();
if (payload.length + revertOptions.revertMessage.length > MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();

(bool deposited,) = tssAddress.call{ value: msg.value }("");

Expand All @@ -308,11 +315,12 @@ contract GatewayEVM is
whenNotPaused
nonReentrant
{
if (revertOptions.callOnRevert) revert CallOnRevertNotSupported();
if (amount == 0) revert InsufficientERC20Amount();
if (receiver == address(0)) revert ZeroAddress();
if (payload.length + revertOptions.revertMessage.length >= MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();
if (payload.length + revertOptions.revertMessage.length > MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();

transferFromToAssetHandler(msg.sender, asset, amount);
_transferFromToAssetHandler(msg.sender, asset, amount);

emit Deposited(msg.sender, receiver, amount, asset, payload, revertOptions);
}
Expand All @@ -330,8 +338,9 @@ contract GatewayEVM is
whenNotPaused
nonReentrant
{
if (revertOptions.callOnRevert) revert CallOnRevertNotSupported();
if (receiver == address(0)) revert ZeroAddress();
if (payload.length + revertOptions.revertMessage.length >= MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();
if (payload.length + revertOptions.revertMessage.length > MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();

emit Called(msg.sender, receiver, payload, revertOptions);
}
Expand Down Expand Up @@ -361,7 +370,7 @@ contract GatewayEVM is
/// @param token Address of the ERC20 token.
/// @param to Address to reset the approval for.
/// @return True if the approval reset was successful, false otherwise.
function resetApproval(address token, address to) private returns (bool) {
function _resetApproval(address token, address to) private returns (bool) {
return IERC20(token).approve(to, 0);
}

Expand All @@ -371,8 +380,9 @@ contract GatewayEVM is
/// @param from Address of the sender.
/// @param token Address of the ERC20 token.
/// @param amount Amount of tokens to transfer.
function transferFromToAssetHandler(address from, address token, uint256 amount) private {
function _transferFromToAssetHandler(address from, address token, uint256 amount) private {
if (token == zetaToken) {
revert ZETANotSupported();
// transfer to connector
lumtis marked this conversation as resolved.
Show resolved Hide resolved
// transfer amount to gateway
IERC20(token).safeTransferFrom(from, address(this), amount);
Expand All @@ -392,8 +402,9 @@ contract GatewayEVM is
/// type.
/// @param token Address of the ERC20 token.
/// @param amount Amount of tokens to transfer.
function transferToAssetHandler(address token, uint256 amount) private {
function _transferToAssetHandler(address token, uint256 amount) private {
if (token == zetaToken) {
revert ZETANotSupported();
skosito marked this conversation as resolved.
Show resolved Hide resolved
// transfer to connector
// approve connector to handle tokens depending on connector version (eg. lock or burn)
if (!IERC20(token).approve(zetaConnector, amount)) revert ApprovalFailed();
Expand All @@ -407,7 +418,7 @@ contract GatewayEVM is
}

// @dev prevent spoofing onRevert functions
function revertIfCallingOnRevert(bytes calldata data) private pure {
function _revertIfCallingOnRevert(bytes calldata data) private pure {
if (data.length >= 4) {
bytes4 functionSelector;
assembly {
Expand Down
49 changes: 39 additions & 10 deletions v2/contracts/evm/ZetaConnectorBase.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

import { RevertContext } from "../../contracts/Revert.sol";
import { IGatewayEVM, IGatewayEVMErrors, IGatewayEVMEvents } from "../../contracts/evm/interfaces/IGatewayEVM.sol";
Expand All @@ -14,16 +16,23 @@ import "../../contracts/evm/interfaces/IZetaConnector.sol";
/// @title ZetaConnectorBase
/// @notice Abstract base contract for ZetaConnector.
/// @dev This contract implements basic functionality for handling tokens and interacting with the Gateway contract.
abstract contract ZetaConnectorBase is IZetaConnectorEvents, ReentrancyGuard, Pausable, AccessControl {
abstract contract ZetaConnectorBase is
Initializable,
UUPSUpgradeable,
IZetaConnectorEvents,
ReentrancyGuardUpgradeable,
PausableUpgradeable,
AccessControlUpgradeable
{
using SafeERC20 for IERC20;

/// @notice Error indicating that a zero address was provided.
error ZeroAddress();

/// @notice The Gateway contract used for executing cross-chain calls.
IGatewayEVM public immutable gateway;
IGatewayEVM public gateway;
/// @notice The address of the Zeta token.
address public immutable zetaToken;
address public zetaToken;
/// @notice The address of the TSS (Threshold Signature Scheme) contract.
address public tssAddress;

Expand All @@ -34,12 +43,27 @@ abstract contract ZetaConnectorBase is IZetaConnectorEvents, ReentrancyGuard, Pa
/// @notice New role identifier for tss role.
bytes32 public constant TSS_ROLE = keccak256("TSS_ROLE");

/// @notice Constructor for ZetaConnectors.
/// @notice Initializer for ZetaConnectors.
/// @dev Set admin as default admin and pauser, and tssAddress as tss role.
constructor(address gateway_, address zetaToken_, address tssAddress_, address admin_) {
function initialize(
address gateway_,
address zetaToken_,
address tssAddress_,
address admin_
)
public
virtual
initializer
{
if (gateway_ == address(0) || zetaToken_ == address(0) || tssAddress_ == address(0) || admin_ == address(0)) {
revert ZeroAddress();
}

__UUPSUpgradeable_init();
__ReentrancyGuard_init();
__AccessControl_init();
__Pausable_init();

gateway = IGatewayEVM(gateway_);
zetaToken = zetaToken_;
tssAddress = tssAddress_;
Expand All @@ -48,8 +72,13 @@ abstract contract ZetaConnectorBase is IZetaConnectorEvents, ReentrancyGuard, Pa
_grantRole(WITHDRAWER_ROLE, tssAddress_);
_grantRole(TSS_ROLE, tssAddress_);
_grantRole(PAUSER_ROLE, admin_);
_grantRole(PAUSER_ROLE, tssAddress_);
}

/// @dev Authorizes the upgrade of the contract, sender must be owner.
/// @param newImplementation Address of the new implementation.
function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) { }

/// @notice Update tss address
/// @param newTSSAddress new tss address
function updateTSSAddress(address newTSSAddress) external onlyRole(DEFAULT_ADMIN_ROLE) {
Expand All @@ -61,9 +90,9 @@ abstract contract ZetaConnectorBase is IZetaConnectorEvents, ReentrancyGuard, Pa
_grantRole(WITHDRAWER_ROLE, newTSSAddress);
_grantRole(TSS_ROLE, newTSSAddress);

tssAddress = newTSSAddress;
emit UpdatedZetaConnectorTSSAddress(tssAddress, newTSSAddress);

emit UpdatedZetaConnectorTSSAddress(newTSSAddress);
tssAddress = newTSSAddress;
}

/// @notice Pause contract.
Expand Down
10 changes: 7 additions & 3 deletions v2/contracts/evm/ZetaConnectorNative.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract ZetaConnectorNative is ZetaConnectorBase {
using SafeERC20 for IERC20;

constructor(
function initialize(
address gateway_,
address zetaToken_,
address tssAddress_,
address admin_
)
ZetaConnectorBase(gateway_, zetaToken_, tssAddress_, admin_)
{ }
public
override
initializer
{
super.initialize(gateway_, zetaToken_, tssAddress_, admin_);
}

/// @notice Withdraw tokens to a specified address.
/// @param to The address to withdraw tokens to.
Expand Down
Loading
Loading