-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: initialize a prototypes (#162)
Co-authored-by: skosito <skostic9242@gmail.com> Co-authored-by: Francisco de Borja Aranda Castillejo <me@fbac.dev>
- Loading branch information
1 parent
925af7b
commit a8343cb
Showing
162 changed files
with
39,738 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.