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: v2 contracts access control #247

Merged
merged 13 commits into from
Jul 25, 2024
37 changes: 19 additions & 18 deletions contracts/prototypes/evm/ERC20CustodyNew.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,47 @@
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./IGatewayEVM.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

import "./IGatewayEVM.sol";
import "./IERC20CustodyNew.sol";

// As the current version, ERC20CustodyNew hold the ERC20s deposited on ZetaChain
// This version include a functionality allowing to call a contract
// ERC20Custody doesn't call smart contract directly, it passes through the Gateway contract
contract ERC20CustodyNew is ReentrancyGuard{
contract ERC20CustodyNew is IERC20CustodyNewEvents, IERC20CustodyNewErrors, ReentrancyGuard {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in other comment, the ideal imho would be:
contract ERC20CustodyNew is IERC20Custody, ReentrancyGuard

using SafeERC20 for IERC20;
error ZeroAddress();

IGatewayEVM public gateway;
address public tssAddress;

Check warning

Code scanning / Slither

State variables that could be declared immutable Warning

ERC20CustodyNew.tssAddress should be immutable
fbac marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
address public tssAddress;
address public immutable tssAddress;


event Withdraw(address indexed token, address indexed to, uint256 amount);
event WithdrawAndCall(address indexed token, address indexed to, uint256 amount, bytes data);
event WithdrawAndRevert(address indexed token, address indexed to, uint256 amount, bytes data);
// @dev Only TSS address allowed modifier.
modifier onlyTSS() {
if (msg.sender != tssAddress) {
revert InvalidSender();
}
_;
}

constructor(address _gateway) {
if (_gateway == address(0)) {
constructor(address _gateway, address _tssAddress) {
if (_gateway == address(0) || _tssAddress == address(0)) {
revert ZeroAddress();
}
gateway = IGatewayEVM(_gateway);
tssAddress = _tssAddress;
}

// Withdraw is called by TSS address, it directly transfers the tokens to the destination address without contract call
// TODO: Finalize access control
// https://github.com/zeta-chain/protocol-contracts/issues/204
function withdraw(address token, address to, uint256 amount) external nonReentrant {
function withdraw(address token, address to, uint256 amount) external nonReentrant onlyTSS {
IERC20(token).safeTransfer(to, amount);

emit Withdraw(token, to, amount);
}

// WithdrawAndCall is called by TSS address, it transfers the tokens and call a contract
// For this, it passes through the Gateway contract, it transfers the tokens to the Gateway contract and then calls the contract
// TODO: Finalize access control
// https://github.com/zeta-chain/protocol-contracts/issues/204
function withdrawAndCall(address token, address to, uint256 amount, bytes calldata data) public nonReentrant {
function withdrawAndCall(address token, address to, uint256 amount, bytes calldata data) public nonReentrant onlyTSS {
// Transfer the tokens to the Gateway contract
IERC20(token).safeTransfer(address(gateway), amount);

Expand All @@ -51,9 +54,7 @@

// WithdrawAndRevert is called by TSS address, it transfers the tokens and call a contract
// For this, it passes through the Gateway contract, it transfers the tokens to the Gateway contract and then calls the contract
// TODO: Finalize access control
// https://github.com/zeta-chain/protocol-contracts/issues/204
function withdrawAndRevert(address token, address to, uint256 amount, bytes calldata data) public nonReentrant {
function withdrawAndRevert(address token, address to, uint256 amount, bytes calldata data) public nonReentrant onlyTSS {
// Transfer the tokens to the Gateway contract
IERC20(token).safeTransfer(address(gateway), amount);

Expand Down
24 changes: 20 additions & 4 deletions contracts/prototypes/evm/GatewayEVM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@
/// @notice The address of the Zeta token contract.
address public zetaToken;

// @dev Only TSS address allowed modifier.
modifier onlyTSS() {
if (msg.sender != tssAddress) {
revert InvalidSender();
}
_;
}

// @dev Only custody address allowed modifier.
skosito marked this conversation as resolved.
Show resolved Hide resolved
modifier onlyCustodyOrConnector() {
skosito marked this conversation as resolved.
Show resolved Hide resolved
if (msg.sender != custody && msg.sender != zetaConnector) {
revert InvalidSender();
}
_;
}

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
Expand Down Expand Up @@ -56,24 +72,24 @@

// Called by the TSS
// Calling onRevert directly
function executeRevert(address destination, bytes calldata data) public payable {
function executeRevert(address destination, bytes calldata data) public payable onlyTSS {

Check notice

Code scanning / Slither

Missing zero address validation Low

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add zero address validation for destination.

The destination parameter should be validated to ensure it is not a zero address.

+  if (destination == address(0)) revert ZeroAddress();
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function executeRevert(address destination, bytes calldata data) public payable onlyTSS {
function executeRevert(address destination, bytes calldata data) public payable onlyTSS {
+ if (destination == address(0)) revert ZeroAddress();
Tools
GitHub Check: Slither

[notice] 75-75: Missing zero address validation
GatewayEVM.executeRevert(address,bytes).destination (contracts/prototypes/evm/GatewayEVM.sol#75) lacks a zero-check on :
- (success,result) = destination.call{value: msg.value}() (contracts/prototypes/evm/GatewayEVM.sol#76)


[notice] 75-81: Reentrancy vulnerabilities
Reentrancy in GatewayEVM.executeRevert(address,bytes) (contracts/prototypes/evm/GatewayEVM.sol#75-81):
External calls:
- (success,result) = destination.call{value: msg.value}() (contracts/prototypes/evm/GatewayEVM.sol#76)
- Revertable(destination).onRevert(data) (contracts/prototypes/evm/GatewayEVM.sol#78)
External calls sending eth:
- (success,result) = destination.call{value: msg.value}() (contracts/prototypes/evm/GatewayEVM.sol#76)
Event emitted after the call(s):
- Reverted(destination,msg.value,data) (contracts/prototypes/evm/GatewayEVM.sol#80)


[warning] 75-81: Low-level calls
Low level call in GatewayEVM.executeRevert(address,bytes) (contracts/prototypes/evm/GatewayEVM.sol#75-81):
- (success,result) = destination.call{value: msg.value}() (contracts/prototypes/evm/GatewayEVM.sol#76)

(bool success, bytes memory result) = destination.call{value: msg.value}("");
if (!success) revert ExecutionFailed();
Revertable(destination).onRevert(data);

emit Reverted(destination, msg.value, data);
}

Check notice

Code scanning / Slither

Reentrancy vulnerabilities Low

Check warning

Code scanning / Slither

Low-level calls Warning


// Called by the TSS
// Execution without ERC20 tokens, it is payable and can be used in the case of WithdrawAndCall for Gas ZRC20
// It can be also used for contract call without asset movement
function execute(address destination, bytes calldata data) external payable returns (bytes memory) {
function execute(address destination, bytes calldata data) external payable onlyTSS returns (bytes memory) {
bytes memory result = _execute(destination, data);

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

return result;
}

Check notice

Code scanning / Slither

Reentrancy vulnerabilities Low


// Called by the ERC20Custody contract
// It call a function using ERC20 transfer
Expand All @@ -84,7 +100,7 @@
address to,
uint256 amount,
bytes calldata data
) public nonReentrant {
) public nonReentrant onlyCustodyOrConnector {
if (amount == 0) revert InsufficientERC20Amount();
// Approve the target contract to spend the tokens
if(!resetApproval(token, to)) revert ApprovalFailed();
Expand All @@ -111,7 +127,7 @@
address to,
uint256 amount,
bytes calldata data
) external nonReentrant {
) external nonReentrant onlyCustodyOrConnector {
if (amount == 0) revert InsufficientERC20Amount();

IERC20(token).safeTransfer(address(to), amount);
Expand Down
13 changes: 13 additions & 0 deletions contracts/prototypes/evm/IERC20CustodyNew.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC20CustodyNewEvents {
event Withdraw(address indexed token, address indexed to, uint256 amount);
event WithdrawAndCall(address indexed token, address indexed to, uint256 amount, bytes data);
event WithdrawAndRevert(address indexed token, address indexed to, uint256 amount, bytes data);
}

interface IERC20CustodyNewErrors {
error ZeroAddress();
error InvalidSender();
}
1 change: 1 addition & 0 deletions contracts/prototypes/evm/IGatewayEVM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface IGatewayEVMErrors {
error ZeroAddress();
error ApprovalFailed();
error CustodyInitialized();
error InvalidSender();
}

interface IGatewayEVM {
Expand Down
8 changes: 8 additions & 0 deletions contracts/prototypes/evm/IZetaConnector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IZetaConnectorEvents {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion the interface shouldn't be specific for events or errors.
When importing an interface, as a developer I'd expect to have full access to the interface: functions, errors and events since the logic is coupled, meaning that the errors and events are related to the functions, etc

My suggestion here and in all the interface files would be grouping events, errors and functions under interface Foo { ... }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, one use case for having separate is if we want to import only events and errors in situations like tests, it is nice to be more granular if we are using interfaces

maybe dont have errors and events in interface then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will follow up on this one, good point

event Withdraw(address indexed to, uint256 amount);
event WithdrawAndCall(address indexed to, uint256 amount, bytes data);
event WithdrawAndRevert(address indexed to, uint256 amount, bytes data);
}
12 changes: 6 additions & 6 deletions contracts/prototypes/evm/ZetaConnectorNative.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract ZetaConnectorNative is ZetaConnectorNewBase {
using SafeERC20 for IERC20;

constructor(address _gateway, address _zetaToken)
ZetaConnectorNewBase(_gateway, _zetaToken)
constructor(address _gateway, address _zetaToken, address _tssAddress)
ZetaConnectorNewBase(_gateway, _zetaToken, _tssAddress)
{}

// @dev withdraw is called by TSS address, it directly transfers zetaToken to the destination address without contract call
function withdraw(address to, uint256 amount, bytes32 internalSendHash) external override nonReentrant {
function withdraw(address to, uint256 amount, bytes32 internalSendHash) external override nonReentrant onlyTSS {
IERC20(zetaToken).safeTransfer(to, amount);
emit Withdraw(to, amount);
}

// @dev withdrawAndCall is called by TSS address, it transfers zetaToken to the gateway and calls a contract
function withdrawAndCall(address to, uint256 amount, bytes calldata data, bytes32 internalSendHash) external override nonReentrant {
function withdrawAndCall(address to, uint256 amount, bytes calldata data, bytes32 internalSendHash) external override nonReentrant onlyTSS {
// Transfer zetaToken to the Gateway contract
IERC20(zetaToken).safeTransfer(address(gateway), amount);

Expand All @@ -30,7 +30,7 @@ contract ZetaConnectorNative is ZetaConnectorNewBase {
}

// @dev withdrawAndRevert is called by TSS address, it transfers zetaToken to the gateway and calls onRevert on a contract
function withdrawAndRevert(address to, uint256 amount, bytes calldata data, bytes32 internalSendHash) external override nonReentrant {
function withdrawAndRevert(address to, uint256 amount, bytes calldata data, bytes32 internalSendHash) external override nonReentrant onlyTSS {
// Transfer zetaToken to the Gateway contract
IERC20(zetaToken).safeTransfer(address(gateway), amount);

Expand All @@ -40,7 +40,7 @@ contract ZetaConnectorNative is ZetaConnectorNewBase {
emit WithdrawAndRevert(to, amount, data);
}

// @dev receiveTokens handles token transfer and burn them
// @dev receiveTokens handles token transfer back to connector
function receiveTokens(uint256 amount) external override {
IERC20(zetaToken).safeTransferFrom(msg.sender, address(this), amount);
}
Expand Down
23 changes: 16 additions & 7 deletions contracts/prototypes/evm/ZetaConnectorNewBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,37 @@
pragma solidity 0.8.7;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./IGatewayEVM.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

abstract contract ZetaConnectorNewBase is ReentrancyGuard {
import "./IGatewayEVM.sol";
import "./IZetaConnector.sol";

abstract contract ZetaConnectorNewBase is IZetaConnectorEvents, ReentrancyGuard {
using SafeERC20 for IERC20;

error ZeroAddress();
error InvalidSender();

IGatewayEVM public immutable gateway;
address public immutable zetaToken;
address public tssAddress;

Check warning

Code scanning / Slither

State variables that could be declared immutable Warning

ZetaConnectorNewBase.tssAddress should be immutable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider marking tssAddress as immutable.

The state variable tssAddress can be marked as immutable since it is only set in the constructor and never changed afterward.

-  address public tssAddress;
+  address public immutable tssAddress;
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
address public tssAddress;
address public immutable tssAddress;
Tools
GitHub Check: Slither

[warning] 19-19: State variables that could be declared immutable
ZetaConnectorNewBase.tssAddress (contracts/prototypes/evm/ZetaConnectorNewBase.sol#19) should be immutable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As analyzed by slither:

Suggested change
address public tssAddress;
address public immutable tssAddress;

tssAddress shouldn't change after setting it at deployment time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets see if we will need a way to update it with tssUpdater

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will follow up here: #255

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
address public tssAddress;
address public immutable tssAddress;


event Withdraw(address indexed to, uint256 amount);
event WithdrawAndCall(address indexed to, uint256 amount, bytes data);
event WithdrawAndRevert(address indexed to, uint256 amount, bytes data);
// @dev Only TSS address allowed modifier.
modifier onlyTSS() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: onlyTSS() is defined two times, we could consider moving it to an external file which defines modifier onlyAddress(address)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what would be a good solution here, would we need separate contract which defines this modifier and inherit from it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can aim for something in the direction of open-zeppelin Ownable (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol)

This way we can abstract the access control logic into a single contract, something like TSSAccessControl.sol that exposes:

  • defines a tssAddress.
  • defines the logic to update the tssAddress (more on this later).
  • exposes an onlyTSS modifier which can be used from different contracts, and those don't have to even know where the TSS address is pointing to.

Regarding the upgrading logic, bear with me as I still lack some context on when and how the TSS address is updated I see a couple options:

  • how is it performed currently?
  • we could explore current developed proxy patterns, such as open zeppelin UpgradeableBeacon.
  • we could have some proxy custom developed pattern implementation, like:
    TSSAddress.sol <- TSSAddressAccessControl.sol <- [contracts implementing RBAC]
    • TSSAddress.sol has a const tssAddress.
    • TSSAddressAccessControl would have a tssAddress variable which is proxied to TSSAddress.sol
    • Rest of the contracts would use TSSAddressAccessControl functions and modifiers.
    • Upgrade procedure: deploy a new TSSAddress.sol, switch the proxy implementation in TSSAddressAccessControl.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like having TssAccessControl.sol, thanks for suggestion , let's tackle that and remaining questions in #255

if (msg.sender != tssAddress) {
revert InvalidSender();
}
_;
}

constructor(address _gateway, address _zetaToken) {
if (_gateway == address(0) || _zetaToken == address(0)) {
constructor(address _gateway, address _zetaToken, address _tssAddress) {
if (_gateway == address(0) || _zetaToken == address(0) || _tssAddress == address(0)) {
revert ZeroAddress();
}
gateway = IGatewayEVM(_gateway);
zetaToken = _zetaToken;
tssAddress = _tssAddress;
}

function withdraw(address to, uint256 amount, bytes32 internalSendHash) external virtual;
Expand Down
10 changes: 5 additions & 5 deletions contracts/prototypes/evm/ZetaConnectorNonNative.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import "./IZetaNonEthNew.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";

contract ZetaConnectorNonNative is ZetaConnectorNewBase {
constructor(address _gateway, address _zetaToken)
ZetaConnectorNewBase(_gateway, _zetaToken)
constructor(address _gateway, address _zetaToken, address _tssAddress)
ZetaConnectorNewBase(_gateway, _zetaToken, _tssAddress)
{}

// @dev withdraw is called by TSS address, it mints zetaToken to the destination address
function withdraw(address to, uint256 amount, bytes32 internalSendHash) external override nonReentrant {
function withdraw(address to, uint256 amount, bytes32 internalSendHash) external override nonReentrant onlyTSS {
IZetaNonEthNew(zetaToken).mint(to, amount, internalSendHash);
emit Withdraw(to, amount);
}

// @dev withdrawAndCall is called by TSS address, it mints zetaToken and calls a contract
function withdrawAndCall(address to, uint256 amount, bytes calldata data, bytes32 internalSendHash) external override nonReentrant {
function withdrawAndCall(address to, uint256 amount, bytes calldata data, bytes32 internalSendHash) external override nonReentrant onlyTSS {
// Mint zetaToken to the Gateway contract
IZetaNonEthNew(zetaToken).mint(address(gateway), amount, internalSendHash);

Expand All @@ -28,7 +28,7 @@ contract ZetaConnectorNonNative is ZetaConnectorNewBase {
}

// @dev withdrawAndRevert is called by TSS address, it mints zetaToken to the gateway and calls onRevert on a contract
function withdrawAndRevert(address to, uint256 amount, bytes calldata data, bytes32 internalSendHash) external override nonReentrant {
function withdrawAndRevert(address to, uint256 amount, bytes calldata data, bytes32 internalSendHash) external override nonReentrant onlyTSS {
// Mint zetaToken to the Gateway contract
IZetaNonEthNew(zetaToken).mint(address(gateway), amount, internalSendHash);

Expand Down
40 changes: 14 additions & 26 deletions contracts/prototypes/zevm/GatewayZEVM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ contract GatewayZEVM is IGatewayZEVMEvents, IGatewayZEVMErrors, Initializable, O
address public constant FUNGIBLE_MODULE_ADDRESS = 0x735b14BB79463307AAcBED86DAf3322B1e6226aB;
address public zetaToken;

// @dev Only Fungible module address allowed modifier.
modifier onlyFungible() {
if (msg.sender != FUNGIBLE_MODULE_ADDRESS) {
revert CallerIsNotFungibleModule();
}
_;
}

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
Expand Down Expand Up @@ -94,93 +102,73 @@ contract GatewayZEVM is IGatewayZEVMEvents, IGatewayZEVMErrors, Initializable, O
}

// Deposit foreign coins into ZRC20
// TODO: Finalize access control
// https://github.com/zeta-chain/protocol-contracts/issues/204
function deposit(
address zrc20,
uint256 amount,
address target
) external {
if (msg.sender != FUNGIBLE_MODULE_ADDRESS) revert CallerIsNotFungibleModule();
) external onlyFungible {
if (target == FUNGIBLE_MODULE_ADDRESS || target == address(this)) revert InvalidTarget();

IZRC20(zrc20).deposit(target, amount);
}

// Execute user specified contract on ZEVM
// TODO: Finalize access control
// https://github.com/zeta-chain/protocol-contracts/issues/204
function execute(
zContext calldata context,
address zrc20,
uint256 amount,
address target,
bytes calldata message
) external {
if (msg.sender != FUNGIBLE_MODULE_ADDRESS) revert CallerIsNotFungibleModule();

) external onlyFungible {
UniversalContract(target).onCrossChainCall(context, zrc20, amount, message);
}

// Deposit foreign coins into ZRC20 and call user specified contract on ZEVM
// TODO: Finalize access control
// https://github.com/zeta-chain/protocol-contracts/issues/204
function depositAndCall(
zContext calldata context,
address zrc20,
uint256 amount,
address target,
bytes calldata message
) external {
if (msg.sender != FUNGIBLE_MODULE_ADDRESS) revert CallerIsNotFungibleModule();
) external onlyFungible {
if (target == FUNGIBLE_MODULE_ADDRESS || target == address(this)) revert InvalidTarget();

IZRC20(zrc20).deposit(target, amount);
UniversalContract(target).onCrossChainCall(context, zrc20, amount, message);
}

// Deposit zeta and call user specified contract on ZEVM
// TODO: Finalize access control
// https://github.com/zeta-chain/protocol-contracts/issues/204
function depositAndCall(
zContext calldata context,
uint256 amount,
address target,
bytes calldata message
) external {
if (msg.sender != FUNGIBLE_MODULE_ADDRESS) revert CallerIsNotFungibleModule();
) external onlyFungible {
if (target == FUNGIBLE_MODULE_ADDRESS || target == address(this)) revert InvalidTarget();

_transferZETA(amount, target);
UniversalContract(target).onCrossChainCall(context, zetaToken, amount, message);
}

// Revert user specified contract on ZEVM
// TODO: Finalize access control
// https://github.com/zeta-chain/protocol-contracts/issues/204
function executeRevert(
revertContext calldata context,
address zrc20,
uint256 amount,
address target,
bytes calldata message
) external {
if (msg.sender != FUNGIBLE_MODULE_ADDRESS) revert CallerIsNotFungibleModule();

) external onlyFungible {
UniversalContract(target).onRevert(context, zrc20, amount, message);
}

// Deposit foreign coins into ZRC20 and revert user specified contract on ZEVM
// TODO: Finalize access control
// https://github.com/zeta-chain/protocol-contracts/issues/204
function depositAndRevert(
revertContext calldata context,
address zrc20,
uint256 amount,
address target,
bytes calldata message
) external {
if (msg.sender != FUNGIBLE_MODULE_ADDRESS) revert CallerIsNotFungibleModule();
) external onlyFungible {
if (target == FUNGIBLE_MODULE_ADDRESS || target == address(this)) revert InvalidTarget();

IZRC20(zrc20).deposit(target, amount);
Expand Down
Loading
Loading