pyth-crosschain/ethereum/contracts/Module-ERC20.sol

212 lines
7.6 KiB
Solidity

// contracts/Module-ERC20.sol
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./BytesLib.sol";
import "./WrappedAsset.sol";
import "./Wormhole.sol";
contract ERC20Bridge is ReentrancyGuard {
using SafeERC20 for IERC20;
using BytesLib for bytes;
using SafeMath for uint256;
uint8 public CHAIN_ID = 2;
uint64 constant MAX_UINT64 = 18_446_744_073_709_551_615;
// Address of the Wrapped asset template
address public wrappedAssetMaster;
// Address of the Wormhole
Wormhole public wormhole;
// Address of the official WETH contract
address constant WETHAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
event LogTokensLocked(
uint8 target_chain,
uint8 token_chain,
uint8 token_decimals,
bytes32 indexed token,
bytes32 indexed sender,
bytes32 recipient,
uint256 amount,
uint32 nonce
);
// Mapping of already consumedVAAs
mapping(bytes32 => bool) public consumedVAAs;
// Mapping of wrapped asset ERC20 contracts
mapping(bytes32 => address) public wrappedAssets;
mapping(address => bool) public isWrappedAsset;
constructor(address wrapped_asset_master, address payable wormhole_bridge) public {
wrappedAssetMaster = wrapped_asset_master;
wormhole = Wormhole(wormhole_bridge);
}
function submitVAA(
bytes calldata vaa
) public nonReentrant {
Wormhole.ParsedVAA memory parsed_vaa = wormhole.parseAndVerifyVAA(vaa);
require(!consumedVAAs[parsed_vaa.hash], "vaa was already executed");
// Set the VAA as consumed
consumedVAAs[parsed_vaa.hash] = true;
// Execute transfer
vaaTransfer(parsed_vaa.payload);
}
function vaaTransfer(bytes memory data) private {
//uint32 nonce = data.toUint64(0);
uint8 source_chain = data.toUint8(4);
uint8 target_chain = data.toUint8(5);
//bytes32 source_address = data.toBytes32(6);
//bytes32 target_address = data.toBytes32(38);
address target_address = data.toAddress(38 + 12);
uint8 token_chain = data.toUint8(70);
//bytes32 token_address = data.toBytes32(71);
uint256 amount = data.toUint256(104);
require(source_chain != target_chain, "same chain transfers are not supported");
require(target_chain == CHAIN_ID, "transfer must be incoming");
if (token_chain != CHAIN_ID) {
bytes32 token_address = data.toBytes32(71);
bytes32 asset_id = keccak256(abi.encodePacked(token_chain, token_address));
// if yes: mint to address
// if no: create and mint
address wrapped_asset = wrappedAssets[asset_id];
if (wrapped_asset == address(0)) {
uint8 asset_decimals = data.toUint8(103);
wrapped_asset = deployWrappedAsset(asset_id, token_chain, token_address, asset_decimals);
}
WrappedAsset(wrapped_asset).mint(target_address, amount);
} else {
address token_address = data.toAddress(71 + 12);
uint8 decimals = ERC20(token_address).decimals();
// Readjust decimals if they've previously been truncated
if (decimals > 9) {
amount = amount.mul(10 ** uint256(decimals - 9));
}
IERC20(token_address).safeTransfer(target_address, amount);
}
}
function deployWrappedAsset(bytes32 seed, uint8 token_chain, bytes32 token_address, uint8 decimals) private returns (address asset){
// Taken from https://github.com/OpenZeppelin/openzeppelin-sdk/blob/master/packages/lib/contracts/upgradeability/ProxyFactory.sol
// Licensed under MIT
bytes20 targetBytes = bytes20(wrappedAssetMaster);
assembly {
let clone := mload(0x40)
mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(clone, 0x14), targetBytes)
mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
asset := create2(0, clone, 0x37, seed)
}
// Call initializer
WrappedAsset(asset).initialize(token_chain, token_address, decimals);
// Store address
wrappedAssets[seed] = asset;
isWrappedAsset[asset] = true;
}
function lockAssets(
address asset,
uint256 amount,
bytes32 recipient,
uint8 target_chain,
uint32 nonce,
bool refund_dust
) public nonReentrant {
require(target_chain != CHAIN_ID, "must not transfer to the same chain");
uint8 asset_chain = CHAIN_ID;
bytes32 asset_address;
uint8 decimals = ERC20(asset).decimals();
if (isWrappedAsset[asset]) {
WrappedAsset(asset).burn(msg.sender, amount);
asset_chain = WrappedAsset(asset).assetChain();
asset_address = WrappedAsset(asset).assetAddress();
} else {
uint256 balanceBefore = IERC20(asset).balanceOf(address(this));
IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
uint256 balanceAfter = IERC20(asset).balanceOf(address(this));
// The amount that was transferred in is the delta between balance before and after the transfer.
// This is to properly handle tokens that charge a fee on transfer.
amount = balanceAfter.sub(balanceBefore);
// Decimal adjust amount - we keep the dust
if (decimals > 9) {
uint256 original_amount = amount;
amount = amount.div(10 ** uint256(decimals - 9));
if (refund_dust) {
IERC20(asset).safeTransfer(msg.sender, original_amount.mod(10 ** uint256(decimals - 9)));
}
decimals = 9;
}
require(balanceAfter.div(10 ** uint256(ERC20(asset).decimals() - 9)) <= MAX_UINT64, "bridge balance would exceed maximum");
asset_address = bytes32(uint256(asset));
}
// Check here after truncation
require(amount != 0, "truncated amount must not be 0");
emit LogTokensLocked(target_chain, asset_chain, decimals, asset_address, bytes32(uint256(msg.sender)), recipient, amount, nonce);
}
function lockETH(
bytes32 recipient,
uint8 target_chain,
uint32 nonce
) public payable nonReentrant {
require(target_chain != CHAIN_ID, "must not transfer to the same chain");
uint256 remainder = msg.value.mod(10 ** 9);
uint256 transfer_amount = msg.value.div(10 ** 9);
require(transfer_amount != 0, "truncated amount must not be 0");
// Transfer back remainder
msg.sender.transfer(remainder);
// Wrap tx value in WETH
WETH(WETHAddress).deposit{value : msg.value - remainder}();
// Log deposit of WETH
emit LogTokensLocked(target_chain, CHAIN_ID, 9, bytes32(uint256(WETHAddress)), bytes32(uint256(msg.sender)), recipient, transfer_amount, nonce);
}
fallback() external payable {revert("please use lockETH to transfer ETH to Solana");}
receive() external payable {revert("please use lockETH to transfer ETH to Solana");}
}
interface WETH is IERC20 {
function deposit() external payable;
function withdraw(uint256 amount) external;
}