Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(SpokePoolPeriphery): Support multiple exchanges #777

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Changes from 4 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
db4bf7b
feat(SpokePoolPeriphery): Support multiple exchanges
nicholaspai Nov 27, 2024
df04404
rename
nicholaspai Nov 27, 2024
1954009
Update SpokeV3PoolPeriphery.sol
nicholaspai Nov 27, 2024
fc1ff8f
Update SpokeV3PoolPeriphery.sol
nicholaspai Nov 27, 2024
c1b6d4f
Update SpokeV3PoolPeriphery.sol
nicholaspai Nov 27, 2024
3b3352e
Add unit tests
nicholaspai Nov 27, 2024
aca4b06
Add whitelistExchanges only owner method
nicholaspai Nov 27, 2024
7149287
rename
nicholaspai Nov 27, 2024
dda8499
Remove onlyOwner
nicholaspai Nov 27, 2024
2da9c63
Remove whitelist of exchanges, add proxy to bypass approval abuse
nicholaspai Nov 28, 2024
90e7cd0
Add some protection to callSpokePoolPeriphery
nicholaspai Nov 28, 2024
9511666
Only call swapAndBridge through proxy
nicholaspai Dec 1, 2024
e0bead2
move periphery funcs into proxy
nicholaspai Dec 2, 2024
5494ee5
Update SpokePoolV3Periphery.sol
nicholaspai Dec 3, 2024
b6db47b
remove depositERC20
nicholaspai Dec 3, 2024
66df238
Merge branch 'master' into spokepool-periphery-multiple-exchanges
nicholaspai Dec 4, 2024
d0a9d0f
Update SpokePoolV3Periphery.sol
nicholaspai Dec 4, 2024
6635803
Add back safeTransferFron's to permit funcs
nicholaspai Dec 4, 2024
6b995e5
Merge branch 'master' into spokepool-periphery-multiple-exchanges
nicholaspai Dec 4, 2024
0de384e
Add unit tests that check if calling deposit and swapAndBridge with n…
nicholaspai Dec 5, 2024
100e707
Add interfaces to make sure we don't add new functions as easily
nicholaspai Dec 5, 2024
6db7d87
Add Create2Factory
nicholaspai Dec 6, 2024
3a16809
Merge branch 'master' into spokepool-periphery-multiple-exchanges
nicholaspai Dec 6, 2024
022a8ec
feat: add permit2 entrypoints to the periphery (#782)
bmzig Dec 6, 2024
372d9cb
Merge branch 'master' into spokepool-periphery-multiple-exchanges
nicholaspai Dec 9, 2024
c9017ff
feat: sponsored swap and deposits (#790)
bmzig Dec 18, 2024
9bc7d91
feat: Delete SwapAndBridge and add submission fees to gasless flow (#…
nicholaspai Dec 19, 2024
f4250d0
Update SpokePoolV3Periphery.sol
nicholaspai Dec 19, 2024
9a4ef73
Update SpokePoolPeriphery.t.sol
nicholaspai Dec 19, 2024
26110a9
fix: eip712 types and hashes (#821)
dohaki Dec 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,27 @@ import { WETH9Interface } from "./external/interfaces/WETH9Interface.sol";

/**
* @title SpokePoolV3Periphery
* @notice Contract for performing more complex interactions with an AcrossV3 spoke pool deployment.
* @dev Variables which may be immutable are not marked as immutable, nor defined in the constructor, so that this contract may be deployed deterministically.
* @notice Contract for performing more complex interactions with an Across spoke pool deployment.
* @dev Variables which may be immutable are not marked as immutable, nor defined in the constructor, so that this
* contract may be deployed deterministically at the same address across different networks.
* @custom:security-contact [email protected]
*/
contract SpokePoolV3Periphery is Lockable, MultiCaller {
using SafeERC20 for IERC20;
using Address for address;

// This contract performs a low level call with arbirary data to an external contract. This is a large attack
// surface and we should whitelist which function selectors are allowed to be called on the exchange.
mapping(bytes4 => bool) public allowedSelectors;
// surface and we should whitelist which function selectors are allowed to be called on which exchange.
mapping(address => mapping(bytes4 => bool)) public allowedSelectors;

struct WhitelistedExchanges {
address exchange;
bytes4[] allowedSelectors;
}

// Across SpokePool we'll submit deposits to with acrossInputToken as the input token.
V3SpokePoolInterface public spokePool;

// Exchange address or router where the swapping will happen.
address public exchange;

// Wrapped native token contract address.
WETH9Interface internal wrappedNativeToken;

Expand Down Expand Up @@ -87,40 +90,47 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {
error InvalidMsgValue();
error InvalidSpokePool();
error InvalidSwapToken();
error InvalidExchange();

/**
* @notice Construct a new SwapAndBridgeBase contract.
* @param _allowedSelectors Function selectors that are allowed to be called on the exchange.
* @dev Is empty and all of the state variables are initialized in the initialize function
* to allow for deployment at a deterministic address via create2, which requires that the bytecode
* across different networks is the same. Constructor parameters affect the bytecode so we can only
* add parameters here that are consistent across networks.
*/
constructor(bytes4[] memory _allowedSelectors) {
for (uint256 i = 0; i < _allowedSelectors.length; i++) {
allowedSelectors[_allowedSelectors[i]] = true;
}
}
constructor() {}

/**
* @notice Initializes the SwapAndBridgeBase contract.
* @param _spokePool Address of the SpokePool contract that we'll submit deposits to.
* @param _wrappedNativeToken Address of the wrapped native token for the network this contract is deployed to.
* @param _exchange Address of the exchange where tokens will be swapped.
* @param exchanges Array of exchange addresses and their allowed function selectors.
* @dev These values are initialized in a function and not in the constructor so that the creation code of this contract
* is the same across networks with different addresses for the wrapped native token, the exchange this contract uses to
* swap and bridge, and this network's corresponding spoke pool contract. This is to allow this contract to be deterministically
* deployed with CREATE2.
* @dev This function can be front-run by anybody, so it is critical to check that the `spokePool`, `wrappedNativeToken`, and `exchange`
* values used in the single call to this function were passed in correctly before enabling the usage of this contract.
* is the same across networks with different addresses for the wrapped native token and this network's
* corresponding spoke pool contract. This is to allow this contract to be deterministically deployed with CREATE2.
* @dev This function can be front-run by anybody, so it is critical to check that the values used in the
* single call to this function were passed in correctly before enabling the usage of this contract.
*/
function initialize(
V3SpokePoolInterface _spokePool,
WETH9Interface _wrappedNativeToken,
address _exchange
WhitelistedExchanges[] calldata exchanges
) external {
if (initialized) revert ContractInitialized();
initialized = true;

if (!address(_spokePool).isContract()) revert InvalidSpokePool();
spokePool = _spokePool;
wrappedNativeToken = _wrappedNativeToken;
exchange = _exchange;
for (uint256 i = 0; i < exchanges.length; i++) {
nicholaspai marked this conversation as resolved.
Show resolved Hide resolved
WhitelistedExchanges memory _exchange = exchanges[i];
if (!_exchange.exchange.isContract()) revert InvalidExchange();
for (uint256 j = 0; j < _exchange.allowedSelectors.length; j++) {
bytes4 selector = _exchange.allowedSelectors[j];
allowedSelectors[_exchange.exchange][selector] = true;
}
}
}

/**
Expand Down Expand Up @@ -157,7 +167,6 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {
bytes memory message
) external payable nonReentrant {
if (msg.value != inputAmount) revert InvalidMsgValue();
if (!address(spokePool).isContract()) revert InvalidSpokePool();
// Set msg.sender as the depositor so that msg.sender can speed up the deposit.
spokePool.depositV3{ value: msg.value }(
msg.sender,
Expand All @@ -184,6 +193,7 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {
* the assumption is that this function will handle only ERC20 tokens.
* @param swapToken Address of the token that will be swapped for acrossInputToken.
* @param acrossInputToken Address of the token that will be bridged via Across as the inputToken.
* @param exchange Address of the exchange contract to call.
* @param routerCalldata ABI encoded function data to call on router. Should form a swap of swapToken for
* enough of acrossInputToken, otherwise this function will revert.
* @param swapTokenAmount Amount of swapToken to swap for a minimum amount of depositData.inputToken.
Expand All @@ -194,6 +204,7 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {
function swapAndBridge(
IERC20 swapToken,
IERC20 acrossInputToken,
address exchange,
bytes calldata routerCalldata,
uint256 swapTokenAmount,
uint256 minExpectedInputTokenAmount,
Expand All @@ -209,6 +220,7 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {
swapToken.safeTransferFrom(msg.sender, address(this), swapTokenAmount);
}
_swapAndBridge(
exchange,
routerCalldata,
swapTokenAmount,
minExpectedInputTokenAmount,
Expand All @@ -224,6 +236,7 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {
* @dev If swapToken does not implement `permit` to the specifications of EIP-2612, this function will fail.
* @param swapToken Address of the token that will be swapped for acrossInputToken.
* @param acrossInputToken Address of the token that will be bridged via Across as the inputToken.
* @param exchange Address of the exchange contract to call.
* @param routerCalldata ABI encoded function data to call on router. Should form a swap of swapToken for
* enough of acrossInputToken, otherwise this function will revert.
* @param swapTokenAmount Amount of swapToken to swap for a minimum amount of depositData.inputToken.
Expand All @@ -238,6 +251,7 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {
function swapAndBridgeWithPermit(
IERC20Permit swapToken,
IERC20 acrossInputToken,
address exchange,
bytes calldata routerCalldata,
uint256 swapTokenAmount,
uint256 minExpectedInputTokenAmount,
Expand All @@ -255,6 +269,7 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {

_swapToken.safeTransferFrom(msg.sender, address(this), swapTokenAmount);
_swapAndBridge(
exchange,
routerCalldata,
swapTokenAmount,
minExpectedInputTokenAmount,
Expand All @@ -270,6 +285,7 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {
* @dev If swapToken does not implement `receiveWithAuthorization` to the specifications of EIP-3009, this call will revert.
* @param swapToken Address of the token that will be swapped for acrossInputToken.
* @param acrossInputToken Address of the token that will be bridged via Across as the inputToken.
* @param exchange Address of the exchange contract to call.
* @param routerCalldata ABI encoded function data to call on router. Should form a swap of swapToken for
* enough of acrossInputToken, otherwise this function will revert.
* @param swapTokenAmount Amount of swapToken to swap for a minimum amount of depositData.inputToken.
Expand All @@ -286,6 +302,7 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {
function swapAndBridgeWithAuthorization(
IERC20Auth swapToken,
IERC20 acrossInputToken,
address exchange,
bytes calldata routerCalldata,
uint256 swapTokenAmount,
uint256 minExpectedInputTokenAmount,
Expand Down Expand Up @@ -314,6 +331,7 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {
IERC20 _swapToken = IERC20(address(swapToken)); // Cast IERC20Auth to IERC20.

_swapAndBridge(
exchange,
routerCalldata,
swapTokenAmount,
minExpectedInputTokenAmount,
Expand Down Expand Up @@ -422,6 +440,7 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {

// This contract supports two variants of swap and bridge, one that allows one token and another that allows the caller to pass them in.
function _swapAndBridge(
address exchange,
bytes calldata routerCalldata,
uint256 swapTokenAmount,
uint256 minExpectedInputTokenAmount,
Expand All @@ -432,7 +451,7 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {
// Note: this check should never be impactful, but is here out of an abundance of caution.
// For example, if the exchange address in the contract is also an ERC20 token that is approved by some
// user on this contract, a malicious actor could call transferFrom to steal the user's tokens.
if (!allowedSelectors[bytes4(routerCalldata)]) revert InvalidFunctionSelector();
if (!allowedSelectors[exchange][bytes4(routerCalldata)]) revert InvalidFunctionSelector();

// Swap and run safety checks.
uint256 srcBalanceBefore = _swapToken.balanceOf(address(this));
Expand All @@ -444,6 +463,7 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {
require(success, string(result));

_checkSwapOutputAndDeposit(
exchange,
swapTokenAmount,
srcBalanceBefore,
dstBalanceBefore,
Expand All @@ -462,6 +482,7 @@ contract SpokePoolV3Periphery is Lockable, MultiCaller {
* @param minExpectedInputTokenAmount Minimum amount of received acrossInputToken that we'll bridge
**/
function _checkSwapOutputAndDeposit(
address exchange,
uint256 swapTokenAmount,
uint256 swapTokenBalanceBefore,
uint256 inputTokenBalanceBefore,
Expand Down
Loading