// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)pragmasolidity ^0.8.20;/**
* @dev Collection of functions related to the address type
*/libraryAddress{
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/errorAddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/errorAddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/errorFailedInnerCall();
/**
* @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://consensys.net/diligence/blog/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.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/functionsendValue(addresspayable recipient, uint256 amount) internal{
if (address(this).balance< amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @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 or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* 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.
*/functionfunctionCall(address target, bytesmemory data) internalreturns (bytesmemory) {
return functionCallWithValue(target, data, 0);
}
/**
* @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`.
*/functionfunctionCallWithValue(address target, bytesmemory data, uint256 value) internalreturns (bytesmemory) {
if (address(this).balance< value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytesmemory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/functionfunctionStaticCall(address target, bytesmemory data) internalviewreturns (bytesmemory) {
(bool success, bytesmemory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/functionfunctionDelegateCall(address target, bytesmemory data) internalreturns (bytesmemory) {
(bool success, bytesmemory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/functionverifyCallResultFromTarget(address target,
bool success,
bytesmemory returndata
) internalviewreturns (bytesmemory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty// otherwise we already know that it was a contractif (returndata.length==0&& target.code.length==0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/functionverifyCallResult(bool success, bytesmemory returndata) internalpurereturns (bytesmemory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/function_revert(bytesmemory returndata) privatepure{
// Look for revert reason and bubble it up if presentif (returndata.length>0) {
// The easiest way to bubble the revert reason is using memory via assembly/// @solidity memory-safe-assemblyassembly {
let returndata_size :=mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}
Contract Source Code
File 2 of 13: Context.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (utils/Context.sol)pragmasolidity ^0.8.20;/**
* @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.
*/abstractcontractContext{
function_msgSender() internalviewvirtualreturns (address) {
returnmsg.sender;
}
function_msgData() internalviewvirtualreturns (bytescalldata) {
returnmsg.data;
}
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)pragmasolidity ^0.8.20;/**
* @dev Interface of the ERC-20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[ERC-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC-20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/interfaceIERC20Permit{
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/functionpermit(address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/functionnonces(address owner) externalviewreturns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/// solhint-disable-next-line func-name-mixedcasefunctionDOMAIN_SEPARATOR() externalviewreturns (bytes32);
}
Contract Source Code
File 5 of 13: ISwapRouter02.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity >=0.7.5;pragmaabicoderv2;/// @title Router token swapping functionality/// @notice Functions for swapping tokens via Uniswap V3interfaceISwapRouter02{
functionfactory() externalviewreturns (address);
//frusturatingly, there is no deadline in this set of params//used on SwapRouter02 on Base chainstructExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
/// @notice Swaps `amountIn` of one token for as much as possible of another token/// @dev Setting `amountIn` to 0 will cause the contract to look up its own balance,/// and swap the entire amount, enabling contracts to send tokens before calling this function./// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata/// @return amountOut The amount of the received tokenfunctionexactInputSingle(ExactInputSingleParams calldata params) externalpayablereturns (uint256 amountOut);
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)pragmasolidity ^0.8.20;import {Context} from"./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.
*
* The initial owner is set to the address provided by the deployer. 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.
*/abstractcontractOwnableisContext{
addressprivate _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/errorOwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/errorOwnableInvalidOwner(address owner);
eventOwnershipTransferred(addressindexed previousOwner, addressindexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/constructor(address initialOwner) {
if (initialOwner ==address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/modifieronlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/functionowner() publicviewvirtualreturns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/function_checkOwner() internalviewvirtual{
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @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.
*/functionrenounceOwnership() publicvirtualonlyOwner{
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/functiontransferOwnership(address newOwner) publicvirtualonlyOwner{
if (newOwner ==address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/function_transferOwnership(address newOwner) internalvirtual{
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
Contract Source Code
File 10 of 13: Portico.sol
// SPDX-License-Identifier: UNLICENSEpragmasolidity ^0.8.9;import"./PorticoStructs.sol";
import"./ITokenBridge.sol";
import"./IWormhole.sol";
import"./IERC20.sol";
import"./IWETH.sol";
//uniswapimport"./uniswap/ISwapRouter02.sol";
//ozimport"./oz/Ownable.sol";
import"./oz/ReentrancyGuard.sol";
import"./oz/SafeERC20.sol";
contractPorticoBaseisOwnable, ReentrancyGuard{
usingSafeERC20forIERC20;
ISwapRouter02 publicimmutable ROUTERV3;
ITokenBridge publicimmutable TOKENBRIDGE;
IWETH publicimmutable WETH;
IWormhole publicimmutable wormhole;
uint16publicimmutable wormholeChainId;
addresspublic FEE_RECIPIENT;
constructor(ISwapRouter02 _routerV3, ITokenBridge _bridge, IWETH _weth, address _feeRecipient) Ownable(_msgSender()) {
ROUTERV3 = _routerV3;
TOKENBRIDGE = _bridge;
wormhole = _bridge.wormhole();
WETH = _weth;
wormholeChainId = wormhole.chainId();
FEE_RECIPIENT = _feeRecipient;
}
functionversion() externalpurereturns (uint32) {
return1;
}
///@notice config recipient for relayer feesfunctionsetFeeRecipient(address newFeeRecipient) externalonlyOwner{
FEE_RECIPIENT = newFeeRecipient;
}
///@notice if current approval is insufficient, approve max///@notice oz safeIncreaseAllowance controls for tokens that require allowance to be reset to 0 before increasing againfunctionupdateApproval(address spender, IERC20 token, uint256 amount) internal{
// get current allowanceuint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < amount) {
// amount is a delta, so need to pass max - current to avoid overflow
token.safeIncreaseAllowance(spender, type(uint256).max- (currentAllowance +1));
}
}
}
abstractcontractPorticoStartisPorticoBase{
usingPorticoFlagSetAccessforPorticoFlagSet;
usingSafeERC20forIERC20;
function_start_v3swap(PorticoStructs.TradeParameters memory params, uint256 actualAmount) internalreturns (uint256 amount) {
updateApproval(address(ROUTERV3), params.startTokenAddress, actualAmount);
ROUTERV3.exactInputSingle(
ISwapRouter02.ExactInputSingleParams(
address(params.startTokenAddress), //tokenInaddress(params.canonAssetAddress), //tokenOut
params.flags.feeTierStart(), //feeaddress(this), //recipient
actualAmount, //amountIn
params.minAmountStart, //minAmountReceived0
)
);
amount = params.canonAssetAddress.balanceOf(address(this));
}
eventPorticoSwapStart(uint64indexed sequence, uint16indexed chainId);
functionstart(
PorticoStructs.TradeParameters memory params
) externalpayablenonReentrantreturns (address emitterAddress, uint16 chainId, uint64 sequence) {
uint256 amount;
uint256 whMessageFee = wormhole.messageFee();
uint256 value =msg.value;
// always check for native wrapping logicif (address(params.startTokenAddress) ==address(WETH) && params.flags.shouldWrapNative()) {
// if wrapping, msg.value should be exactly amountSpecified + wormhole message feerequire(value == params.amountSpecified + whMessageFee, "msg.value incorrect");
// if we are wrapping a token, we call WETH.deposit for the user, assuming we have been sent what we need.
WETH.deposit{ value: params.amountSpecified }();
// because wormhole rounds to 1e8, some dust may exist from previous txs// we use balanceOf to lump this in with future txs
amount = WETH.balanceOf(address(this));
} else {
// ensure no eth needs to be refundedrequire(value == whMessageFee, "msg.value incorrect");
// otherwise, just get the token we need to do the swap (if we are swapping, or just the token itself)
params.startTokenAddress.safeTransferFrom(_msgSender(), address(this), params.amountSpecified);
// Because wormhole rounds to 1e8, some dust may exist from previous txs// we use balanceOf to lump this in with future txs
amount = params.startTokenAddress.balanceOf(address(this));
}
// sanity check amount receivedrequire(amount >= params.amountSpecified, "transfer insufficient");
// if the start token is the canon token, we don't need to swapif (params.startTokenAddress != params.canonAssetAddress) {
// do the swap, and amount is now the amount that we received from the swap
amount = _start_v3swap(params, amount);
}
// allow the token bridge to do its token bridge things
updateApproval(address(TOKENBRIDGE), params.canonAssetAddress, amount);
// now we need to produce the payload we are sending
PorticoStructs.DecodedVAA memory decodedVAA = PorticoStructs.DecodedVAA(
params.flags,
params.finalTokenAddress,
params.recipientAddress,
amount,
params.minAmountFinish,
params.relayerFee
);
// send the actual transfer tx, and get the sequence
sequence = TOKENBRIDGE.transferTokensWithPayload{ value: whMessageFee }(
address(params.canonAssetAddress),
amount,
params.flags.recipientChain(),
padAddress(params.recipientPorticoAddress),
params.flags.bridgeNonce(),
abi.encode(decodedVAA)
);
// local chain id
chainId = wormholeChainId;
// emitter is the local tokenbridge
emitterAddress =address(TOKENBRIDGE);
// emit eventemit PorticoSwapStart(sequence, chainId);
}
///@notice @return addr in bytes32 format, as required by WormholefunctionpadAddress(address addr) internalpurereturns (bytes32) {
returnbytes32(uint256(uint160(addr)));
}
}
abstractcontractPorticoFinishisPorticoBase{
usingPorticoFlagSetAccessforPorticoFlagSet;
usingSafeERC20forIERC20;
eventPorticoSwapFinish(bool swapCompleted, uint256 finaluserAmount, uint256 relayerFeeAmount, PorticoStructs.DecodedVAA data);
// receiveMessageAndSwap is the entrypoint for finishing the swapfunctionreceiveMessageAndSwap(bytescalldata encodedTransferMessage) externalnonReentrant{
// start by calling _completeTransfer, submitting the VAA to the token bridge
(PorticoStructs.DecodedVAA memory message, PorticoStructs.BridgeInfo memory bridgeInfo) = _completeTransfer(encodedTransferMessage);
// we modify the message to set the relayerFee to 0 if the msgSender is the fee recipient// this allows users to self-relay and not pay the fee, even if the fee was set to non-zero at tx origin
bridgeInfo.relayerFeeAmount = (_msgSender() == message.recipientAddress) ? 0 : message.relayerFee;
//now process
(bool swapCompleted, uint256 finalUserAmount) = finish(message, bridgeInfo);
// simply emit the raw data bytes. it should be trivial to parse.emit PorticoSwapFinish(swapCompleted, finalUserAmount, bridgeInfo.relayerFeeAmount, message);
}
// _completeTransfer takes the vaa for a payload3 token transfer, redeems it with the token bridge, and returns the decoded vaa payloadfunction_completeTransfer(bytescalldata encodedTransferMessage
) internalreturns (PorticoStructs.DecodedVAA memory message, PorticoStructs.BridgeInfo memory bridgeInfo) {
/**
* Call `completeTransferWithPayload` on the token bridge. This
* method acts as a reentrancy protection since it does not allow
* transfers to be redeemed more than once.
*/bytesmemory transferPayload = TOKENBRIDGE.completeTransferWithPayload(encodedTransferMessage);
// parse the wormhole message payload into the `TransferWithPayload` struct, a payload3 token transfer
ITokenBridge.TransferWithPayload memory transfer = TOKENBRIDGE.parseTransferWithPayload(transferPayload);
// ensure that the to address is this addressrequire(unpadAddress(transfer.to) ==address(this) && transfer.toChain == wormholeChainId, "Token not sent to this address");
// decode the payload3 we originally sent into the decodedVAA struct.
message =abi.decode(transfer.payload, (PorticoStructs.DecodedVAA));
// get the address for the token on this address.
bridgeInfo.tokenReceived = IERC20(
transfer.tokenChain == wormholeChainId
? unpadAddress(transfer.tokenAddress)
: TOKENBRIDGE.wrappedAsset(transfer.tokenChain, transfer.tokenAddress)
);
// put the transfer amount into amountReceived, knowing we may need to change it in a sec
bridgeInfo.amountReceived = transfer.amount;
// if there are more than 8 decimals, we need to denormalize. wormhole token bridge truncates tokens of more than 8 decimals to 8 decimals.uint8 decimals = bridgeInfo.tokenReceived.decimals();
if (decimals >8) {
bridgeInfo.amountReceived *=uint256(10) ** (decimals -8);
}
}
///@notice determines we need to swap and/or unwrap, does those things if needed, and sends tokens to user & pays relayer feefunctionfinish(
PorticoStructs.DecodedVAA memory params,
PorticoStructs.BridgeInfo memory bridgeInfo
) internalreturns (bool swapCompleted, uint256 finalUserAmount) {
// see if the unwrap flag is set, and that the finalTokenAddress is the address we have set on deploy as our native weth9 addressbool shouldUnwrap = params.flags.shouldUnwrapNative() &&address(params.finalTokenAddress) ==address(WETH);
if ((params.finalTokenAddress) == bridgeInfo.tokenReceived) {
// this means that we don't need to do a swap, aka, we received the canon asset.
finalUserAmount = payOut(shouldUnwrap, params.finalTokenAddress, params.recipientAddress, bridgeInfo.relayerFeeAmount);
return (false, finalUserAmount);
}
// if we are here, if means we need to do the swap, resulting aset from the swap is sent to this contract
swapCompleted = _finish_v3swap(params, bridgeInfo);
// if the swap fails, we just transfer the amount we received from the token bridge to the recipientAddress.if (!swapCompleted) {
bridgeInfo.tokenReceived.transfer(params.recipientAddress, bridgeInfo.amountReceived);
// we also mark swapCompleted to be false for PorticoSwapFinish eventreturn (swapCompleted, bridgeInfo.amountReceived);
}
// we must call payout if the swap was completed
finalUserAmount = payOut(shouldUnwrap, params.finalTokenAddress, params.recipientAddress, bridgeInfo.relayerFeeAmount);
}
/**
* @notice perform the swap via Uniswap V3 Router
* if swap fails, we don't pay fees to the relayer
* the reason is because that typically, the swap fails because of bad market conditions
* in this case, it is in the best interest of the mev/relayer to NOT relay this message until conditions are good
* the user of course, who if they self relay, does not pay a fee, does not have this problem, so they can force this if they wish
* swap failed - return canon asset to recipient
* it will return true if the swap was completed, indicating that funds need to be sent from this contract to the recipient
*/function_finish_v3swap(
PorticoStructs.DecodedVAA memory params,
PorticoStructs.BridgeInfo memory bridgeInfo
) internalreturns (bool swapCompleted) {
// set swap options with params decoded from the payload
ISwapRouter02.ExactInputSingleParams memory swapParams = ISwapRouter02.ExactInputSingleParams({
tokenIn: address(bridgeInfo.tokenReceived),
tokenOut: address(params.finalTokenAddress),
fee: params.flags.feeTierFinish(),
recipient: address(this), // we need to receive the token in order to correctly split the fee. tragic.
amountIn: bridgeInfo.amountReceived,
amountOutMinimum: params.minAmountFinish,
sqrtPriceLimitX96: 0//sqrtPriceLimit is not used
});
// update approval
updateApproval(address(ROUTERV3), bridgeInfo.tokenReceived, bridgeInfo.amountReceived);
// try the swaptry ROUTERV3.exactInputSingle(swapParams) {
swapCompleted =true;
} catch {}
}
///@notice pay out to user and relayer///@notice this should always be called UNLESS swap fails, in which case payouts happen there// NOTE if relayerFeeAmount is incorrectly scaled, then the end user may receive nothing, and all proceeds go to relayer// it is incumbent upon the cross chain tx origin to ensure the relayerFeeAmount is passed correctlyfunctionpayOut(bool unwrap, IERC20 finalToken, address recipient, uint256 relayerFeeAmount) internalreturns (uint256 finalUserAmount) {
uint256 totalBalance = finalToken.balanceOf(address(this));
// square up balances with what we actually have, don't trust reporting from the bridgeif (relayerFeeAmount > totalBalance) {
// control for underflow
finalUserAmount =0;
relayerFeeAmount = totalBalance;
} else {
// user gets total - relayer fee
finalUserAmount = totalBalance - relayerFeeAmount;
}
// if feeRecipient is not set, then send fees to msg.senderaddress feeRecipient = FEE_RECIPIENT ==address(0x0) ? _msgSender() : FEE_RECIPIENT;
if (unwrap) {
WETH.withdraw(WETH.balanceOf(address(this)));
if (finalUserAmount >0) {
// send to user
sendEther(recipient, finalUserAmount);
}
if (relayerFeeAmount >0) {
// pay relayer fee
sendEther(feeRecipient, relayerFeeAmount);
}
} else {
// send to userif (finalUserAmount >0) {
finalToken.safeTransfer(recipient, finalUserAmount);
}
if (relayerFeeAmount >0) {
// pay relayer fee
finalToken.safeTransfer(feeRecipient, relayerFeeAmount);
}
}
}
receive() externalpayable{}
///@dev https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/Utils.sol#L10-L15functionunpadAddress(bytes32 whFormatAddress) internalpurereturns (address) {
if (uint256(whFormatAddress) >>160!=0) {
revert("Not EVM Addr");
}
returnaddress(uint160(uint256(whFormatAddress)));
}
///@notice send ether without exposing to gas griefing attacks via returned bytesfunctionsendEther(address to, uint256 value) internal{
bool sent;
assembly {
sent :=call(gas(), to, value, 0, 0, 0, 0)
}
if (!sent) {
revert("failed to send ether");
}
}
}
contractPorticoisPorticoFinish, PorticoStart{
constructor(
ISwapRouter02 _routerV3,
ITokenBridge _bridge,
IWETH _weth,
address _feeRecipient
) PorticoBase(_routerV3, _bridge, _weth, _feeRecipient) {}
}
Contract Source Code
File 11 of 13: PorticoStructs.sol
// SPDX-License-Identifier: UNLICENSEpragmasolidity ^0.8.9;import"./IERC20.sol";
type PorticoFlagSet isbytes32;
libraryPorticoFlagSetAccess{
// the portico uses one word (32 bytes) to represent a large amount of variables// bytes 0-1 is the recipient chainfunctionrecipientChain(PorticoFlagSet flagset) internalpurereturns (uint16 ans) {
assembly {
ans :=add(byte(0, flagset), shl(8, byte(1, flagset)))
}
}
// bytes 2-5 is the bridge noncefunctionbridgeNonce(PorticoFlagSet flagset) internalpurereturns (uint32 ans) {
assembly {
ans :=add(add(add(byte(2, flagset), shl(8, byte(3, flagset))), shl(16, byte(4, flagset))), shl(24, byte(5, flagset)))
}
}
// bytes 6,7,8 is the fee tier for start pathfunctionfeeTierStart(PorticoFlagSet flagset) internalpurereturns (uint24 ans) {
assembly {
ans :=add(add(byte(6, flagset), shl(8, byte(7, flagset))), shl(16, byte(8, flagset)))
}
}
// bytes 9,10,11 is the fee tier for finish pathfunctionfeeTierFinish(PorticoFlagSet flagset) internalpurereturns (uint24 ans) {
assembly {
ans :=add(add(byte(9, flagset), shl(8, byte(10, flagset))), shl(16, byte(11, flagset)))
}
}
// shouldWrapNative is the first bit of the byte 31functionshouldWrapNative(PorticoFlagSet flagset) internalpurereturns (bool) {
bytes32 fs = PorticoFlagSet.unwrap(flagset);
returnuint8(fs[31]) & (1<<0) >0;
}
// shouldUnwrapNative is the second bit of byte 31functionshouldUnwrapNative(PorticoFlagSet flagset) internalpurereturns (bool) {
bytes32 fs = PorticoFlagSet.unwrap(flagset);
returnuint8(fs[31]) & (1<<1) >0;
}
}
libraryPorticoStructs{
//https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/WormholeRelayerSDK.sol#L177//https://docs.wormhole.com/wormhole/quick-start/tutorials/hello-token#receiving-a-tokenstructTokenReceived {
bytes32 tokenHomeAddress;
uint16 tokenHomeChain;
IERC20 tokenAddress;
uint256 amount;
}
//268,090 - to beatstructTradeParameters {
PorticoFlagSet flags;
IERC20 startTokenAddress;
IERC20 canonAssetAddress;
IERC20 finalTokenAddress;
// address of the recipient on the recipientChainaddress recipientAddress;
// address of the portico on the recipient chainaddress recipientPorticoAddress;
// the amount of the token that the person wishes to transferuint256 amountSpecified;
uint256 minAmountStart;
uint256 minAmountFinish;
uint256 relayerFee; // the amount of tokens of the recipient to give to the relayer
}
//268,041 158,788structDecodedVAA {
PorticoFlagSet flags;
IERC20 finalTokenAddress;
// the person to receive the tokenaddress recipientAddress;
// the x asset amount expected to be receiveduint256 canonAssetAmount;
uint256 minAmountFinish;
uint256 relayerFee;
}
structBridgeInfo {
IERC20 tokenReceived;
uint256 amountReceived;
uint256 relayerFeeAmount;
}
}
Contract Source Code
File 12 of 13: ReentrancyGuard.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)pragmasolidity ^0.8.20;/**
* @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].
*/abstractcontractReentrancyGuard{
// 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.uint256privateconstant NOT_ENTERED =1;
uint256privateconstant ENTERED =2;
uint256private _status;
/**
* @dev Unauthorized reentrant call.
*/errorReentrancyGuardReentrantCall();
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.
*/modifiernonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function_nonReentrantBefore() private{
// On the first call to nonReentrant, _status will be NOT_ENTEREDif (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// 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() internalviewreturns (bool) {
return _status == ENTERED;
}
}
Contract Source Code
File 13 of 13: SafeERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)pragmasolidity ^0.8.20;import {IERC20} from"../IERC20.sol";
import {IERC20Permit} from"./IERC20Permit.sol";
import {Address} from"./Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 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.
*/librarySafeERC20{
usingAddressforaddress;
/**
* @dev An operation with an ERC-20 token failed.
*/errorSafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/errorSafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/functionsafeTransfer(IERC20 token, address to, uint256 value) internal{
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/functionsafeTransferFrom(IERC20 token, addressfrom, address to, uint256 value) internal{
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/functionsafeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal{
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/functionsafeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal{
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/functionforceApprove(IERC20 token, address spender, uint256 value) internal{
bytesmemory approvalCall =abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @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, bytesmemory 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.bytesmemory returndata =address(token).functionCall(data);
if (returndata.length!=0&&!abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @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).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/function_callOptionalReturnBool(IERC20 token, bytesmemory data) privatereturns (bool) {
// 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 cannot use {Address-functionCall} here since this should return false// and not revert is the subcall reverts.
(bool success, bytesmemory returndata) =address(token).call(data);
return success && (returndata.length==0||abi.decode(returndata, (bool))) &&address(token).code.length>0;
}
}