Skip to content

Commit

Permalink
Merge pull request #3 from skip-mev/revision_v1
Browse files Browse the repository at this point in the history
Use proxy contract, enable ERC20 forwarding, fix unwrapping
  • Loading branch information
wllmshao authored Nov 9, 2023
2 parents e55dfdf + 44f738a commit ee9c448
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 34 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
[submodule "lib/axelar-gmp-sdk-solidity"]
path = lib/axelar-gmp-sdk-solidity
url = https://github.com/axelarnetwork/axelar-gmp-sdk-solidity
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
2 changes: 1 addition & 1 deletion lib/openzeppelin-contracts
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts-upgradeable
9 changes: 8 additions & 1 deletion script/AxelarHandlerDeployment.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {Environment} from "test/Environment.sol";

import {AxelarHandler} from "src/AxelarHandler.sol";

import {ERC1967Proxy} from "lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract AxelarHandlerDeploymentScript is Script {
Environment public env;
AxelarHandler public handler;
Expand All @@ -22,7 +24,12 @@ contract AxelarHandlerDeploymentScript is Script {
string memory wethSymbol = env.wethSymbol();

vm.startBroadcast(vm.envUint("PRIVATE_KEY"));
handler = new AxelarHandler(gateway, gasService, wethSymbol);
AxelarHandler handlerImpl = new AxelarHandler();
ERC1967Proxy handlerProxy = new ERC1967Proxy(
address(handlerImpl),
abi.encodeWithSignature("initialize(address,address,string)")
);
handler = AxelarHandler(payable(address(handlerProxy)));
vm.stopBroadcast();

console2.log("Axelar Handler Address: ", address(handler));
Expand Down
98 changes: 98 additions & 0 deletions src/AxelarExecutableUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {IAxelarGateway} from "lib/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol";
import {IAxelarExecutable} from "lib/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarExecutable.sol";
import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";

contract AxelarExecutableUpgradeable is IAxelarExecutable, Initializable {
IAxelarGateway public gateway;
uint256[5] __gap;

constructor() {
_disableInitializers();
}

function __AxelarExecutable_init(
address axelarGateway
) internal onlyInitializing {
__AxelarExecutable_init_unchained(axelarGateway);
}

function __AxelarExecutable_init_unchained(
address axelarGateway
) internal onlyInitializing {
gateway = IAxelarGateway(axelarGateway);
}

function initialize(address gateway_) external initializer {
if (gateway_ == address(0)) revert InvalidAddress();

gateway = IAxelarGateway(gateway_);
}

function execute(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload
) external {
bytes32 payloadHash = keccak256(payload);

if (
!gateway.validateContractCall(
commandId,
sourceChain,
sourceAddress,
payloadHash
)
) revert NotApprovedByGateway();

_execute(sourceChain, sourceAddress, payload);
}

function executeWithToken(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload,
string calldata tokenSymbol,
uint256 amount
) external {
bytes32 payloadHash = keccak256(payload);

if (
!gateway.validateContractCallAndMint(
commandId,
sourceChain,
sourceAddress,
payloadHash,
tokenSymbol,
amount
)
) revert NotApprovedByGateway();

_executeWithToken(
sourceChain,
sourceAddress,
payload,
tokenSymbol,
amount
);
}

function _execute(
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload
) internal virtual {}

function _executeWithToken(
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload,
string calldata tokenSymbol,
uint256 amount
) internal virtual {}
}
75 changes: 53 additions & 22 deletions src/AxelarHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,23 @@ pragma solidity 0.8.18;
import {IWETH} from "./interfaces/IWETH.sol";

import {IAxelarGasService} from "lib/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol";
import {AxelarExecutable} from "lib/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol";
import {AxelarExecutableUpgradeable} from "./AxelarExecutableUpgradeable.sol";

import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20Upgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/IERC20Upgradeable.sol";
import {SafeERC20Upgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {Ownable2StepUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol";
import {UUPSUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";
import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";

/// @title AxelarHandler
/// @notice allows to send and receive tokens to/from other chains through axelar gateway while wrapping the native tokens.
/// @author Skip Protocol.
contract AxelarHandler is AxelarExecutable {
using SafeERC20 for IERC20;
contract AxelarHandler is
AxelarExecutableUpgradeable,
Ownable2StepUpgradeable,
UUPSUpgradeable
{
using SafeERC20Upgradeable for IERC20Upgradeable;

error EmptySymbol();
error NativeSentDoesNotMatchAmounts();
Expand All @@ -25,21 +32,29 @@ contract AxelarHandler is AxelarExecutable {
error NonNativeCannotBeUnwrapped();
error NativePaymentFailed();

bytes32 private immutable _wETHSymbolHash;
bytes32 private _wETHSymbolHash;

string public wETHSymbol;
IAxelarGasService public gasService;

mapping(address => bool) public approved;

constructor(
constructor() {
_disableInitializers();
}

function initialize(
address axGateway,
address axGasService,
string memory wethSymbol
) AxelarExecutable(axGateway) {
) external initializer {
if (axGasService == address(0)) revert ZeroAddress();
if (bytes(wethSymbol).length == 0) revert EmptySymbol();

__AxelarExecutable_init(axGateway);
__Ownable2Step_init();
__UUPSUpgradeable_init();

gasService = IAxelarGasService(axGasService);
wETHSymbol = wethSymbol;
_wETHSymbolHash = keccak256(abi.encodePacked(wethSymbol));
Expand Down Expand Up @@ -83,7 +98,7 @@ contract AxelarHandler is AxelarExecutable {
if (amount == 0) revert ZeroAmount();
if (bytes(symbol).length == 0) revert EmptySymbol();

IERC20 token = IERC20(_getTokenAddress(symbol));
IERC20Upgradeable token = IERC20Upgradeable(_getTokenAddress(symbol));

token.safeTransferFrom(msg.sender, address(this), amount);

Expand Down Expand Up @@ -156,7 +171,7 @@ contract AxelarHandler is AxelarExecutable {
if (bytes(symbol).length == 0) revert EmptySymbol();

// Get the token address.
IERC20 token = IERC20(_getTokenAddress(symbol));
IERC20Upgradeable token = IERC20Upgradeable(_getTokenAddress(symbol));

// Transfer the amount from the msg.sender.
token.safeTransferFrom(msg.sender, address(this), amount);
Expand Down Expand Up @@ -205,7 +220,7 @@ contract AxelarHandler is AxelarExecutable {
if (bytes(symbol).length == 0) revert EmptySymbol();

// Get the token address.
IERC20 token = IERC20(_getTokenAddress(symbol));
IERC20Upgradeable token = IERC20Upgradeable(_getTokenAddress(symbol));

// Transfer the amount and gas payment amount from the msg.sender.
token.safeTransferFrom(
Expand Down Expand Up @@ -235,6 +250,10 @@ contract AxelarHandler is AxelarExecutable {
);
}

receive() external payable {}

fallback() external payable {}

/// @notice Ensures a token is supported by the axelar gateway, and returns it's address.
/// @param symbol the symbol of the ERC20 token to be checked.
function _getTokenAddress(
Expand All @@ -244,8 +263,14 @@ contract AxelarHandler is AxelarExecutable {
if (token == address(0)) revert TokenNotSupported();

if (!approved[token]) {
IERC20(token).approve(address(gateway), type(uint256).max);
IERC20(token).approve(address(gasService), type(uint256).max);
IERC20Upgradeable(token).approve(
address(gateway),
type(uint256).max
);
IERC20Upgradeable(token).approve(
address(gasService),
type(uint256).max
);
approved[token] = true;
}
}
Expand All @@ -268,14 +293,15 @@ contract AxelarHandler is AxelarExecutable {
payload,
(bool, address)
);
IERC20 token = IERC20(_getTokenAddress(tokenSymbol));

if (unwrap) {
// Non-native tokens cannot be unwrapped.
if (keccak256(abi.encodePacked(tokenSymbol)) != _wETHSymbolHash) {
revert NonNativeCannotBeUnwrapped();
}
IERC20Upgradeable token = IERC20Upgradeable(
_getTokenAddress(tokenSymbol)
);

// If unwrap is set and the token can be unwrapped.
if (
unwrap &&
keccak256(abi.encodePacked(tokenSymbol)) != _wETHSymbolHash
) {
// Unwrap native token.
IWETH weth = IWETH(address(token));
weth.withdraw(amount);
Expand All @@ -286,9 +312,14 @@ contract AxelarHandler is AxelarExecutable {
if (!success) {
revert NativePaymentFailed();
}
} else {
// Just send the tokens received to the destination.
}
// Just send the tokens received to the destination.
else {
token.safeTransfer(destination, amount);
}
}

function _authorizeUpgrade(
address newImplementation
) internal override onlyOwner {}
}
4 changes: 2 additions & 2 deletions src/interfaces/IWETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
// Adapted by Ethereum Community 2021
pragma solidity ^0.8.0;

import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {IERC20Upgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/IERC20Upgradeable.sol";

/// @dev Wrapped Ether v10 (WETH10) is an Ether (ETH) ERC-20 wrapper. You can `deposit` ETH and obtain a WETH10 balance which can then be operated as an ERC-20 token. You can
/// `withdraw` ETH from WETH10, which will then burn WETH10 token in your wallet. The amount of WETH10 token in any wallet is always identical to the
/// balance of ETH deposited minus the ETH withdrawn with that specific wallet.
interface IWETH is IERC20 {
interface IWETH is IERC20Upgradeable {
/// @dev `msg.value` of ETH sent to this contract grants caller account a matching increase in WETH10 token balance.
/// Emits {Transfer} event to reflect WETH10 token mint of `msg.value` from `address(0)` to caller account.
function deposit() external payable;
Expand Down
Loading

0 comments on commit ee9c448

Please sign in to comment.