Skip to content

Commit

Permalink
feat: initialize a prototypes (#162)
Browse files Browse the repository at this point in the history
Co-authored-by: skosito <skostic9242@gmail.com>
Co-authored-by: Francisco de Borja Aranda Castillejo <me@fbac.dev>
  • Loading branch information
3 people authored Jul 3, 2024
1 parent 925af7b commit a8343cb
Show file tree
Hide file tree
Showing 162 changed files with 39,738 additions and 77 deletions.
50 changes: 50 additions & 0 deletions contracts/prototypes/evm/ERC20CustodyNew.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.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{
using SafeERC20 for IERC20;
error ZeroAddress();

IGatewayEVM public gateway;

event Withdraw(address indexed token, address indexed to, uint256 amount);
event WithdrawAndCall(address indexed token, address indexed to, uint256 amount, bytes data);

constructor(address _gateway) {
if (_gateway == address(0)) {
revert ZeroAddress();
}
gateway = IGatewayEVM(_gateway);
}

// 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 {
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) external nonReentrant {
// Transfer the tokens to the Gateway contract
IERC20(token).safeTransfer(address(gateway), amount);

// Forward the call to the Gateway contract
gateway.executeWithERC20(token, to, amount, data);

emit WithdrawAndCall(token, to, amount, data);
}
}
145 changes: 145 additions & 0 deletions contracts/prototypes/evm/GatewayEVM.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

// The GatewayEVM contract is the endpoint to call smart contracts on external chains
// The contract doesn't hold any funds and should never have active allowances
contract GatewayEVM is Initializable, OwnableUpgradeable, UUPSUpgradeable {
using SafeERC20 for IERC20;

error ExecutionFailed();
error DepositFailed();
error InsufficientETHAmount();
error InsufficientERC20Amount();
error ZeroAddress();
error ApprovalFailed();

address public custody;
address public tssAddress;

event Executed(address indexed destination, uint256 value, bytes data);
event ExecutedWithERC20(address indexed token, address indexed to, uint256 amount, bytes data);
event Deposit(address indexed sender, address indexed receiver, uint256 amount, address asset, bytes payload);
event Call(address indexed sender, address indexed receiver, bytes payload);

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

function initialize(address _tssAddress) public initializer {
__Ownable_init();
__UUPSUpgradeable_init();

if (_tssAddress == address(0)) revert ZeroAddress();

tssAddress = _tssAddress;
}

function _authorizeUpgrade(address newImplementation) internal override onlyOwner() {}

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;
}

// 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) {
bytes memory result = _execute(destination, data);

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

return result;
}

// Called by the ERC20Custody contract
// It call a function using ERC20 transfer
// Since the goal is to allow calling contract not designed for ZetaChain specifically, it uses ERC20 allowance system
// It provides allowance to destination contract and call destination contract. In the end, it remove remaining allowance and transfer remaining tokens back to the custody contract for security purposes
function executeWithERC20(
address token,
address to,
uint256 amount,
bytes calldata data
) external returns (bytes memory) {
if (amount == 0) revert InsufficientETHAmount();
// Approve the target contract to spend the tokens
if(!resetApproval(token, to)) revert ApprovalFailed();
if(!IERC20(token).approve(to, amount)) revert ApprovalFailed();

// Execute the call on the target contract
bytes memory result = _execute(to, data);

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

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

emit ExecutedWithERC20(token, to, amount, data);

return result;
}

// Deposit ETH to tss
function deposit(address receiver) external payable {
if (msg.value == 0) revert InsufficientETHAmount();
(bool deposited, ) = tssAddress.call{value: msg.value}("");

if (deposited == false) revert DepositFailed();

emit Deposit(msg.sender, receiver, msg.value, address(0), "");
}

// Deposit ERC20 tokens to custody
function deposit(address receiver, uint256 amount, address asset) external {
if (amount == 0) revert InsufficientERC20Amount();
IERC20(asset).safeTransferFrom(msg.sender, address(custody), amount);

emit Deposit(msg.sender, receiver, amount, asset, "");
}

// Deposit ETH to tss and call an omnichain smart contract
function depositAndCall(address receiver, bytes calldata payload) external payable {
if (msg.value == 0) revert InsufficientETHAmount();
(bool deposited, ) = tssAddress.call{value: msg.value}("");

if (deposited == false) revert DepositFailed();

emit Deposit(msg.sender, receiver, msg.value, address(0), payload);
}

// Deposit ERC20 tokens to custody and call an omnichain smart contract
function depositAndCall(address receiver, uint256 amount, address asset, bytes calldata payload) external {
if (amount == 0) revert InsufficientERC20Amount();
IERC20(asset).safeTransferFrom(msg.sender, address(custody), amount);

emit Deposit(msg.sender, receiver, amount, asset, payload);
}

// Call an omnichain smart contract without asset transfer
function call(address receiver, bytes calldata payload) external {
emit Call(msg.sender, receiver, payload);
}

function setCustody(address _custody) external {
custody = _custody;
}

function resetApproval(address token, address to) private returns (bool) {
return IERC20(token).approve(to, 0);
}
}
118 changes: 118 additions & 0 deletions contracts/prototypes/evm/GatewayEVMUpgradeTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";


// NOTE: Purpose of this contract is to test upgrade process, the only difference should be name of Executed event
// The Gateway contract is the endpoint to call smart contracts on external chains
// The contract doesn't hold any funds and should never have active allowances
contract GatewayEVMUpgradeTest is Initializable, OwnableUpgradeable, UUPSUpgradeable {
using SafeERC20 for IERC20;

error ExecutionFailed();
error SendFailed();
error InsufficientETHAmount();
error ZeroAddress();
error ApprovalFailed();

address public custody;
address public tssAddress;

event ExecutedV2(address indexed destination, uint256 value, bytes data);
event ExecutedWithERC20(address indexed token, address indexed to, uint256 amount, bytes data);
event SendERC20(bytes recipient, address indexed asset, uint256 amount);
event Send(bytes recipient, uint256 amount);

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

function initialize(address _tssAddress) public initializer {
__Ownable_init();
__UUPSUpgradeable_init();

if (_tssAddress == address(0)) revert ZeroAddress();

tssAddress = _tssAddress;
}

function _authorizeUpgrade(address newImplementation) internal override onlyOwner() {}

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;
}

// 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) {
bytes memory result = _execute(destination, data);

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

return result;
}

// Called by the ERC20Custody contract
// It call a function using ERC20 transfer
// Since the goal is to allow calling contract not designed for ZetaChain specifically, it uses ERC20 allowance system
// It provides allowance to destination contract and call destination contract. In the end, it remove remaining allowance and transfer remaining tokens back to the custody contract for security purposes
function executeWithERC20(
address token,
address to,
uint256 amount,
bytes calldata data
) external returns (bytes memory) {
// Approve the target contract to spend the tokens
if(!IERC20(token).approve(to, 0)) revert ApprovalFailed();
if(!IERC20(token).approve(to, amount)) revert ApprovalFailed();

// Execute the call on the target contract
bytes memory result = _execute(to, data);

// Reset approval
if(!IERC20(token).approve(to, 0)) revert ApprovalFailed();

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

emit ExecutedWithERC20(token, to, amount, data);

return result;
}

// Transfer specified token amount to ERC20Custody and emits event
function sendERC20(bytes calldata recipient, address token, uint256 amount) external {
IERC20(token).safeTransferFrom(msg.sender, address(custody), amount);

emit SendERC20(recipient, token, amount);
}

// Transfer specified ETH amount to TSS address and emits event
function send(bytes calldata recipient, uint256 amount) external payable {
if (msg.value == 0) revert InsufficientETHAmount();

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

if (sent == false) revert SendFailed();

emit Send(recipient, msg.value);
}

function setCustody(address _custody) external {
custody = _custody;
}
}
37 changes: 37 additions & 0 deletions contracts/prototypes/evm/Receiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

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

contract Receiver {
using SafeERC20 for IERC20;

event ReceivedPayable(address sender, uint256 value, string str, uint256 num, bool flag);
event ReceivedNonPayable(address sender, string[] strs, uint256[] nums, bool flag);
event ReceivedERC20(address sender, uint256 amount, address token, address destination);
event ReceivedNoParams(address sender);

// Payable function
function receivePayable(string memory str, uint256 num, bool flag) external payable {
emit ReceivedPayable(msg.sender, msg.value, str, num, flag);
}

// Non-payable function
function receiveNonPayable(string[] memory strs, uint256[] memory nums, bool flag) external {
emit ReceivedNonPayable(msg.sender, strs, nums, flag);
}

// Function using IERC20
function receiveERC20(uint256 amount, address token, address destination) external {
// Transfer tokens from the Gateway contract to the destination address
IERC20(token).safeTransferFrom(msg.sender, destination, amount);

emit ReceivedERC20(msg.sender, amount, token, destination);
}

// Function without parameters
function receiveNoParams() external {
emit ReceivedNoParams(msg.sender);
}
}
12 changes: 12 additions & 0 deletions contracts/prototypes/evm/TestERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract TestERC20 is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}

function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
17 changes: 17 additions & 0 deletions contracts/prototypes/evm/interfaces.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

interface IGatewayEVM {
function executeWithERC20(
address token,
address to,
uint256 amount,
bytes calldata data
) external returns (bytes memory);

function execute(address destination, bytes calldata data) external payable returns (bytes memory);

function sendERC20(bytes calldata recipient, address asset, uint256 amount) external;

function send(bytes calldata recipient, uint256 amount) external payable;
}
Loading

0 comments on commit a8343cb

Please sign in to comment.