// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
//SPDX-License-Identifier: BUSL
pragma solidity 0.8.10;
/*
* @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 GSN 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 _msgSenderContext() internal view virtual returns (address) {
return msg.sender;
}
function _msgDataContext() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
import "../interfaces/IGasStationRecipient.sol";
abstract contract GasStationRecipient is IGasStationRecipient {
/*
* Allowed Gas Station Contract for accept calls from
*/
address private _gasStation;
function isOwnGasStation(address addressToCheck) external view returns(bool) {
return _gasStation == addressToCheck;
}
function gasStation() external view returns (address) {
return _gasStation;
}
function _setGasStation(address newGasStation) internal {
require(newGasStation != address(0), "Invalid new gas station address");
_gasStation = newGasStation;
emit GasStationChanged(_gasStation);
}
/**
* return the sender of this call.
* if the call came through our trusted forwarder, then the real sender is appended as the last 20 bytes
* of the msg.data.
* otherwise, return `msg.sender`
* should be used in the contract anywhere instead of msg.sender
*/
function _msgSender() internal view returns (address ret) {
if (msg.data.length >= 20 && this.isOwnGasStation(msg.sender)) {
// At this point we know that the sender is a trusted forwarder,
// so we trust that the last bytes of msg.data are the verified sender address.
// extract sender address from the end of msg.data
assembly {
ret := shr(96,calldataload(sub(calldatasize(),20)))
}
} else {
ret = msg.sender;
}
}
/**
* return the msg.data of this call.
* if the call came through our trusted forwarder, then the real sender was appended as the last 20 bytes
* of the msg.data - so this method will strip those 20 bytes off.
* otherwise (if the call was made directly and not through the forwarder), return `msg.data`
* should be used in the contract instead of msg.data, where this difference matters.
*/
function _msgData() internal view returns (bytes calldata ret) {
if (msg.data.length >= 20 && this.isOwnGasStation(msg.sender)) {
return msg.data[0:msg.data.length-20];
} else {
return msg.data;
}
}
}
// SPDX-License-Identifier: BUSL
pragma solidity 0.8.10;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@plasma-fi/contracts/utils/GasStationRecipient.sol";
import "@plasma-fi/contracts/interfaces/ITokensApprover.sol";
import "./interfaces/IHyperDex.sol";
import "./packages/LibBytesV06.sol";
import "./packages/LibProxyRichErrors.sol";
import "./packages/Ownable.sol";
/// @title Plasma Finance proxy contract for 0x proxy
/// @dev A generic proxy contract which extracts a fee before delegation
contract HyperDexRouter is GasStationRecipient, Ownable {
using LibBytesV06 for bytes;
using SafeERC20 for IERC20;
// Native currency address (ETH - 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, MATIC - 0x0000000000000000000000000000000000001010)
address private nativeAddress;
address payable public beneficiary;
address payable public allowanceTarget;
IHyperDex public hyperDex;
ITokensApprover public approver;
uint256 public feeBeneficiary = 5; // 0.05%
uint256[4] public feeReferrals = [4, 3, 2, 1]; // 0.05%, 0.03%, 0.02%, 0.01%
event BeneficiaryChanged(address indexed beneficiary);
event AllowanceTargetChanged(address indexed allowanceTarget);
event HyperDexChanged(address indexed hyperDex);
event TokensApproverChanged(address indexed approver);
event FeePayment(address indexed recipient, address token, uint256 amount);
/// @dev Construct this contract and specify a fee beneficiary, 0x proxy contract address, and allowance target
constructor(address _nativeAddress, IHyperDex _hyperDex, address payable _allowanceTarget, address payable _beneficiary, address _gasStation, ITokensApprover _approver) {
nativeAddress = _nativeAddress;
hyperDex = _hyperDex;
allowanceTarget = _allowanceTarget;
beneficiary = _beneficiary;
approver = _approver;
_setGasStation(_gasStation);
}
/// @dev Fallback for just receiving ether.
receive() external payable {}
/// @dev Set a new MultiSwap proxy contract address
/// @param _hyperDex New Exchange proxy address
function setHyperDex(IHyperDex _hyperDex) public onlyOwner {
require(address(_hyperDex) != address(0), "Invalid HyperDex address");
hyperDex = _hyperDex;
emit HyperDexChanged(address(hyperDex));
}
/// @dev Set a new new allowance target address
/// @param _allowanceTarget New allowance target address
function setAllowanceTarget(address payable _allowanceTarget) public onlyOwner {
require(_allowanceTarget != address(0), "Invalid allowance target");
allowanceTarget = _allowanceTarget;
emit AllowanceTargetChanged(allowanceTarget);
}
/// @dev Set a new beneficiary address
/// @param _beneficiary New beneficiary target address
function setBeneficiary(address payable _beneficiary) public onlyOwner {
require(_beneficiary != address(0), "Invalid beneficiary");
beneficiary = _beneficiary;
emit BeneficiaryChanged(beneficiary);
}
/// @dev Set a new trusted gas station address
/// @param _gasStation New gas station address
function setGasStation(address _gasStation) external onlyOwner {
_setGasStation(_gasStation);
}
/// @dev Set a new tokens approver contract address
/// @param _approver New approver address
function setApprover(ITokensApprover _approver) external onlyOwner {
require(address(_approver) != address(0), "Invalid beneficiary");
approver = _approver;
emit TokensApproverChanged(address(approver));
}
/// @dev Set a referrals fees
/// @param _feeReferrals New referrals fees values
function setFeeReferrals(uint256[4] memory _feeReferrals) public onlyOwner {
feeReferrals = _feeReferrals;
}
/// @dev Set a beneficiary fees
/// @param _feeBeneficiary New beneficiary fees value
function setFeeBeneficiary(uint256 _feeBeneficiary) public onlyOwner {
feeBeneficiary = _feeBeneficiary;
}
/// @dev Forwards calls to the HyperDex contract and extracts a fee based on provided arguments
/// @param msgData The byte data representing a swap using the original HyperDex contract. This is either recieved from the Multiswap API directly or we construct it in order to perform a single swap trade
/// @param inputToken The ERC20 the user is selling. If this is ETH it should be the standard 0xeee ETH address
/// @param inputAmount The amount of inputToken being sold, without fees
/// @param outputToken The ERC20 the user is buying. If this is ETH it should be the standard 0xeee ETH address
/// @param referrals Referral addresses for which interest will be accrued from each exchange.
function multiRoute(
bytes calldata msgData,
address inputToken,
uint256 inputAmount,
address outputToken,
address[4] memory referrals
) external payable returns (bytes memory) {
return _multiRoute(msgData, inputToken, inputAmount, outputToken, referrals);
}
function multiRouteWithPermit(
bytes calldata msgData,
address inputToken,
uint256 inputAmount,
address outputToken,
address[4] memory referrals,
bytes calldata approvalData
) external payable returns (bytes memory) {
_permit(inputToken, approvalData);
return _multiRoute(msgData, inputToken, inputAmount, outputToken, referrals);
}
function _multiRoute(
bytes calldata msgData,
address inputToken,
uint256 inputAmount,
address outputToken,
address[4] memory referrals
) internal returns (bytes memory) {
// Calculate total fees and send to beneficiary.
uint256 inputAmountPercent = inputAmount / 10000;
uint256 fee = inputAmountPercent * feeBeneficiary;
_payFees(inputToken, fee, beneficiary);
for (uint256 i = 0; i < referrals.length; i++) {
if (referrals[i] != address(0) && feeReferrals[i] != 0) {
uint256 feeReferral = inputAmountPercent * feeReferrals[i];
fee = fee + feeReferral;
_payFees(inputToken, feeReferral, payable(referrals[i]));
}
}
// Checking the ETH balance and approve for token transfer
uint256 value = 0;
if (inputToken == nativeAddress) {
require(msg.value == inputAmount + fee, "Insufficient value with fee");
value = inputAmount;
} else {
_sendERC20(IERC20(inputToken), _msgSender(), address(this), inputAmount);
uint256 allowedAmount = IERC20(inputToken).allowance(address(this), allowanceTarget);
if (allowedAmount < inputAmount) {
IERC20(inputToken).safeIncreaseAllowance(allowanceTarget, inputAmount - allowedAmount);
}
}
// Call HyperDex multi swap
(bool success, bytes memory resultData) = address(hyperDex).call{value : value}(msgData);
if (!success) {
_revertWithData(resultData);
}
// We send the received tokens back to the sender
if (outputToken == nativeAddress) {
if (address(this).balance > 0) {
_sendETH(payable(_msgSender()), address(this).balance);
} else {
_revertWithData(resultData);
}
} else {
uint256 tokenBalance = IERC20(outputToken).balanceOf(address(this));
if (tokenBalance > 0) {
IERC20(outputToken).safeTransfer(_msgSender(), tokenBalance);
} else {
_revertWithData(resultData);
}
}
_returnWithData(resultData);
}
function _permit(address token, bytes calldata approvalData) internal {
if (approvalData.length > 0 && approver.hasConfigured(token)) {
(bool success,) = approver.callPermit(token, approvalData);
require(success, "Permit Method Call Error");
}
}
/// @dev Pay fee to beneficiary
/// @param token token address to pay fee in, can be ETH
/// @param amount fee amount to pay
function _payFees(address token, uint256 amount, address payable recipient) private {
if (token == nativeAddress) {
_sendETH(recipient, amount);
} else {
_sendERC20(IERC20(token), _msgSender(), recipient, amount);
}
emit FeePayment(recipient, token, amount);
}
function _sendETH(address payable toAddress, uint256 amount) private {
if (amount > 0) {
(bool success,) = toAddress.call{value : amount}("");
require(success, "Unable to send ETH");
}
}
function _sendERC20(IERC20 token, address fromAddress, address toAddress, uint256 amount) private {
if (amount > 0) {
token.safeTransferFrom(fromAddress, toAddress, amount);
}
}
/// @dev Revert with arbitrary bytes.
/// @param data Revert data.
function _revertWithData(bytes memory data) private pure {
assembly {revert(add(data, 32), mload(data))}
}
/// @dev Return with arbitrary bytes.
/// @param data Return data.
function _returnWithData(bytes memory data) private pure {
assembly {
return (add(data, 32), mload(data))
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @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);
/**
* @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);
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
interface IGasStationRecipient {
event GasStationChanged(address indexed gasStation);
function isOwnGasStation(address addressToCheck) external view returns(bool);
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
interface IHyperDex {
function getFunctionImplementation(bytes4 _signature) external returns (address);
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
interface ITokensApprover {
/**
* @notice Data for issuing permissions for the token
*/
struct ApproveConfig {
string name;
string version;
string domainType;
string primaryType;
string noncesMethod;
string permitMethod;
bytes4 permitMethodSelector;
}
event TokensApproverConfigAdded(uint256 indexed id);
event TokensApproverConfigUpdated(uint256 indexed id);
event TokensApproverTokenAdded(address indexed token, uint256 id);
event TokensApproverTokenRemoved(address indexed token);
function addConfig(ApproveConfig calldata config) external returns (uint256);
function updateConfig(uint256 id, ApproveConfig calldata config) external returns (uint256);
function setToken(uint256 id, address token) external;
function removeToken(address token) external;
function getConfig(address token) view external returns (ApproveConfig memory);
function getConfigById(uint256 id) view external returns (ApproveConfig memory);
function configsLength() view external returns (uint256);
function hasConfigured(address token) view external returns (bool);
function callPermit(address token, bytes calldata permitCallData) external returns (bool, bytes memory);
}
//SPDX-License-Identifier: BUSL
pragma solidity 0.8.10;
library LibBytesRichErrorsV06 {
enum InvalidByteOperationErrorCodes {
FromLessThanOrEqualsToRequired,
ToLessThanOrEqualsLengthRequired,
LengthGreaterThanZeroRequired,
LengthGreaterThanOrEqualsFourRequired,
LengthGreaterThanOrEqualsTwentyRequired,
LengthGreaterThanOrEqualsThirtyTwoRequired,
LengthGreaterThanOrEqualsNestedBytesLengthRequired,
DestinationLengthGreaterThanOrEqualSourceLengthRequired
}
// bytes4(keccak256("InvalidByteOperationError(uint8,uint256,uint256)"))
bytes4 internal constant INVALID_BYTE_OPERATION_ERROR_SELECTOR =
0x28006595;
// solhint-disable func-name-mixedcase
function InvalidByteOperationError(
InvalidByteOperationErrorCodes errorCode,
uint256 offset,
uint256 required
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
INVALID_BYTE_OPERATION_ERROR_SELECTOR,
errorCode,
offset,
required
);
}
}
//SPDX-License-Identifier: BUSL
pragma solidity 0.8.10;
import "./LibBytesRichErrorsV06.sol";
import "./LibRichErrorsV06.sol";
library LibBytesV06 {
using LibBytesV06 for bytes;
/// @dev Gets the memory address for a byte array.
/// @param input Byte array to lookup.
/// @return memoryAddress Memory address of byte array. This
/// points to the header of the byte array which contains
/// the length.
function rawAddress(bytes memory input)
internal
pure
returns (uint256 memoryAddress)
{
assembly {
memoryAddress := input
}
return memoryAddress;
}
/// @dev Gets the memory address for the contents of a byte array.
/// @param input Byte array to lookup.
/// @return memoryAddress Memory address of the contents of the byte array.
function contentAddress(bytes memory input)
internal
pure
returns (uint256 memoryAddress)
{
assembly {
memoryAddress := add(input, 32)
}
return memoryAddress;
}
/// @dev Copies `length` bytes from memory location `source` to `dest`.
/// @param dest memory address to copy bytes to.
/// @param source memory address to copy bytes from.
/// @param length number of bytes to copy.
function memCopy(
uint256 dest,
uint256 source,
uint256 length
)
internal
pure
{
if (length < 32) {
// Handle a partial word by reading destination and masking
// off the bits we are interested in.
// This correctly handles overlap, zero lengths and source == dest
assembly {
let mask := sub(exp(256, sub(32, length)), 1)
let s := and(mload(source), not(mask))
let d := and(mload(dest), mask)
mstore(dest, or(s, d))
}
} else {
// Skip the O(length) loop when source == dest.
if (source == dest) {
return;
}
// For large copies we copy whole words at a time. The final
// word is aligned to the end of the range (instead of after the
// previous) to handle partial words. So a copy will look like this:
//
// ####
// ####
// ####
// ####
//
// We handle overlap in the source and destination range by
// changing the copying direction. This prevents us from
// overwriting parts of source that we still need to copy.
//
// This correctly handles source == dest
//
if (source > dest) {
assembly {
// We subtract 32 from `sEnd` and `dEnd` because it
// is easier to compare with in the loop, and these
// are also the addresses we need for copying the
// last bytes.
length := sub(length, 32)
let sEnd := add(source, length)
let dEnd := add(dest, length)
// Remember the last 32 bytes of source
// This needs to be done here and not after the loop
// because we may have overwritten the last bytes in
// source already due to overlap.
let last := mload(sEnd)
// Copy whole words front to back
// Note: the first check is always true,
// this could have been a do-while loop.
// solhint-disable-next-line no-empty-blocks
for {} lt(source, sEnd) {} {
mstore(dest, mload(source))
source := add(source, 32)
dest := add(dest, 32)
}
// Write the last 32 bytes
mstore(dEnd, last)
}
} else {
assembly {
// We subtract 32 from `sEnd` and `dEnd` because those
// are the starting points when copying a word at the end.
length := sub(length, 32)
let sEnd := add(source, length)
let dEnd := add(dest, length)
// Remember the first 32 bytes of source
// This needs to be done here and not after the loop
// because we may have overwritten the first bytes in
// source already due to overlap.
let first := mload(source)
// Copy whole words back to front
// We use a signed comparisson here to allow dEnd to become
// negative (happens when source and dest < 32). Valid
// addresses in local memory will never be larger than
// 2**255, so they can be safely re-interpreted as signed.
// Note: the first check is always true,
// this could have been a do-while loop.
// solhint-disable-next-line no-empty-blocks
for {} slt(dest, dEnd) {} {
mstore(dEnd, mload(sEnd))
sEnd := sub(sEnd, 32)
dEnd := sub(dEnd, 32)
}
// Write the first 32 bytes
mstore(dest, first)
}
}
}
}
/// @dev Returns a slices from a byte array.
/// @param b The byte array to take a slice from.
/// @param from The starting index for the slice (inclusive).
/// @param to The final index for the slice (exclusive).
/// @return result The slice containing bytes at indices [from, to)
function slice(
bytes memory b,
uint256 from,
uint256 to
)
internal
pure
returns (bytes memory result)
{
// Ensure that the from and to positions are valid positions for a slice within
// the byte array that is being used.
if (from > to) {
LibRichErrorsV06.rrevert(LibBytesRichErrorsV06.InvalidByteOperationError(
LibBytesRichErrorsV06.InvalidByteOperationErrorCodes.FromLessThanOrEqualsToRequired,
from,
to
));
}
if (to > b.length) {
LibRichErrorsV06.rrevert(LibBytesRichErrorsV06.InvalidByteOperationError(
LibBytesRichErrorsV06.InvalidByteOperationErrorCodes.ToLessThanOrEqualsLengthRequired,
to,
b.length
));
}
// Create a new bytes structure and copy contents
result = new bytes(to - from);
memCopy(
result.contentAddress(),
b.contentAddress() + from,
result.length
);
return result;
}
/// @dev Returns a slice from a byte array without preserving the input.
/// When `from == 0`, the original array will match the slice.
/// In other cases its state will be corrupted.
/// @param b The byte array to take a slice from. Will be destroyed in the process.
/// @param from The starting index for the slice (inclusive).
/// @param to The final index for the slice (exclusive).
/// @return result The slice containing bytes at indices [from, to)
function sliceDestructive(
bytes memory b,
uint256 from,
uint256 to
)
internal
pure
returns (bytes memory result)
{
// Ensure that the from and to positions are valid positions for a slice within
// the byte array that is being used.
if (from > to) {
LibRichErrorsV06.rrevert(LibBytesRichErrorsV06.InvalidByteOperationError(
LibBytesRichErrorsV06.InvalidByteOperationErrorCodes.FromLessThanOrEqualsToRequired,
from,
to
));
}
if (to > b.length) {
LibRichErrorsV06.rrevert(LibBytesRichErrorsV06.InvalidByteOperationError(
LibBytesRichErrorsV06.InvalidByteOperationErrorCodes.ToLessThanOrEqualsLengthRequired,
to,
b.length
));
}
// Create a new bytes structure around [from, to) in-place.
assembly {
result := add(b, from)
mstore(result, sub(to, from))
}
return result;
}
/// @dev Pops the last byte off of a byte array by modifying its length.
/// @param b Byte array that will be modified.
/// @return result The byte that was popped off.
function popLastByte(bytes memory b)
internal
pure
returns (bytes1 result)
{
if (b.length == 0) {
LibRichErrorsV06.rrevert(LibBytesRichErrorsV06.InvalidByteOperationError(
LibBytesRichErrorsV06.InvalidByteOperationErrorCodes.LengthGreaterThanZeroRequired,
b.length,
0
));
}
// Store last byte.
result = b[b.length - 1];
assembly {
// Decrement length of byte array.
let newLen := sub(mload(b), 1)
mstore(b, newLen)
}
return result;
}
/// @dev Tests equality of two byte arrays.
/// @param lhs First byte array to compare.
/// @param rhs Second byte array to compare.
/// @return equal True if arrays are the same. False otherwise.
function equals(
bytes memory lhs,
bytes memory rhs
)
internal
pure
returns (bool equal)
{
// Keccak gas cost is 30 + numWords * 6. This is a cheap way to compare.
// We early exit on unequal lengths, but keccak would also correctly
// handle this.
return lhs.length == rhs.length && keccak256(lhs) == keccak256(rhs);
}
/// @dev Reads an address from a position in a byte array.
/// @param b Byte array containing an address.
/// @param index Index in byte array of address.
/// @return result address from byte array.
function readAddress(
bytes memory b,
uint256 index
)
internal
pure
returns (address result)
{
if (b.length < index + 20) {
LibRichErrorsV06.rrevert(LibBytesRichErrorsV06.InvalidByteOperationError(
LibBytesRichErrorsV06.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsTwentyRequired,
b.length,
index + 20 // 20 is length of address
));
}
// Add offset to index:
// 1. Arrays are prefixed by 32-byte length parameter (add 32 to index)
// 2. Account for size difference between address length and 32-byte storage word (subtract 12 from index)
index += 20;
// Read address from array memory
assembly {
// 1. Add index to address of bytes array
// 2. Load 32-byte word from memory
// 3. Apply 20-byte mask to obtain address
result := and(mload(add(b, index)), 0xffffffffffffffffffffffffffffffffffffffff)
}
return result;
}
/// @dev Writes an address into a specific position in a byte array.
/// @param b Byte array to insert address into.
/// @param index Index in byte array of address.
/// @param input Address to put into byte array.
function writeAddress(
bytes memory b,
uint256 index,
address input
)
internal
pure
{
if (b.length < index + 20) {
LibRichErrorsV06.rrevert(LibBytesRichErrorsV06.InvalidByteOperationError(
LibBytesRichErrorsV06.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsTwentyRequired,
b.length,
index + 20 // 20 is length of address
));
}
// Add offset to index:
// 1. Arrays are prefixed by 32-byte length parameter (add 32 to index)
// 2. Account for size difference between address length and 32-byte storage word (subtract 12 from index)
index += 20;
// Store address into array memory
assembly {
// The address occupies 20 bytes and mstore stores 32 bytes.
// First fetch the 32-byte word where we'll be storing the address, then
// apply a mask so we have only the bytes in the word that the address will not occupy.
// Then combine these bytes with the address and store the 32 bytes back to memory with mstore.
// 1. Add index to address of bytes array
// 2. Load 32-byte word from memory
// 3. Apply 12-byte mask to obtain extra bytes occupying word of memory where we'll store the address
let neighbors := and(
mload(add(b, index)),
0xffffffffffffffffffffffff0000000000000000000000000000000000000000
)
// Make sure input address is clean.
// (Solidity does not guarantee this)
input := and(input, 0xffffffffffffffffffffffffffffffffffffffff)
// Store the neighbors and address into memory
mstore(add(b, index), xor(input, neighbors))
}
}
/// @dev Reads a bytes32 value from a position in a byte array.
/// @param b Byte array containing a bytes32 value.
/// @param index Index in byte array of bytes32 value.
/// @return result bytes32 value from byte array.
function readBytes32(
bytes memory b,
uint256 index
)
internal
pure
returns (bytes32 result)
{
if (b.length < index + 32) {
LibRichErrorsV06.rrevert(LibBytesRichErrorsV06.InvalidByteOperationError(
LibBytesRichErrorsV06.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
b.length,
index + 32
));
}
// Arrays are prefixed by a 256 bit length parameter
index += 32;
// Read the bytes32 from array memory
assembly {
result := mload(add(b, index))
}
return result;
}
/// @dev Writes a bytes32 into a specific position in a byte array.
/// @param b Byte array to insert <input> into.
/// @param index Index in byte array of <input>.
/// @param input bytes32 to put into byte array.
function writeBytes32(
bytes memory b,
uint256 index,
bytes32 input
)
internal
pure
{
if (b.length < index + 32) {
LibRichErrorsV06.rrevert(LibBytesRichErrorsV06.InvalidByteOperationError(
LibBytesRichErrorsV06.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired,
b.length,
index + 32
));
}
// Arrays are prefixed by a 256 bit length parameter
index += 32;
// Read the bytes32 from array memory
assembly {
mstore(add(b, index), input)
}
}
/// @dev Reads a uint256 value from a position in a byte array.
/// @param b Byte array containing a uint256 value.
/// @param index Index in byte array of uint256 value.
/// @return result uint256 value from byte array.
function readUint256(
bytes memory b,
uint256 index
)
internal
pure
returns (uint256 result)
{
result = uint256(readBytes32(b, index));
return result;
}
/// @dev Writes a uint256 into a specific position in a byte array.
/// @param b Byte array to insert <input> into.
/// @param index Index in byte array of <input>.
/// @param input uint256 to put into byte array.
function writeUint256(
bytes memory b,
uint256 index,
uint256 input
)
internal
pure
{
writeBytes32(b, index, bytes32(input));
}
/// @dev Reads an unpadded bytes4 value from a position in a byte array.
/// @param b Byte array containing a bytes4 value.
/// @param index Index in byte array of bytes4 value.
/// @return result bytes4 value from byte array.
function readBytes4(
bytes memory b,
uint256 index
)
internal
pure
returns (bytes4 result)
{
if (b.length < index + 4) {
LibRichErrorsV06.rrevert(LibBytesRichErrorsV06.InvalidByteOperationError(
LibBytesRichErrorsV06.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsFourRequired,
b.length,
index + 4
));
}
// Arrays are prefixed by a 32 byte length field
index += 32;
// Read the bytes4 from array memory
assembly {
result := mload(add(b, index))
// Solidity does not require us to clean the trailing bytes.
// We do it anyway
result := and(result, 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000)
}
return result;
}
/// @dev Writes a new length to a byte array.
/// Decreasing length will lead to removing the corresponding lower order bytes from the byte array.
/// Increasing length may lead to appending adjacent in-memory bytes to the end of the byte array.
/// @param b Bytes array to write new length to.
/// @param length New length of byte array.
function writeLength(bytes memory b, uint256 length)
internal
pure
{
assembly {
mstore(b, length)
}
}
}
//SPDX-License-Identifier: BUSL
pragma solidity 0.8.10;
library LibProxyRichErrors {
// solhint-disable func-name-mixedcase
function NotImplementedError(bytes4 selector)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("NotImplementedError(bytes4)")),
selector
);
}
function InvalidBootstrapCallerError(address actual, address expected)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("InvalidBootstrapCallerError(address,address)")),
actual,
expected
);
}
function InvalidDieCallerError(address actual, address expected)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("InvalidDieCallerError(address,address)")),
actual,
expected
);
}
function BootstrapCallFailedError(address target, bytes memory resultData)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("BootstrapCallFailedError(address,bytes)")),
target,
resultData
);
}
}
//SPDX-License-Identifier: BUSL
pragma solidity 0.8.10;
library LibRichErrorsV06 {
// bytes4(keccak256("Error(string)"))
bytes4 internal constant STANDARD_ERROR_SELECTOR = 0x08c379a0;
// solhint-disable func-name-mixedcase
/// @dev ABI encode a standard, string revert error payload.
/// This is the same payload that would be included by a `revert(string)`
/// solidity statement. It has the function signature `Error(string)`.
/// @param message The error string.
/// @return The ABI encoded error.
function StandardError(string memory message)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
STANDARD_ERROR_SELECTOR,
bytes(message)
);
}
// solhint-enable func-name-mixedcase
/// @dev Reverts an encoded rich revert reason `errorData`.
/// @param errorData ABI encoded error data.
function rrevert(bytes memory errorData)
internal
pure
{
assembly {
revert(add(errorData, 0x20), mload(errorData))
}
}
}
//SPDX-License-Identifier: BUSL
pragma solidity 0.8.10;
import "./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 () {
address msgSender = _msgSenderContext();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSenderContext(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = 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");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
{
"compilationTarget": {
"contracts/HyperDexRouter.sol": "HyperDexRouter"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_nativeAddress","type":"address"},{"internalType":"contract IHyperDex","name":"_hyperDex","type":"address"},{"internalType":"address payable","name":"_allowanceTarget","type":"address"},{"internalType":"address payable","name":"_beneficiary","type":"address"},{"internalType":"address","name":"_gasStation","type":"address"},{"internalType":"contract ITokensApprover","name":"_approver","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"allowanceTarget","type":"address"}],"name":"AllowanceTargetChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"beneficiary","type":"address"}],"name":"BeneficiaryChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeePayment","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"gasStation","type":"address"}],"name":"GasStationChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"hyperDex","type":"address"}],"name":"HyperDexChanged","type":"event"},{"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":true,"internalType":"address","name":"approver","type":"address"}],"name":"TokensApproverChanged","type":"event"},{"inputs":[],"name":"allowanceTarget","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"approver","outputs":[{"internalType":"contract ITokensApprover","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"beneficiary","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeBeneficiary","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"feeReferrals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gasStation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"hyperDex","outputs":[{"internalType":"contract IHyperDex","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addressToCheck","type":"address"}],"name":"isOwnGasStation","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"msgData","type":"bytes"},{"internalType":"address","name":"inputToken","type":"address"},{"internalType":"uint256","name":"inputAmount","type":"uint256"},{"internalType":"address","name":"outputToken","type":"address"},{"internalType":"address[4]","name":"referrals","type":"address[4]"}],"name":"multiRoute","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"msgData","type":"bytes"},{"internalType":"address","name":"inputToken","type":"address"},{"internalType":"uint256","name":"inputAmount","type":"uint256"},{"internalType":"address","name":"outputToken","type":"address"},{"internalType":"address[4]","name":"referrals","type":"address[4]"},{"internalType":"bytes","name":"approvalData","type":"bytes"}],"name":"multiRouteWithPermit","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"_allowanceTarget","type":"address"}],"name":"setAllowanceTarget","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ITokensApprover","name":"_approver","type":"address"}],"name":"setApprover","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"_beneficiary","type":"address"}],"name":"setBeneficiary","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_feeBeneficiary","type":"uint256"}],"name":"setFeeBeneficiary","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[4]","name":"_feeReferrals","type":"uint256[4]"}],"name":"setFeeReferrals","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_gasStation","type":"address"}],"name":"setGasStation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IHyperDex","name":"_hyperDex","type":"address"}],"name":"setHyperDex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]