2021-06-21 07:04:07 -07:00
|
|
|
// contracts/Bridge.sol
|
|
|
|
// SPDX-License-Identifier: Apache 2
|
|
|
|
|
|
|
|
pragma solidity ^0.8.0;
|
|
|
|
|
2022-01-20 13:37:08 -08:00
|
|
|
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
2021-06-21 07:04:07 -07:00
|
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
|
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
|
|
|
|
|
|
import "../libraries/external/BytesLib.sol";
|
|
|
|
|
|
|
|
import "./BridgeGetters.sol";
|
|
|
|
import "./BridgeSetters.sol";
|
|
|
|
import "./BridgeStructs.sol";
|
|
|
|
import "./BridgeGovernance.sol";
|
|
|
|
|
|
|
|
import "./token/Token.sol";
|
|
|
|
import "./token/TokenImplementation.sol";
|
|
|
|
|
2022-01-20 13:37:08 -08:00
|
|
|
contract Bridge is BridgeGovernance, ReentrancyGuard {
|
2021-06-21 07:04:07 -07:00
|
|
|
using BytesLib for bytes;
|
|
|
|
|
2022-05-23 15:06:33 -07:00
|
|
|
/*
|
|
|
|
* @dev Produce a AssetMeta message for a given token
|
|
|
|
*/
|
2022-09-07 13:22:27 -07:00
|
|
|
function attestToken(address tokenAddress, uint32 nonce) public payable returns (uint64 sequence) {
|
2021-06-21 07:04:07 -07:00
|
|
|
// decimals, symbol & token are not part of the core ERC20 token standard, so we need to support contracts that dont implement them
|
|
|
|
(,bytes memory queriedDecimals) = tokenAddress.staticcall(abi.encodeWithSignature("decimals()"));
|
|
|
|
(,bytes memory queriedSymbol) = tokenAddress.staticcall(abi.encodeWithSignature("symbol()"));
|
|
|
|
(,bytes memory queriedName) = tokenAddress.staticcall(abi.encodeWithSignature("name()"));
|
|
|
|
|
|
|
|
uint8 decimals = abi.decode(queriedDecimals, (uint8));
|
|
|
|
|
|
|
|
string memory symbolString = abi.decode(queriedSymbol, (string));
|
|
|
|
string memory nameString = abi.decode(queriedName, (string));
|
|
|
|
|
|
|
|
bytes32 symbol;
|
|
|
|
bytes32 name;
|
|
|
|
assembly {
|
2021-10-22 05:32:10 -07:00
|
|
|
// first 32 bytes hold string length
|
2021-06-21 07:04:07 -07:00
|
|
|
symbol := mload(add(symbolString, 32))
|
|
|
|
name := mload(add(nameString, 32))
|
|
|
|
}
|
|
|
|
|
|
|
|
BridgeStructs.AssetMeta memory meta = BridgeStructs.AssetMeta({
|
2021-09-01 04:13:51 -07:00
|
|
|
payloadID : 2,
|
2022-05-27 03:56:25 -07:00
|
|
|
tokenAddress : bytes32(uint256(uint160(tokenAddress))), // Address of the token. Left-zero-padded if shorter than 32 bytes
|
|
|
|
tokenChain : chainId(), // Chain ID of the token
|
|
|
|
decimals : decimals, // Number of decimals of the token (big-endian uint8)
|
|
|
|
symbol : symbol, // Symbol of the token (UTF-8)
|
|
|
|
name : name // Name of the token (UTF-8)
|
2021-06-21 07:04:07 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
bytes memory encoded = encodeAssetMeta(meta);
|
|
|
|
|
|
|
|
sequence = wormhole().publishMessage{
|
2021-10-22 05:32:10 -07:00
|
|
|
value : msg.value
|
2022-05-20 08:06:53 -07:00
|
|
|
}(nonce, encoded, finality());
|
2021-06-21 07:04:07 -07:00
|
|
|
}
|
|
|
|
|
2022-05-27 03:56:25 -07:00
|
|
|
/*
|
|
|
|
* @notice Send eth through portal by first wrapping it to WETH.
|
|
|
|
*/
|
|
|
|
function wrapAndTransferETH(
|
|
|
|
uint16 recipientChain,
|
|
|
|
bytes32 recipient,
|
|
|
|
uint256 arbiterFee,
|
|
|
|
uint32 nonce
|
2022-09-07 13:22:27 -07:00
|
|
|
) public payable returns (uint64 sequence) {
|
2022-05-27 03:56:25 -07:00
|
|
|
BridgeStructs.TransferResult
|
|
|
|
memory transferResult = _wrapAndTransferETH(arbiterFee);
|
|
|
|
sequence = logTransfer(
|
|
|
|
transferResult.tokenChain,
|
|
|
|
transferResult.tokenAddress,
|
|
|
|
transferResult.normalizedAmount,
|
|
|
|
recipientChain,
|
|
|
|
recipient,
|
|
|
|
transferResult.normalizedArbiterFee,
|
|
|
|
transferResult.wormholeFee,
|
|
|
|
nonce
|
|
|
|
);
|
2021-12-22 19:48:52 -08:00
|
|
|
}
|
|
|
|
|
2022-05-27 03:56:25 -07:00
|
|
|
/*
|
|
|
|
* @notice Send eth through portal by first wrapping it.
|
|
|
|
*
|
|
|
|
* @dev This type of transfer is called a "contract-controlled transfer".
|
|
|
|
* There are three differences from a regular token transfer:
|
|
|
|
* 1) Additional arbitrary payload can be attached to the message
|
|
|
|
* 2) Only the recipient (typically a contract) can redeem the transaction
|
|
|
|
* 3) The sender's address (msg.sender) is also included in the transaction payload
|
|
|
|
*
|
|
|
|
* With these three additional components, xDapps can implement cross-chain
|
|
|
|
* composable interactions.
|
|
|
|
*/
|
|
|
|
function wrapAndTransferETHWithPayload(
|
|
|
|
uint16 recipientChain,
|
|
|
|
bytes32 recipient,
|
|
|
|
uint32 nonce,
|
|
|
|
bytes memory payload
|
2022-09-07 13:22:27 -07:00
|
|
|
) public payable returns (uint64 sequence) {
|
2022-05-27 03:56:25 -07:00
|
|
|
BridgeStructs.TransferResult
|
2022-05-23 15:06:33 -07:00
|
|
|
memory transferResult = _wrapAndTransferETH(0);
|
2022-05-27 03:56:25 -07:00
|
|
|
sequence = logTransferWithPayload(
|
|
|
|
transferResult.tokenChain,
|
|
|
|
transferResult.tokenAddress,
|
|
|
|
transferResult.normalizedAmount,
|
|
|
|
recipientChain,
|
|
|
|
recipient,
|
|
|
|
transferResult.wormholeFee,
|
|
|
|
nonce,
|
|
|
|
payload
|
|
|
|
);
|
2021-12-22 19:48:52 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
function _wrapAndTransferETH(uint256 arbiterFee) internal returns (BridgeStructs.TransferResult memory transferResult) {
|
2021-06-21 07:04:07 -07:00
|
|
|
uint wormholeFee = wormhole().messageFee();
|
|
|
|
|
|
|
|
require(wormholeFee < msg.value, "value is smaller than wormhole fee");
|
|
|
|
|
2021-07-27 02:36:49 -07:00
|
|
|
uint amount = msg.value - wormholeFee;
|
|
|
|
|
|
|
|
require(arbiterFee <= amount, "fee is bigger than amount minus wormhole fee");
|
|
|
|
|
2021-10-22 05:32:10 -07:00
|
|
|
uint normalizedAmount = normalizeAmount(amount, 18);
|
|
|
|
uint normalizedArbiterFee = normalizeAmount(arbiterFee, 18);
|
2021-07-27 02:36:49 -07:00
|
|
|
|
|
|
|
// refund dust
|
2021-10-22 05:32:10 -07:00
|
|
|
uint dust = amount - deNormalizeAmount(normalizedAmount, 18);
|
2021-07-27 02:36:49 -07:00
|
|
|
if (dust > 0) {
|
2021-09-01 04:13:51 -07:00
|
|
|
payable(msg.sender).transfer(dust);
|
2021-07-27 02:36:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// deposit into WETH
|
2021-06-21 07:04:07 -07:00
|
|
|
WETH().deposit{
|
2021-10-22 05:32:10 -07:00
|
|
|
value : amount - dust
|
2021-06-21 07:04:07 -07:00
|
|
|
}();
|
|
|
|
|
2021-07-27 02:36:49 -07:00
|
|
|
// track and check outstanding token amounts
|
|
|
|
bridgeOut(address(WETH()), normalizedAmount);
|
|
|
|
|
2021-12-22 19:48:52 -08:00
|
|
|
transferResult = BridgeStructs.TransferResult({
|
|
|
|
tokenChain : chainId(),
|
|
|
|
tokenAddress : bytes32(uint256(uint160(address(WETH())))),
|
|
|
|
normalizedAmount : normalizedAmount,
|
|
|
|
normalizedArbiterFee : normalizedArbiterFee,
|
|
|
|
wormholeFee : wormholeFee
|
|
|
|
});
|
2021-06-21 07:04:07 -07:00
|
|
|
}
|
|
|
|
|
2022-05-27 03:56:25 -07:00
|
|
|
/*
|
|
|
|
* @notice Send ERC20 token through portal.
|
|
|
|
*/
|
|
|
|
function transferTokens(
|
|
|
|
address token,
|
|
|
|
uint256 amount,
|
|
|
|
uint16 recipientChain,
|
|
|
|
bytes32 recipient,
|
|
|
|
uint256 arbiterFee,
|
|
|
|
uint32 nonce
|
2022-09-07 13:22:27 -07:00
|
|
|
) public payable nonReentrant returns (uint64 sequence) {
|
2022-05-27 03:56:25 -07:00
|
|
|
BridgeStructs.TransferResult memory transferResult = _transferTokens(
|
|
|
|
token,
|
|
|
|
amount,
|
|
|
|
arbiterFee
|
|
|
|
);
|
|
|
|
sequence = logTransfer(
|
|
|
|
transferResult.tokenChain,
|
|
|
|
transferResult.tokenAddress,
|
|
|
|
transferResult.normalizedAmount,
|
|
|
|
recipientChain,
|
|
|
|
recipient,
|
|
|
|
transferResult.normalizedArbiterFee,
|
|
|
|
transferResult.wormholeFee,
|
|
|
|
nonce
|
|
|
|
);
|
2021-12-22 19:48:52 -08:00
|
|
|
}
|
|
|
|
|
2022-05-27 03:56:25 -07:00
|
|
|
/*
|
|
|
|
* @notice Send ERC20 token through portal.
|
|
|
|
*
|
|
|
|
* @dev This type of transfer is called a "contract-controlled transfer".
|
|
|
|
* There are three differences from a regular token transfer:
|
|
|
|
* 1) Additional arbitrary payload can be attached to the message
|
|
|
|
* 2) Only the recipient (typically a contract) can redeem the transaction
|
|
|
|
* 3) The sender's address (msg.sender) is also included in the transaction payload
|
|
|
|
*
|
|
|
|
* With these three additional components, xDapps can implement cross-chain
|
|
|
|
* composable interactions.
|
|
|
|
*/
|
|
|
|
function transferTokensWithPayload(
|
|
|
|
address token,
|
|
|
|
uint256 amount,
|
|
|
|
uint16 recipientChain,
|
|
|
|
bytes32 recipient,
|
|
|
|
uint32 nonce,
|
|
|
|
bytes memory payload
|
2022-09-07 13:22:27 -07:00
|
|
|
) public payable nonReentrant returns (uint64 sequence) {
|
2022-05-27 03:56:25 -07:00
|
|
|
BridgeStructs.TransferResult memory transferResult = _transferTokens(
|
|
|
|
token,
|
|
|
|
amount,
|
2022-05-23 15:06:33 -07:00
|
|
|
0
|
2022-05-27 03:56:25 -07:00
|
|
|
);
|
|
|
|
sequence = logTransferWithPayload(
|
|
|
|
transferResult.tokenChain,
|
|
|
|
transferResult.tokenAddress,
|
|
|
|
transferResult.normalizedAmount,
|
|
|
|
recipientChain,
|
|
|
|
recipient,
|
|
|
|
transferResult.wormholeFee,
|
|
|
|
nonce,
|
|
|
|
payload
|
|
|
|
);
|
2021-12-22 19:48:52 -08:00
|
|
|
}
|
|
|
|
|
2022-05-27 03:56:25 -07:00
|
|
|
/*
|
|
|
|
* @notice Initiate a transfer
|
|
|
|
*/
|
2021-12-22 19:48:52 -08:00
|
|
|
function _transferTokens(address token, uint256 amount, uint256 arbiterFee) internal returns (BridgeStructs.TransferResult memory transferResult) {
|
2021-07-27 02:36:49 -07:00
|
|
|
// determine token parameters
|
|
|
|
uint16 tokenChain;
|
|
|
|
bytes32 tokenAddress;
|
2021-09-01 04:13:51 -07:00
|
|
|
if (isWrappedAsset(token)) {
|
2021-07-27 02:36:49 -07:00
|
|
|
tokenChain = TokenImplementation(token).chainId();
|
|
|
|
tokenAddress = TokenImplementation(token).nativeContract();
|
2021-09-01 04:13:51 -07:00
|
|
|
} else {
|
2021-07-27 02:36:49 -07:00
|
|
|
tokenChain = chainId();
|
|
|
|
tokenAddress = bytes32(uint256(uint160(token)));
|
|
|
|
}
|
|
|
|
|
|
|
|
// query tokens decimals
|
|
|
|
(,bytes memory queriedDecimals) = token.staticcall(abi.encodeWithSignature("decimals()"));
|
|
|
|
uint8 decimals = abi.decode(queriedDecimals, (uint8));
|
|
|
|
|
2021-10-22 05:32:10 -07:00
|
|
|
// don't deposit dust that can not be bridged due to the decimal shift
|
|
|
|
amount = deNormalizeAmount(normalizeAmount(amount, decimals), decimals);
|
2021-07-27 02:36:49 -07:00
|
|
|
|
2021-09-01 04:13:51 -07:00
|
|
|
if (tokenChain == chainId()) {
|
2021-10-22 05:32:10 -07:00
|
|
|
// query own token balance before transfer
|
|
|
|
(,bytes memory queriedBalanceBefore) = token.staticcall(abi.encodeWithSelector(IERC20.balanceOf.selector, address(this)));
|
|
|
|
uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));
|
|
|
|
|
|
|
|
// transfer tokens
|
2021-07-27 02:36:49 -07:00
|
|
|
SafeERC20.safeTransferFrom(IERC20(token), msg.sender, address(this), amount);
|
2021-06-21 07:04:07 -07:00
|
|
|
|
2021-10-22 05:32:10 -07:00
|
|
|
// query own token balance after transfer
|
|
|
|
(,bytes memory queriedBalanceAfter) = token.staticcall(abi.encodeWithSelector(IERC20.balanceOf.selector, address(this)));
|
|
|
|
uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256));
|
|
|
|
|
|
|
|
// correct amount for potential transfer fees
|
|
|
|
amount = balanceAfter - balanceBefore;
|
2021-07-27 02:36:49 -07:00
|
|
|
} else {
|
|
|
|
SafeERC20.safeTransferFrom(IERC20(token), msg.sender, address(this), amount);
|
2021-06-21 07:04:07 -07:00
|
|
|
|
2021-07-27 02:36:49 -07:00
|
|
|
TokenImplementation(token).burn(address(this), amount);
|
2021-06-21 07:04:07 -07:00
|
|
|
}
|
|
|
|
|
2021-10-22 05:32:10 -07:00
|
|
|
// normalize amounts decimals
|
|
|
|
uint256 normalizedAmount = normalizeAmount(amount, decimals);
|
|
|
|
uint256 normalizedArbiterFee = normalizeAmount(arbiterFee, decimals);
|
|
|
|
|
|
|
|
// track and check outstanding token amounts
|
|
|
|
if (tokenChain == chainId()) {
|
|
|
|
bridgeOut(token, normalizedAmount);
|
|
|
|
}
|
|
|
|
|
2021-12-22 19:48:52 -08:00
|
|
|
transferResult = BridgeStructs.TransferResult({
|
|
|
|
tokenChain : tokenChain,
|
|
|
|
tokenAddress : tokenAddress,
|
|
|
|
normalizedAmount : normalizedAmount,
|
|
|
|
normalizedArbiterFee : normalizedArbiterFee,
|
|
|
|
wormholeFee : msg.value
|
|
|
|
});
|
2021-06-21 07:04:07 -07:00
|
|
|
}
|
|
|
|
|
2021-10-22 05:32:10 -07:00
|
|
|
function normalizeAmount(uint256 amount, uint8 decimals) internal pure returns(uint256){
|
|
|
|
if (decimals > 8) {
|
|
|
|
amount /= 10 ** (decimals - 8);
|
|
|
|
}
|
|
|
|
return amount;
|
|
|
|
}
|
|
|
|
|
|
|
|
function deNormalizeAmount(uint256 amount, uint8 decimals) internal pure returns(uint256){
|
|
|
|
if (decimals > 8) {
|
|
|
|
amount *= 10 ** (decimals - 8);
|
|
|
|
}
|
|
|
|
return amount;
|
|
|
|
}
|
|
|
|
|
2022-05-27 03:56:25 -07:00
|
|
|
function logTransfer(
|
|
|
|
uint16 tokenChain,
|
|
|
|
bytes32 tokenAddress,
|
|
|
|
uint256 amount,
|
|
|
|
uint16 recipientChain,
|
|
|
|
bytes32 recipient,
|
|
|
|
uint256 fee,
|
|
|
|
uint256 callValue,
|
|
|
|
uint32 nonce
|
|
|
|
) internal returns (uint64 sequence) {
|
2021-06-21 07:04:07 -07:00
|
|
|
require(fee <= amount, "fee exceeds amount");
|
|
|
|
|
|
|
|
BridgeStructs.Transfer memory transfer = BridgeStructs.Transfer({
|
2022-05-27 03:56:25 -07:00
|
|
|
payloadID: 1,
|
|
|
|
amount: amount,
|
|
|
|
tokenAddress: tokenAddress,
|
|
|
|
tokenChain: tokenChain,
|
|
|
|
to: recipient,
|
|
|
|
toChain: recipientChain,
|
|
|
|
fee: fee
|
2021-06-21 07:04:07 -07:00
|
|
|
});
|
|
|
|
|
2022-05-27 03:56:25 -07:00
|
|
|
sequence = wormhole().publishMessage{value: callValue}(
|
|
|
|
nonce,
|
2022-08-25 13:47:02 -07:00
|
|
|
encodeTransfer(transfer),
|
2022-05-20 08:06:53 -07:00
|
|
|
finality()
|
2022-05-27 03:56:25 -07:00
|
|
|
);
|
2021-06-21 07:04:07 -07:00
|
|
|
}
|
|
|
|
|
2022-05-27 03:56:25 -07:00
|
|
|
/*
|
|
|
|
* @dev Publish a token transfer message with payload.
|
|
|
|
*
|
|
|
|
* @return The sequence number of the published message.
|
|
|
|
*/
|
|
|
|
function logTransferWithPayload(
|
|
|
|
uint16 tokenChain,
|
|
|
|
bytes32 tokenAddress,
|
|
|
|
uint256 amount,
|
|
|
|
uint16 recipientChain,
|
|
|
|
bytes32 recipient,
|
|
|
|
uint256 callValue,
|
|
|
|
uint32 nonce,
|
|
|
|
bytes memory payload
|
|
|
|
) internal returns (uint64 sequence) {
|
|
|
|
BridgeStructs.TransferWithPayload memory transfer = BridgeStructs
|
|
|
|
.TransferWithPayload({
|
|
|
|
payloadID: 3,
|
|
|
|
amount: amount,
|
|
|
|
tokenAddress: tokenAddress,
|
|
|
|
tokenChain: tokenChain,
|
|
|
|
to: recipient,
|
|
|
|
toChain: recipientChain,
|
2022-05-23 15:06:33 -07:00
|
|
|
fromAddress : bytes32(uint256(uint160(msg.sender))),
|
2022-05-27 03:56:25 -07:00
|
|
|
payload: payload
|
|
|
|
});
|
2021-12-22 19:48:52 -08:00
|
|
|
|
2022-05-27 03:56:25 -07:00
|
|
|
sequence = wormhole().publishMessage{value: callValue}(
|
|
|
|
nonce,
|
2022-08-25 13:47:02 -07:00
|
|
|
encodeTransferWithPayload(transfer),
|
2022-05-20 08:06:53 -07:00
|
|
|
finality()
|
2022-05-27 03:56:25 -07:00
|
|
|
);
|
2021-12-22 19:48:52 -08:00
|
|
|
}
|
2022-08-25 13:47:02 -07:00
|
|
|
|
2022-09-07 13:22:27 -07:00
|
|
|
function updateWrapped(bytes memory encodedVm) external returns (address token) {
|
2021-09-01 04:13:51 -07:00
|
|
|
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
|
|
|
|
|
|
|
|
require(valid, reason);
|
|
|
|
require(verifyBridgeVM(vm), "invalid emitter");
|
|
|
|
|
|
|
|
BridgeStructs.AssetMeta memory meta = parseAssetMeta(vm.payload);
|
|
|
|
return _updateWrapped(meta, vm.sequence);
|
|
|
|
}
|
|
|
|
|
|
|
|
function _updateWrapped(BridgeStructs.AssetMeta memory meta, uint64 sequence) internal returns (address token) {
|
|
|
|
address wrapped = wrappedAsset(meta.tokenChain, meta.tokenAddress);
|
|
|
|
require(wrapped != address(0), "wrapped asset does not exists");
|
|
|
|
|
|
|
|
// Update metadata
|
|
|
|
TokenImplementation(wrapped).updateDetails(bytes32ToString(meta.name), bytes32ToString(meta.symbol), sequence);
|
|
|
|
|
|
|
|
return wrapped;
|
|
|
|
}
|
|
|
|
|
2022-09-07 13:22:27 -07:00
|
|
|
function createWrapped(bytes memory encodedVm) external returns (address token) {
|
2021-06-21 07:04:07 -07:00
|
|
|
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
|
|
|
|
|
|
|
|
require(valid, reason);
|
|
|
|
require(verifyBridgeVM(vm), "invalid emitter");
|
|
|
|
|
|
|
|
BridgeStructs.AssetMeta memory meta = parseAssetMeta(vm.payload);
|
2021-09-01 04:13:51 -07:00
|
|
|
return _createWrapped(meta, vm.sequence);
|
2021-06-21 07:04:07 -07:00
|
|
|
}
|
|
|
|
|
2021-09-01 04:13:51 -07:00
|
|
|
// Creates a wrapped asset using AssetMeta
|
|
|
|
function _createWrapped(BridgeStructs.AssetMeta memory meta, uint64 sequence) internal returns (address token) {
|
2021-06-21 07:04:07 -07:00
|
|
|
require(meta.tokenChain != chainId(), "can only wrap tokens from foreign chains");
|
|
|
|
require(wrappedAsset(meta.tokenChain, meta.tokenAddress) == address(0), "wrapped asset already exists");
|
|
|
|
|
|
|
|
// initialize the TokenImplementation
|
|
|
|
bytes memory initialisationArgs = abi.encodeWithSelector(
|
|
|
|
TokenImplementation.initialize.selector,
|
|
|
|
bytes32ToString(meta.name),
|
|
|
|
bytes32ToString(meta.symbol),
|
|
|
|
meta.decimals,
|
2021-09-01 04:13:51 -07:00
|
|
|
sequence,
|
2021-06-21 07:04:07 -07:00
|
|
|
|
|
|
|
address(this),
|
|
|
|
|
|
|
|
meta.tokenChain,
|
|
|
|
meta.tokenAddress
|
|
|
|
);
|
|
|
|
|
|
|
|
// initialize the BeaconProxy
|
|
|
|
bytes memory constructorArgs = abi.encode(address(this), initialisationArgs);
|
|
|
|
|
|
|
|
// deployment code
|
|
|
|
bytes memory bytecode = abi.encodePacked(type(BridgeToken).creationCode, constructorArgs);
|
|
|
|
|
|
|
|
bytes32 salt = keccak256(abi.encodePacked(meta.tokenChain, meta.tokenAddress));
|
|
|
|
|
|
|
|
assembly {
|
|
|
|
token := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
|
|
|
|
|
|
|
|
if iszero(extcodesize(token)) {
|
|
|
|
revert(0, 0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setWrappedAsset(meta.tokenChain, meta.tokenAddress, token);
|
|
|
|
}
|
|
|
|
|
2022-05-23 15:06:33 -07:00
|
|
|
/*
|
|
|
|
* @notice Complete a contract-controlled transfer of an ERC20 token.
|
|
|
|
*
|
|
|
|
* @dev The transaction can only be redeemed by the recipient, typically a
|
|
|
|
* contract.
|
|
|
|
*
|
|
|
|
* @param encodedVm A byte array containing a VAA signed by the guardians.
|
|
|
|
*
|
|
|
|
* @return The byte array representing a BridgeStructs.TransferWithPayload.
|
|
|
|
*/
|
|
|
|
function completeTransferWithPayload(bytes memory encodedVm) public returns (bytes memory) {
|
|
|
|
return _completeTransfer(encodedVm, false);
|
2021-12-22 19:48:52 -08:00
|
|
|
}
|
|
|
|
|
2022-05-23 15:06:33 -07:00
|
|
|
/*
|
|
|
|
* @notice Complete a contract-controlled transfer of WETH, and unwrap to ETH.
|
|
|
|
*
|
|
|
|
* @dev The transaction can only be redeemed by the recipient, typically a
|
|
|
|
* contract.
|
|
|
|
*
|
|
|
|
* @param encodedVm A byte array containing a VAA signed by the guardians.
|
|
|
|
*
|
|
|
|
* @return The byte array representing a BridgeStructs.TransferWithPayload.
|
|
|
|
*/
|
|
|
|
function completeTransferAndUnwrapETHWithPayload(bytes memory encodedVm) public returns (bytes memory) {
|
|
|
|
return _completeTransfer(encodedVm, true);
|
2021-12-22 19:48:52 -08:00
|
|
|
}
|
|
|
|
|
2022-05-23 15:06:33 -07:00
|
|
|
/*
|
|
|
|
* @notice Complete a transfer of an ERC20 token.
|
|
|
|
*
|
|
|
|
* @dev The msg.sender gets paid the associated fee.
|
|
|
|
*
|
|
|
|
* @param encodedVm A byte array containing a VAA signed by the guardians.
|
|
|
|
*/
|
2021-06-21 07:04:07 -07:00
|
|
|
function completeTransfer(bytes memory encodedVm) public {
|
2022-05-23 15:06:33 -07:00
|
|
|
_completeTransfer(encodedVm, false);
|
2021-06-21 07:04:07 -07:00
|
|
|
}
|
|
|
|
|
2022-05-23 15:06:33 -07:00
|
|
|
/*
|
|
|
|
* @notice Complete a transfer of WETH and unwrap to eth.
|
|
|
|
*
|
|
|
|
* @dev The msg.sender gets paid the associated fee.
|
|
|
|
*
|
|
|
|
* @param encodedVm A byte array containing a VAA signed by the guardians.
|
|
|
|
*/
|
2021-06-21 07:04:07 -07:00
|
|
|
function completeTransferAndUnwrapETH(bytes memory encodedVm) public {
|
2022-05-23 15:06:33 -07:00
|
|
|
_completeTransfer(encodedVm, true);
|
2021-06-21 07:04:07 -07:00
|
|
|
}
|
|
|
|
|
2022-08-23 11:41:03 -07:00
|
|
|
/*
|
|
|
|
* @dev Truncate a 32 byte array to a 20 byte address.
|
|
|
|
* Reverts if the array contains non-0 bytes in the first 12 bytes.
|
|
|
|
*
|
|
|
|
* @param bytes32 bytes The 32 byte array to be converted.
|
|
|
|
*/
|
|
|
|
function _truncateAddress(bytes32 b) internal pure returns (address) {
|
|
|
|
require(bytes12(b) == 0, "invalid EVM address");
|
|
|
|
return address(uint160(uint256(b)));
|
|
|
|
}
|
|
|
|
|
2021-06-21 07:04:07 -07:00
|
|
|
// Execute a Transfer message
|
2022-09-07 13:22:27 -07:00
|
|
|
function _completeTransfer(bytes memory encodedVm, bool unwrapWETH) internal returns (bytes memory) {
|
2021-06-21 07:04:07 -07:00
|
|
|
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
|
|
|
|
|
|
|
|
require(valid, reason);
|
|
|
|
require(verifyBridgeVM(vm), "invalid emitter");
|
|
|
|
|
2022-05-23 15:06:33 -07:00
|
|
|
BridgeStructs.Transfer memory transfer = _parseTransferCommon(vm.payload);
|
2021-06-21 07:04:07 -07:00
|
|
|
|
2021-12-22 19:48:52 -08:00
|
|
|
// payload 3 must be redeemed by the designated proxy contract
|
2022-08-23 11:41:03 -07:00
|
|
|
address transferRecipient = _truncateAddress(transfer.to);
|
2021-12-22 19:48:52 -08:00
|
|
|
if (transfer.payloadID == 3) {
|
|
|
|
require(msg.sender == transferRecipient, "invalid sender");
|
|
|
|
}
|
|
|
|
|
2021-06-21 07:04:07 -07:00
|
|
|
require(!isTransferCompleted(vm.hash), "transfer already completed");
|
|
|
|
setTransferCompleted(vm.hash);
|
|
|
|
|
|
|
|
require(transfer.toChain == chainId(), "invalid target chain");
|
|
|
|
|
|
|
|
IERC20 transferToken;
|
2021-09-01 04:13:51 -07:00
|
|
|
if (transfer.tokenChain == chainId()) {
|
2022-08-23 11:41:03 -07:00
|
|
|
transferToken = IERC20(_truncateAddress(transfer.tokenAddress));
|
2021-07-27 02:36:49 -07:00
|
|
|
|
|
|
|
// track outstanding token amounts
|
|
|
|
bridgedIn(address(transferToken), transfer.amount);
|
2021-06-21 07:04:07 -07:00
|
|
|
} else {
|
|
|
|
address wrapped = wrappedAsset(transfer.tokenChain, transfer.tokenAddress);
|
|
|
|
require(wrapped != address(0), "no wrapper for this token created yet");
|
|
|
|
|
|
|
|
transferToken = IERC20(wrapped);
|
|
|
|
}
|
|
|
|
|
2021-07-27 02:36:49 -07:00
|
|
|
require(unwrapWETH == false || address(transferToken) == address(WETH()), "invalid token, can only unwrap WETH");
|
|
|
|
|
|
|
|
// query decimals
|
|
|
|
(,bytes memory queriedDecimals) = address(transferToken).staticcall(abi.encodeWithSignature("decimals()"));
|
|
|
|
uint8 decimals = abi.decode(queriedDecimals, (uint8));
|
|
|
|
|
|
|
|
// adjust decimals
|
2021-10-22 05:32:10 -07:00
|
|
|
uint256 nativeAmount = deNormalizeAmount(transfer.amount, decimals);
|
|
|
|
uint256 nativeFee = deNormalizeAmount(transfer.fee, decimals);
|
2021-07-27 02:36:49 -07:00
|
|
|
|
|
|
|
// transfer fee to arbiter
|
2022-05-23 15:06:33 -07:00
|
|
|
if (nativeFee > 0 && transferRecipient != msg.sender) {
|
2021-07-27 02:36:49 -07:00
|
|
|
require(nativeFee <= nativeAmount, "fee higher than transferred amount");
|
2021-06-21 07:04:07 -07:00
|
|
|
|
|
|
|
if (unwrapWETH) {
|
2021-07-27 02:36:49 -07:00
|
|
|
WETH().withdraw(nativeFee);
|
|
|
|
|
2022-05-23 15:06:33 -07:00
|
|
|
payable(msg.sender).transfer(nativeFee);
|
2021-06-21 07:04:07 -07:00
|
|
|
} else {
|
2021-09-01 04:13:51 -07:00
|
|
|
if (transfer.tokenChain != chainId()) {
|
2021-08-17 09:52:46 -07:00
|
|
|
// mint wrapped asset
|
2022-05-23 15:06:33 -07:00
|
|
|
TokenImplementation(address(transferToken)).mint(msg.sender, nativeFee);
|
2021-09-01 04:13:51 -07:00
|
|
|
} else {
|
2022-05-23 15:06:33 -07:00
|
|
|
SafeERC20.safeTransfer(transferToken, msg.sender, nativeFee);
|
2021-08-17 09:52:46 -07:00
|
|
|
}
|
2021-06-21 07:04:07 -07:00
|
|
|
}
|
2021-12-22 19:48:52 -08:00
|
|
|
} else {
|
|
|
|
// set fee to zero in case transferRecipient == feeRecipient
|
|
|
|
nativeFee = 0;
|
2021-06-21 07:04:07 -07:00
|
|
|
}
|
|
|
|
|
2021-07-27 02:36:49 -07:00
|
|
|
// transfer bridged amount to recipient
|
|
|
|
uint transferAmount = nativeAmount - nativeFee;
|
2021-06-21 07:04:07 -07:00
|
|
|
|
|
|
|
if (unwrapWETH) {
|
|
|
|
WETH().withdraw(transferAmount);
|
2021-07-27 02:36:49 -07:00
|
|
|
|
|
|
|
payable(transferRecipient).transfer(transferAmount);
|
2021-06-21 07:04:07 -07:00
|
|
|
} else {
|
2021-09-01 04:13:51 -07:00
|
|
|
if (transfer.tokenChain != chainId()) {
|
2021-08-17 09:52:46 -07:00
|
|
|
// mint wrapped asset
|
|
|
|
TokenImplementation(address(transferToken)).mint(transferRecipient, transferAmount);
|
2021-09-01 04:13:51 -07:00
|
|
|
} else {
|
2021-08-17 09:52:46 -07:00
|
|
|
SafeERC20.safeTransfer(transferToken, transferRecipient, transferAmount);
|
|
|
|
}
|
2021-06-21 07:04:07 -07:00
|
|
|
}
|
2021-12-22 19:48:52 -08:00
|
|
|
|
|
|
|
return vm.payload;
|
2021-06-21 07:04:07 -07:00
|
|
|
}
|
|
|
|
|
2021-07-27 02:36:49 -07:00
|
|
|
function bridgeOut(address token, uint normalizedAmount) internal {
|
|
|
|
uint outstanding = outstandingBridged(token);
|
|
|
|
require(outstanding + normalizedAmount <= type(uint64).max, "transfer exceeds max outstanding bridged token amount");
|
|
|
|
setOutstandingBridged(token, outstanding + normalizedAmount);
|
|
|
|
}
|
|
|
|
|
|
|
|
function bridgedIn(address token, uint normalizedAmount) internal {
|
|
|
|
setOutstandingBridged(token, outstandingBridged(token) - normalizedAmount);
|
|
|
|
}
|
|
|
|
|
2022-09-09 06:06:04 -07:00
|
|
|
function verifyBridgeVM(IWormhole.VM memory vm) internal view returns (bool){
|
2022-09-09 07:47:00 -07:00
|
|
|
require(!isFork(), "invalid fork");
|
2022-08-25 13:47:02 -07:00
|
|
|
return bridgeContracts(vm.emitterChainId) == vm.emitterAddress;
|
2021-06-21 07:04:07 -07:00
|
|
|
}
|
|
|
|
|
2021-09-01 04:13:51 -07:00
|
|
|
function encodeAssetMeta(BridgeStructs.AssetMeta memory meta) public pure returns (bytes memory encoded) {
|
2021-06-21 07:04:07 -07:00
|
|
|
encoded = abi.encodePacked(
|
|
|
|
meta.payloadID,
|
|
|
|
meta.tokenAddress,
|
|
|
|
meta.tokenChain,
|
|
|
|
meta.decimals,
|
|
|
|
meta.symbol,
|
|
|
|
meta.name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function encodeTransfer(BridgeStructs.Transfer memory transfer) public pure returns (bytes memory encoded) {
|
|
|
|
encoded = abi.encodePacked(
|
|
|
|
transfer.payloadID,
|
|
|
|
transfer.amount,
|
|
|
|
transfer.tokenAddress,
|
|
|
|
transfer.tokenChain,
|
|
|
|
transfer.to,
|
|
|
|
transfer.toChain,
|
|
|
|
transfer.fee
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-12-22 19:48:52 -08:00
|
|
|
function encodeTransferWithPayload(BridgeStructs.TransferWithPayload memory transfer) public pure returns (bytes memory encoded) {
|
|
|
|
encoded = abi.encodePacked(
|
|
|
|
transfer.payloadID,
|
|
|
|
transfer.amount,
|
|
|
|
transfer.tokenAddress,
|
|
|
|
transfer.tokenChain,
|
|
|
|
transfer.to,
|
|
|
|
transfer.toChain,
|
2022-05-23 15:06:33 -07:00
|
|
|
transfer.fromAddress,
|
2021-12-22 19:48:52 -08:00
|
|
|
transfer.payload
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-05-23 15:06:33 -07:00
|
|
|
function parsePayloadID(bytes memory encoded) public pure returns (uint8 payloadID) {
|
|
|
|
payloadID = encoded.toUint8(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* @dev Parse a token metadata attestation (payload id 2)
|
|
|
|
*/
|
2021-09-01 04:13:51 -07:00
|
|
|
function parseAssetMeta(bytes memory encoded) public pure returns (BridgeStructs.AssetMeta memory meta) {
|
2021-06-21 07:04:07 -07:00
|
|
|
uint index = 0;
|
|
|
|
|
|
|
|
meta.payloadID = encoded.toUint8(index);
|
|
|
|
index += 1;
|
|
|
|
|
|
|
|
require(meta.payloadID == 2, "invalid AssetMeta");
|
|
|
|
|
|
|
|
meta.tokenAddress = encoded.toBytes32(index);
|
|
|
|
index += 32;
|
|
|
|
|
|
|
|
meta.tokenChain = encoded.toUint16(index);
|
|
|
|
index += 2;
|
|
|
|
|
|
|
|
meta.decimals = encoded.toUint8(index);
|
|
|
|
index += 1;
|
|
|
|
|
|
|
|
meta.symbol = encoded.toBytes32(index);
|
|
|
|
index += 32;
|
|
|
|
|
|
|
|
meta.name = encoded.toBytes32(index);
|
|
|
|
index += 32;
|
|
|
|
|
|
|
|
require(encoded.length == index, "invalid AssetMeta");
|
|
|
|
}
|
|
|
|
|
2022-05-23 15:06:33 -07:00
|
|
|
/*
|
|
|
|
* @dev Parse a token transfer (payload id 1).
|
|
|
|
*
|
|
|
|
* @params encoded The byte array corresponding to the token transfer (not
|
|
|
|
* the whole VAA, only the payload)
|
|
|
|
*/
|
2021-09-01 04:13:51 -07:00
|
|
|
function parseTransfer(bytes memory encoded) public pure returns (BridgeStructs.Transfer memory transfer) {
|
2021-06-21 07:04:07 -07:00
|
|
|
uint index = 0;
|
|
|
|
|
|
|
|
transfer.payloadID = encoded.toUint8(index);
|
|
|
|
index += 1;
|
|
|
|
|
2022-05-23 15:06:33 -07:00
|
|
|
require(transfer.payloadID == 1, "invalid Transfer");
|
2021-06-21 07:04:07 -07:00
|
|
|
|
|
|
|
transfer.amount = encoded.toUint256(index);
|
|
|
|
index += 32;
|
|
|
|
|
|
|
|
transfer.tokenAddress = encoded.toBytes32(index);
|
|
|
|
index += 32;
|
|
|
|
|
|
|
|
transfer.tokenChain = encoded.toUint16(index);
|
|
|
|
index += 2;
|
|
|
|
|
|
|
|
transfer.to = encoded.toBytes32(index);
|
|
|
|
index += 32;
|
|
|
|
|
|
|
|
transfer.toChain = encoded.toUint16(index);
|
|
|
|
index += 2;
|
|
|
|
|
|
|
|
transfer.fee = encoded.toUint256(index);
|
|
|
|
index += 32;
|
|
|
|
|
2022-05-23 15:06:33 -07:00
|
|
|
require(encoded.length == index, "invalid Transfer");
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* @dev Parse a token transfer with payload (payload id 3).
|
|
|
|
*
|
|
|
|
* @params encoded The byte array corresponding to the token transfer (not
|
|
|
|
* the whole VAA, only the payload)
|
|
|
|
*/
|
|
|
|
function parseTransferWithPayload(bytes memory encoded) public pure returns (BridgeStructs.TransferWithPayload memory transfer) {
|
|
|
|
uint index = 0;
|
|
|
|
|
|
|
|
transfer.payloadID = encoded.toUint8(index);
|
|
|
|
index += 1;
|
|
|
|
|
|
|
|
require(transfer.payloadID == 3, "invalid Transfer");
|
|
|
|
|
|
|
|
transfer.amount = encoded.toUint256(index);
|
|
|
|
index += 32;
|
|
|
|
|
|
|
|
transfer.tokenAddress = encoded.toBytes32(index);
|
|
|
|
index += 32;
|
|
|
|
|
|
|
|
transfer.tokenChain = encoded.toUint16(index);
|
|
|
|
index += 2;
|
|
|
|
|
|
|
|
transfer.to = encoded.toBytes32(index);
|
|
|
|
index += 32;
|
|
|
|
|
|
|
|
transfer.toChain = encoded.toUint16(index);
|
|
|
|
index += 2;
|
|
|
|
|
|
|
|
transfer.fromAddress = encoded.toBytes32(index);
|
|
|
|
index += 32;
|
|
|
|
|
|
|
|
transfer.payload = encoded.slice(index, encoded.length - index);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* @dev Parses either a type 1 transfer or a type 3 transfer ("transfer with
|
|
|
|
* payload") as a Transfer struct. The fee is set to 0 for type 3
|
|
|
|
* transfers, since they have no fees associated with them.
|
|
|
|
*
|
|
|
|
* The sole purpose of this function is to get around the local
|
|
|
|
* variable count limitation in _completeTransfer.
|
|
|
|
*/
|
|
|
|
function _parseTransferCommon(bytes memory encoded) public pure returns (BridgeStructs.Transfer memory transfer) {
|
|
|
|
uint8 payloadID = parsePayloadID(encoded);
|
|
|
|
|
|
|
|
if (payloadID == 1) {
|
|
|
|
transfer = parseTransfer(encoded);
|
|
|
|
} else if (payloadID == 3) {
|
|
|
|
BridgeStructs.TransferWithPayload memory t = parseTransferWithPayload(encoded);
|
|
|
|
transfer.payloadID = 3;
|
|
|
|
transfer.amount = t.amount;
|
|
|
|
transfer.tokenAddress = t.tokenAddress;
|
|
|
|
transfer.tokenChain = t.tokenChain;
|
|
|
|
transfer.to = t.to;
|
|
|
|
transfer.toChain = t.toChain;
|
|
|
|
// Type 3 payloads don't have fees.
|
|
|
|
transfer.fee = 0;
|
|
|
|
} else {
|
|
|
|
revert("Invalid payload id");
|
|
|
|
}
|
2021-06-21 07:04:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function bytes32ToString(bytes32 input) internal pure returns (string memory) {
|
|
|
|
uint256 i;
|
2021-09-01 04:13:51 -07:00
|
|
|
while (i < 32 && input[i] != 0) {
|
2021-06-21 07:04:07 -07:00
|
|
|
i++;
|
|
|
|
}
|
|
|
|
bytes memory array = new bytes(i);
|
|
|
|
for (uint c = 0; c < i; c++) {
|
|
|
|
array[c] = input[c];
|
|
|
|
}
|
|
|
|
return string(array);
|
|
|
|
}
|
|
|
|
|
|
|
|
// we need to accept ETH sends to unwrap WETH
|
|
|
|
receive() external payable {}
|
2021-12-22 19:48:52 -08:00
|
|
|
}
|