// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastValue;
// Update the index for the moved value
set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface of the LzApp that functions not exist in the @layerzerolabs package
*/
interface ILzApp {
function minDstGasLookup(uint16 _dstChainId, uint16 _type) external view returns (uint256 _minGasLimit);
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.14;
/*
░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝
*
* MIT License
* ===========
*
* Copyright (c) 2020 WooTrade
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/// @title WOOFi cross fee.
interface ISgInfo {
/* ----- State Variables ----- */
function sgRouter() external view returns (address sgRouter);
function sgChainIdLocal() external view returns (uint16 sgChainIdLocal);
function sgPoolIds(uint16 _chainId, address _addr) external view returns (uint256 sgPoolId);
function sgETHs(uint16 _chainId) external view returns (address sgETH);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IStargateEthVault {
function deposit() external payable;
function transfer(address to, uint256 value) external returns (bool);
function withdraw(uint256) external;
function approve(address guy, uint256 wad) external returns (bool);
function transferFrom(
address src,
address dst,
uint256 wad
) external returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IStargateRouter {
struct lzTxObj {
uint256 dstGasForCall;
uint256 dstNativeAmount;
bytes dstNativeAddr;
}
function addLiquidity(
uint256 _poolId,
uint256 _amountLD,
address _to
) external;
function swap(
uint16 _dstChainId,
uint256 _srcPoolId,
uint256 _dstPoolId,
address payable _refundAddress,
uint256 _amountLD,
uint256 _minAmountLD,
lzTxObj memory _lzTxParams,
bytes calldata _to,
bytes calldata _payload
) external payable;
function redeemRemote(
uint16 _dstChainId,
uint256 _srcPoolId,
uint256 _dstPoolId,
address payable _refundAddress,
uint256 _amountLP,
uint256 _minAmountLD,
bytes calldata _to,
lzTxObj memory _lzTxParams
) external payable;
function instantRedeemLocal(
uint16 _srcPoolId,
uint256 _amountLP,
address _to
) external returns (uint256);
function redeemLocal(
uint16 _dstChainId,
uint256 _srcPoolId,
uint256 _dstPoolId,
address payable _refundAddress,
uint256 _amountLP,
bytes calldata _to,
lzTxObj memory _lzTxParams
) external payable;
function sendCredits(
uint16 _dstChainId,
uint256 _srcPoolId,
uint256 _dstPoolId,
address payable _refundAddress
) external payable;
function quoteLayerZeroFee(
uint16 _dstChainId,
uint8 _functionType,
bytes calldata _toAddress,
bytes calldata _transferAndCallPayload,
lzTxObj memory _lzTxParams
) external view returns (uint256, uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Wrapped ETH.
interface IWETH {
/// @dev Deposit ETH into WETH
function deposit() external payable;
/// @dev Transfer WETH to receiver
/// @param to address of WETH receiver
/// @param value amount of WETH to transfer
/// @return get true when succeed, else false
function transfer(address to, uint256 value) external returns (bool);
/// @dev Withdraw WETH to ETH
function withdraw(uint256) external;
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.14;
/*
░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝
*
* MIT License
* ===========
*
* Copyright (c) 2020 WooTrade
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/// @title WOOFi cross chain router interface (version 3, supporting WOOFi and 1inch).
/// @notice functions to interface with WOOFi cross chain swap, and 1inch for local swap
interface IWooCrossChainRouterV3 {
/* ----- Structs ----- */
struct SrcInfos {
address fromToken;
address bridgeToken;
uint256 fromAmount;
uint256 minBridgeAmount;
}
struct Src1inch {
address swapRouter;
bytes data;
}
struct DstInfos {
uint16 chainId;
address toToken;
address bridgeToken;
uint256 minToAmount;
uint256 airdropNativeAmount;
uint256 dstGasForCall;
}
struct Dst1inch {
address swapRouter;
bytes data;
}
/* ----- Events ----- */
event WooCrossSwapOnSrcChain(
uint256 indexed refId,
address indexed sender,
address indexed to,
address fromToken,
uint256 fromAmount,
address bridgeToken,
uint256 minBridgeAmount,
uint256 realBridgeAmount,
uint8 swapType,
uint256 fee
);
event WooCrossSwapOnDstChain(
uint256 indexed refId,
address indexed sender,
address indexed to,
address bridgedToken,
uint256 bridgedAmount,
address toToken,
address realToToken,
uint256 minToAmount,
uint256 realToAmount,
uint8 swapType,
uint256 fee
);
/* ----- State Variables ----- */
function bridgeSlippage() external view returns (uint256);
function wooCrossRouters(uint16 chainId) external view returns (address wooCrossRouter);
/* ----- Functions ----- */
function crossSwap(
uint256 refId,
address payable to,
SrcInfos memory srcInfos,
DstInfos calldata dstInfos,
Src1inch calldata src1inch,
Dst1inch calldata dst1inch
) external payable;
function sgReceive(
uint16 srcChainId,
bytes memory srcAddress,
uint256 nonce,
address bridgedToken,
uint256 amountLD,
bytes memory payload
) external;
function quoteLayerZeroFee(
uint256 refId,
address to,
DstInfos calldata dstInfos,
Dst1inch calldata dst1inch
) external view returns (uint256 nativeAmount, uint256 zroAmount);
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.14;
/*
░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝
*
* MIT License
* ===========
*
* Copyright (c) 2020 WooTrade
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/// @title Woo private pool for swap.
/// @notice Use this contract to directly interfact with woo's synthetic proactive
/// marketing making pool.
/// @author woo.network
interface IWooPPV2 {
/* ----- Events ----- */
event Deposit(address indexed token, address indexed sender, uint256 amount);
event Withdraw(address indexed token, address indexed receiver, uint256 amount);
event Migrate(address indexed token, address indexed receiver, uint256 amount);
event AdminUpdated(address indexed addr, bool flag);
event FeeAddrUpdated(address indexed newFeeAddr);
event WooracleUpdated(address indexed newWooracle);
event WooSwap(
address indexed fromToken,
address indexed toToken,
uint256 fromAmount,
uint256 toAmount,
address from,
address indexed to,
address rebateTo,
uint256 swapVol,
uint256 swapFee
);
/* ----- External Functions ----- */
/// @notice The quote token address (immutable).
/// @return address of quote token
function quoteToken() external view returns (address);
/// @notice Gets the pool size of the specified token (swap liquidity).
/// @param token the token address
/// @return the pool size
function poolSize(address token) external view returns (uint256);
/// @notice Query the amount to swap `fromToken` to `toToken`, without checking the pool reserve balance.
/// @param fromToken the from token
/// @param toToken the to token
/// @param fromAmount the amount of `fromToken` to swap
/// @return toAmount the swapped amount of `toToken`
function tryQuery(
address fromToken,
address toToken,
uint256 fromAmount
) external view returns (uint256 toAmount);
/// @notice Query the amount to swap `fromToken` to `toToken`, with checking the pool reserve balance.
/// @dev tx reverts when 'toToken' balance is insufficient.
/// @param fromToken the from token
/// @param toToken the to token
/// @param fromAmount the amount of `fromToken` to swap
/// @return toAmount the swapped amount of `toToken`
function query(
address fromToken,
address toToken,
uint256 fromAmount
) external view returns (uint256 toAmount);
/// @notice Swap `fromToken` to `toToken`.
/// @param fromToken the from token
/// @param toToken the to token
/// @param fromAmount the amount of `fromToken` to swap
/// @param minToAmount the minimum amount of `toToken` to receive
/// @param to the destination address
/// @param rebateTo the rebate address (optional, can be address ZERO)
/// @return realToAmount the amount of toToken to receive
function swap(
address fromToken,
address toToken,
uint256 fromAmount,
uint256 minToAmount,
address to,
address rebateTo
) external returns (uint256 realToAmount);
/// @notice Deposit the specified token into the liquidity pool of WooPPV2.
/// @param token the token to deposit
/// @param amount the deposit amount
function deposit(address token, uint256 amount) external;
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.14;
/*
░██╗░░░░░░░██╗░█████╗░░█████╗░░░░░░░███████╗██╗
░██║░░██╗░░██║██╔══██╗██╔══██╗░░░░░░██╔════╝██║
░╚██╗████╗██╔╝██║░░██║██║░░██║█████╗█████╗░░██║
░░████╔═████║░██║░░██║██║░░██║╚════╝██╔══╝░░██║
░░╚██╔╝░╚██╔╝░╚█████╔╝╚█████╔╝░░░░░░██║░░░░░██║
░░░╚═╝░░░╚═╝░░░╚════╝░░╚════╝░░░░░░░╚═╝░░░░░╚═╝
*
* MIT License
* ===========
*
* Copyright (c) 2020 WooTrade
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import "../interfaces/IWooPPV2.sol";
/// @title Woo router interface (version 2)
/// @notice functions to interface with WooFi swap
interface IWooRouterV2 {
/* ----- Type declarations ----- */
enum SwapType {
WooSwap,
DodoSwap
}
/* ----- Events ----- */
event WooRouterSwap(
SwapType swapType,
address indexed fromToken,
address indexed toToken,
uint256 fromAmount,
uint256 toAmount,
address from,
address indexed to,
address rebateTo
);
event WooPoolChanged(address newPool);
/* ----- Router properties ----- */
function WETH() external view returns (address);
function wooPool() external view returns (IWooPPV2);
/* ----- Main query & swap APIs ----- */
/// @notice query the amount to swap fromToken -> toToken
/// @param fromToken the from token
/// @param toToken the to token
/// @param fromAmount the amount of fromToken to swap
/// @return toAmount the predicted amount to receive
function querySwap(
address fromToken,
address toToken,
uint256 fromAmount
) external view returns (uint256 toAmount);
/// @notice query the amount to swap fromToken -> toToken,
/// WITHOUT checking the reserve balance; so it
/// always returns the quoted amount (for reference).
/// @param fromToken the from token
/// @param toToken the to token
/// @param fromAmount the amount of fromToken to swap
/// @return toAmount the predicted amount to receive
function tryQuerySwap(
address fromToken,
address toToken,
uint256 fromAmount
) external view returns (uint256 toAmount);
/// @notice Swap `fromToken` to `toToken`.
/// @param fromToken the from token
/// @param toToken the to token
/// @param fromAmount the amount of `fromToken` to swap
/// @param minToAmount the minimum amount of `toToken` to receive
/// @param to the destination address
/// @param rebateTo the rebate address (optional, can be 0)
/// @return realToAmount the amount of toToken to receive
function swap(
address fromToken,
address toToken,
uint256 fromAmount,
uint256 minToAmount,
address payable to,
address rebateTo
) external payable returns (uint256 realToAmount);
/* ----- 3rd party DEX swap ----- */
/// @notice swap fromToken -> toToken via an external 3rd swap
/// @param approveTarget the contract address for token transfer approval
/// @param swapTarget the contract address for swap
/// @param fromToken the from token
/// @param toToken the to token
/// @param fromAmount the amount of fromToken to swap
/// @param minToAmount the min amount of swapped toToken
/// @param to the destination address
/// @param data call data for external call
function externalSwap(
address approveTarget,
address swapTarget,
address fromToken,
address toToken,
uint256 fromAmount,
uint256 minToAmount,
address payable to,
bytes calldata data
) external payable returns (uint256 realToAmount);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;
// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
function safeApprove(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('approve(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"TransferHelper::safeApprove: approve failed"
);
}
function safeTransfer(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"TransferHelper::safeTransfer: transfer failed"
);
}
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"TransferHelper::transferFrom: transferFrom failed"
);
}
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, "TransferHelper::safeTransferETH: ETH transfer failed");
}
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.14;
// OpenZeppelin Contracts
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
// Local Contracts
import {IWETH} from "../interfaces/IWETH.sol";
import {IWooCrossChainRouterV3} from "../interfaces/CrossChain/IWooCrossChainRouterV3.sol";
import {IWooRouterV2} from "../interfaces/IWooRouterV2.sol";
import {IStargateEthVault} from "../interfaces/Stargate/IStargateEthVault.sol";
import {IStargateRouter} from "../interfaces/Stargate/IStargateRouter.sol";
import {ILzApp} from "../interfaces/LayerZero/ILzApp.sol";
import {ISgInfo} from "../interfaces/CrossChain/ISgInfo.sol";
import {TransferHelper} from "../libraries/TransferHelper.sol";
/// @title cross chain router implementation, version 3.
/// @notice Router for stateless execution of cross chain swap against WOOFi or 1inch swap.
/// @custom:stargate-contracts https://stargateprotocol.gitbook.io/stargate/developers/contract-addresses/mainnet
contract WooCrossChainRouterV4 is IWooCrossChainRouterV3, Ownable, Pausable, ReentrancyGuard {
using EnumerableSet for EnumerableSet.AddressSet;
/* ----- Constants ----- */
address public constant ETH_PLACEHOLDER_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/* ----- Variables ----- */
IWooRouterV2 public wooRouter;
ISgInfo public sgInfo;
address public immutable weth;
address public feeAddr;
uint256 public bridgeSlippage; // 1 in 10000th: default 1%
uint16 public srcExternalFeeRate; // unit: 0.1 bps (1e6 = 100%, 25 = 2.5 bps)
uint16 public dstExternalFeeRate; // unit: 0.1 bps (1e6 = 100%, 25 = 2.5 bps)
uint256 public constant FEE_BASE = 1e5;
mapping(uint16 => address) public wooCrossRouters; // chainId => WooCrossChainRouterV3 address
receive() external payable {}
constructor(
address _weth,
address _wooRouter,
address _sgInfo
) {
weth = _weth;
wooRouter = IWooRouterV2(_wooRouter);
sgInfo = ISgInfo(_sgInfo);
bridgeSlippage = 100;
srcExternalFeeRate = 25;
dstExternalFeeRate = 25;
}
/* ----- Functions ----- */
function crossSwap(
uint256 refId,
address payable to,
SrcInfos memory srcInfos,
DstInfos calldata dstInfos,
Src1inch calldata src1inch,
Dst1inch calldata dst1inch
) external payable whenNotPaused nonReentrant {
require(srcInfos.fromToken != address(0), "WooCrossChainRouterV3: !srcInfos.fromToken");
require(
dstInfos.toToken != address(0) && dstInfos.toToken != sgInfo.sgETHs(dstInfos.chainId),
"WooCrossChainRouterV3: !dstInfos.toToken"
);
require(to != address(0), "WooCrossChainRouterV3: !to");
uint256 msgValue = msg.value;
uint256 bridgeAmount;
uint256 fee = 0;
{
// Step 1: transfer
if (srcInfos.fromToken == ETH_PLACEHOLDER_ADDR) {
require(srcInfos.fromAmount <= msgValue, "WooCrossChainRouterV3: !srcInfos.fromAmount");
srcInfos.fromToken = weth;
IWETH(weth).deposit{value: srcInfos.fromAmount}();
msgValue -= srcInfos.fromAmount;
} else {
TransferHelper.safeTransferFrom(srcInfos.fromToken, msg.sender, address(this), srcInfos.fromAmount);
}
// Step 2: local swap by 1inch router
if (srcInfos.fromToken != srcInfos.bridgeToken) {
TransferHelper.safeApprove(srcInfos.fromToken, address(wooRouter), srcInfos.fromAmount);
if (src1inch.swapRouter != address(0)) {
// external swap via 1inch
bridgeAmount = wooRouter.externalSwap(
src1inch.swapRouter,
src1inch.swapRouter,
srcInfos.fromToken,
srcInfos.bridgeToken,
srcInfos.fromAmount,
srcInfos.minBridgeAmount,
payable(address(this)),
src1inch.data
);
fee = (bridgeAmount * srcExternalFeeRate) / FEE_BASE;
} else {
// swap via WOOFi
bridgeAmount = wooRouter.swap(
srcInfos.fromToken,
srcInfos.bridgeToken,
srcInfos.fromAmount,
srcInfos.minBridgeAmount,
payable(address(this)),
to
);
}
} else {
require(
srcInfos.fromAmount == srcInfos.minBridgeAmount,
"WooCrossChainRouterV3: !srcInfos.minBridgeAmount"
);
bridgeAmount = srcInfos.fromAmount;
}
require(
bridgeAmount <= IERC20(srcInfos.bridgeToken).balanceOf(address(this)),
"WooCrossChainRouterV3: !bridgeAmount"
);
}
// Step 3: deduct the swap fee
bridgeAmount -= fee;
// Step 4: cross chain swap by StargateRouter
_bridgeByStargate(refId, to, msgValue, bridgeAmount, srcInfos, dstInfos, dst1inch);
emit WooCrossSwapOnSrcChain(
refId,
_msgSender(),
to,
srcInfos.fromToken,
srcInfos.fromAmount,
srcInfos.bridgeToken,
srcInfos.minBridgeAmount,
bridgeAmount,
src1inch.swapRouter == address(0) ? 0 : 1,
fee
);
}
function sgReceive(
uint16, // srcChainId
bytes memory, // srcAddress
uint256, // nonce
address bridgedToken,
uint256 amountLD,
bytes memory payload
) external {
require(msg.sender == sgInfo.sgRouter(), "WooCrossChainRouterV3: INVALID_CALLER");
// make sure the same order to abi.encode when decode payload
(uint256 refId, address to, address toToken, uint256 minToAmount, Dst1inch memory dst1inch) = abi.decode(
payload,
(uint256, address, address, uint256, Dst1inch)
);
// toToken won't be SGETH, and bridgedToken won't be ETH_PLACEHOLDER_ADDR
if (bridgedToken == sgInfo.sgETHs(sgInfo.sgChainIdLocal())) {
// bridgedToken is SGETH, received native token
_handleNativeReceived(refId, to, toToken, amountLD, minToAmount, dst1inch);
} else {
// bridgedToken is not SGETH, received ERC20 token
_handleERC20Received(refId, to, toToken, bridgedToken, amountLD, minToAmount, dst1inch);
}
}
function quoteLayerZeroFee(
uint256 refId,
address to,
DstInfos calldata dstInfos,
Dst1inch calldata dst1inch
) external view returns (uint256, uint256) {
bytes memory payload = abi.encode(refId, to, dstInfos.toToken, dstInfos.minToAmount, dst1inch);
IStargateRouter.lzTxObj memory obj = IStargateRouter.lzTxObj(
dstInfos.dstGasForCall,
dstInfos.airdropNativeAmount,
abi.encodePacked(to)
);
IStargateRouter stargateRouter = IStargateRouter(sgInfo.sgRouter());
return
stargateRouter.quoteLayerZeroFee(
dstInfos.chainId,
1, // https://stargateprotocol.gitbook.io/stargate/developers/function-types
obj.dstNativeAddr,
payload,
obj
);
}
/// @dev OKAY to be public method
function claimFee(address token) external nonReentrant {
require(feeAddr != address(0), "WooCrossChainRouterV3: !feeAddr");
uint256 amount = _generalBalanceOf(token, address(this));
if (amount > 0) {
if (token == ETH_PLACEHOLDER_ADDR) {
TransferHelper.safeTransferETH(feeAddr, amount);
} else {
TransferHelper.safeTransfer(token, feeAddr, amount);
}
}
}
function _bridgeByStargate(
uint256 refId,
address payable to,
uint256 msgValue,
uint256 bridgeAmount,
SrcInfos memory srcInfos,
DstInfos calldata dstInfos,
Dst1inch calldata dst1inch
) internal {
require(
sgInfo.sgPoolIds(sgInfo.sgChainIdLocal(), srcInfos.bridgeToken) > 0,
"WooCrossChainRouterV3: !srcInfos.bridgeToken"
);
require(
sgInfo.sgPoolIds(dstInfos.chainId, dstInfos.bridgeToken) > 0,
"WooCrossChainRouterV3: !dstInfos.bridgeToken"
);
bytes memory payload = abi.encode(refId, to, dstInfos.toToken, dstInfos.minToAmount, dst1inch);
uint256 dstMinBridgeAmount = (bridgeAmount * (10000 - bridgeSlippage)) / 10000;
bytes memory dstWooCrossChainRouter = abi.encodePacked(wooCrossRouters[dstInfos.chainId]);
IStargateRouter.lzTxObj memory obj = IStargateRouter.lzTxObj(
dstInfos.dstGasForCall,
dstInfos.airdropNativeAmount,
abi.encodePacked(to)
);
IStargateRouter stargateRouter = IStargateRouter(sgInfo.sgRouter());
if (srcInfos.bridgeToken == weth) {
IWETH(weth).withdraw(bridgeAmount);
msgValue += bridgeAmount;
} else {
TransferHelper.safeApprove(srcInfos.bridgeToken, sgInfo.sgRouter(), bridgeAmount);
}
stargateRouter.swap{value: msgValue}(
dstInfos.chainId, // dst chain id
sgInfo.sgPoolIds(sgInfo.sgChainIdLocal(), srcInfos.bridgeToken), // bridge token's pool id on src chain
sgInfo.sgPoolIds(dstInfos.chainId, dstInfos.bridgeToken), // bridge token's pool id on dst chain
payable(_msgSender()), // rebate address
bridgeAmount, // swap amount on src chain
dstMinBridgeAmount, // min received amount on dst chain
obj, // config: dstGasForCall, dstAirdropNativeAmount, dstReceiveAirdropNativeTokenAddr
dstWooCrossChainRouter, // smart contract to call on dst chain
payload // payload to piggyback
);
}
function _handleNativeReceived(
uint256 refId,
address to,
address toToken,
uint256 bridgedAmount,
uint256 minToAmount,
Dst1inch memory dst1inch
) internal {
address msgSender = _msgSender();
if (toToken == ETH_PLACEHOLDER_ADDR) {
// Directly transfer ETH
TransferHelper.safeTransferETH(to, bridgedAmount);
emit WooCrossSwapOnDstChain(
refId,
msgSender,
to,
weth,
bridgedAmount,
toToken,
ETH_PLACEHOLDER_ADDR,
minToAmount,
bridgedAmount,
dst1inch.swapRouter == address(0) ? 0 : 1,
0
);
return;
}
// Swap required!
IWETH(weth).deposit{value: bridgedAmount}();
if (dst1inch.swapRouter != address(0)) {
uint256 fee = (bridgedAmount * dstExternalFeeRate) / FEE_BASE;
uint256 swapAmount = bridgedAmount - fee;
TransferHelper.safeApprove(weth, address(wooRouter), swapAmount);
try
wooRouter.externalSwap(
dst1inch.swapRouter,
dst1inch.swapRouter,
weth,
toToken,
swapAmount,
minToAmount,
payable(to),
dst1inch.data
)
returns (uint256 realToAmount) {
emit WooCrossSwapOnDstChain(
refId,
msgSender,
to,
weth,
swapAmount,
toToken,
toToken,
minToAmount,
realToAmount,
dst1inch.swapRouter == address(0) ? 0 : 1,
fee
);
} catch {
TransferHelper.safeApprove(weth, address(wooRouter), 0);
TransferHelper.safeTransfer(weth, to, bridgedAmount);
emit WooCrossSwapOnDstChain(
refId,
msgSender,
to,
weth,
bridgedAmount,
toToken,
weth,
minToAmount,
bridgedAmount,
dst1inch.swapRouter == address(0) ? 0 : 1,
0
);
}
} else {
TransferHelper.safeApprove(weth, address(wooRouter), bridgedAmount);
try wooRouter.swap(weth, toToken, bridgedAmount, minToAmount, payable(to), to) returns (
uint256 realToAmount
) {
emit WooCrossSwapOnDstChain(
refId,
msgSender,
to,
weth,
bridgedAmount,
toToken,
toToken,
minToAmount,
realToAmount,
dst1inch.swapRouter == address(0) ? 0 : 1,
0
);
} catch {
TransferHelper.safeApprove(weth, address(wooRouter), 0);
TransferHelper.safeTransfer(weth, to, bridgedAmount);
emit WooCrossSwapOnDstChain(
refId,
msgSender,
to,
weth,
bridgedAmount,
toToken,
weth,
minToAmount,
bridgedAmount,
dst1inch.swapRouter == address(0) ? 0 : 1,
0
);
}
}
}
function _handleERC20Received(
uint256 refId,
address to,
address toToken,
address bridgedToken,
uint256 bridgedAmount,
uint256 minToAmount,
Dst1inch memory dst1inch
) internal {
address msgSender = _msgSender();
if (toToken == bridgedToken) {
TransferHelper.safeTransfer(bridgedToken, to, bridgedAmount);
emit WooCrossSwapOnDstChain(
refId,
msgSender,
to,
bridgedToken,
bridgedAmount,
toToken,
toToken,
minToAmount,
bridgedAmount,
dst1inch.swapRouter == address(0) ? 0 : 1,
0
);
} else {
// Deduct the external swap fee
uint256 fee = (bridgedAmount * dstExternalFeeRate) / FEE_BASE;
bridgedAmount -= fee;
TransferHelper.safeApprove(bridgedToken, address(wooRouter), bridgedAmount);
if (dst1inch.swapRouter != address(0)) {
try
wooRouter.externalSwap(
dst1inch.swapRouter,
dst1inch.swapRouter,
bridgedToken,
toToken,
bridgedAmount,
minToAmount,
payable(to),
dst1inch.data
)
returns (uint256 realToAmount) {
emit WooCrossSwapOnDstChain(
refId,
msgSender,
to,
bridgedToken,
bridgedAmount,
toToken,
toToken,
minToAmount,
realToAmount,
dst1inch.swapRouter == address(0) ? 0 : 1,
fee
);
} catch {
bridgedAmount += fee;
TransferHelper.safeTransfer(bridgedToken, to, bridgedAmount);
emit WooCrossSwapOnDstChain(
refId,
msgSender,
to,
bridgedToken,
bridgedAmount,
toToken,
bridgedToken,
minToAmount,
bridgedAmount,
dst1inch.swapRouter == address(0) ? 0 : 1,
0
);
}
} else {
try wooRouter.swap(bridgedToken, toToken, bridgedAmount, minToAmount, payable(to), to) returns (
uint256 realToAmount
) {
emit WooCrossSwapOnDstChain(
refId,
msgSender,
to,
bridgedToken,
bridgedAmount,
toToken,
toToken,
minToAmount,
realToAmount,
dst1inch.swapRouter == address(0) ? 0 : 1,
0
);
} catch {
TransferHelper.safeTransfer(bridgedToken, to, bridgedAmount);
emit WooCrossSwapOnDstChain(
refId,
msgSender,
to,
bridgedToken,
bridgedAmount,
toToken,
bridgedToken,
minToAmount,
bridgedAmount,
dst1inch.swapRouter == address(0) ? 0 : 1,
0
);
}
}
}
}
function _generalBalanceOf(address token, address who) internal view returns (uint256) {
return token == ETH_PLACEHOLDER_ADDR ? who.balance : IERC20(token).balanceOf(who);
}
/* ----- Owner & Admin Functions ----- */
function setFeeAddr(address _feeAddr) external onlyOwner {
feeAddr = _feeAddr;
}
function setWooRouter(address _wooRouter) external onlyOwner {
require(_wooRouter != address(0), "WooCrossChainRouterV3: !_wooRouter");
wooRouter = IWooRouterV2(_wooRouter);
}
function setBridgeSlippage(uint256 _bridgeSlippage) external onlyOwner {
require(_bridgeSlippage <= 10000, "WooCrossChainRouterV3: !_bridgeSlippage");
bridgeSlippage = _bridgeSlippage;
}
function setWooCrossRouter(uint16 _chainId, address _crossRouter) external onlyOwner {
require(_crossRouter != address(0), "WooCrossChainRouterV3: !_crossRouter");
wooCrossRouters[_chainId] = _crossRouter;
}
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
function inCaseTokenGotStuck(address stuckToken) external onlyOwner {
if (stuckToken == ETH_PLACEHOLDER_ADDR) {
TransferHelper.safeTransferETH(msg.sender, address(this).balance);
} else {
uint256 amount = IERC20(stuckToken).balanceOf(address(this));
TransferHelper.safeTransfer(stuckToken, msg.sender, amount);
}
}
}
{
"compilationTarget": {
"contracts/CrossChain/WooCrossChainRouterV4.sol": "WooCrossChainRouterV4"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 20000
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_weth","type":"address"},{"internalType":"address","name":"_wooRouter","type":"address"},{"internalType":"address","name":"_sgInfo","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refId","type":"uint256"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"address","name":"bridgedToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"bridgedAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"toToken","type":"address"},{"indexed":false,"internalType":"address","name":"realToToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"minToAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"realToAmount","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"swapType","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"WooCrossSwapOnDstChain","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refId","type":"uint256"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"address","name":"fromToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"fromAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"bridgeToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"minBridgeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"realBridgeAmount","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"swapType","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"WooCrossSwapOnSrcChain","type":"event"},{"inputs":[],"name":"ETH_PLACEHOLDER_ADDR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_BASE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bridgeSlippage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"claimFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"refId","type":"uint256"},{"internalType":"address payable","name":"to","type":"address"},{"components":[{"internalType":"address","name":"fromToken","type":"address"},{"internalType":"address","name":"bridgeToken","type":"address"},{"internalType":"uint256","name":"fromAmount","type":"uint256"},{"internalType":"uint256","name":"minBridgeAmount","type":"uint256"}],"internalType":"struct IWooCrossChainRouterV3.SrcInfos","name":"srcInfos","type":"tuple"},{"components":[{"internalType":"uint16","name":"chainId","type":"uint16"},{"internalType":"address","name":"toToken","type":"address"},{"internalType":"address","name":"bridgeToken","type":"address"},{"internalType":"uint256","name":"minToAmount","type":"uint256"},{"internalType":"uint256","name":"airdropNativeAmount","type":"uint256"},{"internalType":"uint256","name":"dstGasForCall","type":"uint256"}],"internalType":"struct IWooCrossChainRouterV3.DstInfos","name":"dstInfos","type":"tuple"},{"components":[{"internalType":"address","name":"swapRouter","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct IWooCrossChainRouterV3.Src1inch","name":"src1inch","type":"tuple"},{"components":[{"internalType":"address","name":"swapRouter","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct IWooCrossChainRouterV3.Dst1inch","name":"dst1inch","type":"tuple"}],"name":"crossSwap","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"dstExternalFeeRate","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeAddr","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"stuckToken","type":"address"}],"name":"inCaseTokenGotStuck","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"refId","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"components":[{"internalType":"uint16","name":"chainId","type":"uint16"},{"internalType":"address","name":"toToken","type":"address"},{"internalType":"address","name":"bridgeToken","type":"address"},{"internalType":"uint256","name":"minToAmount","type":"uint256"},{"internalType":"uint256","name":"airdropNativeAmount","type":"uint256"},{"internalType":"uint256","name":"dstGasForCall","type":"uint256"}],"internalType":"struct IWooCrossChainRouterV3.DstInfos","name":"dstInfos","type":"tuple"},{"components":[{"internalType":"address","name":"swapRouter","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct IWooCrossChainRouterV3.Dst1inch","name":"dst1inch","type":"tuple"}],"name":"quoteLayerZeroFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_bridgeSlippage","type":"uint256"}],"name":"setBridgeSlippage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_feeAddr","type":"address"}],"name":"setFeeAddr","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"_chainId","type":"uint16"},{"internalType":"address","name":"_crossRouter","type":"address"}],"name":"setWooCrossRouter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_wooRouter","type":"address"}],"name":"setWooRouter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"sgInfo","outputs":[{"internalType":"contract ISgInfo","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"","type":"uint16"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"bridgedToken","type":"address"},{"internalType":"uint256","name":"amountLD","type":"uint256"},{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"sgReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"srcExternalFeeRate","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"weth","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"","type":"uint16"}],"name":"wooCrossRouters","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"wooRouter","outputs":[{"internalType":"contract IWooRouterV2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]