Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ EURC_OWNER_ADDRESS=0x498581fF718922c3f8e6A244956aF099B2652b2b
WETH_OWNER_ADDRESS=0x498581fF718922c3f8e6A244956aF099B2652b2b
PRIME_OWNER_ADDRESS=0x75a44A70cCb0E886E25084Be14bD45af57915451
USDC_OWNER_ETH_ADDRESS=0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf
DAI_OWNER_ETH_ADDRESS=0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf
WBTC_OWNER_ETH_ADDRESS=0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf
6 changes: 3 additions & 3 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,12 @@ jobs:
echo ""

# Check 2: CI must be >= main baseline (coverage didn't decrease)
echo "Check 2: Did coverage decrease?"
if awk "BEGIN {exit !($CI_LINES >= $MAIN_LINES)}"; then
echo "Check 2: Did coverage decrease? 0.5% tolerance"
if awk "BEGIN {exit !($CI_LINES >= ($MAIN_LINES - 0.5))}"; then
if awk "BEGIN {exit !($CI_LINES > $MAIN_LINES)}"; then
echo " ✅ PASS - Coverage improved! ($MAIN_LINES% → $CI_LINES%)"
else
echo " ✅ PASS - Coverage maintained ($CI_LINES%)"
echo " ✅ PASS - Coverage maintained within tolerance ($CI_LINES%)"
fi
else
echo " ❌ FAIL - Coverage decreased!"
Expand Down
19 changes: 17 additions & 2 deletions contracts/Repayer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {AcrossAdapter} from "./utils/AcrossAdapter.sol";
import {StargateAdapter} from "./utils/StargateAdapter.sol";
import {EverclearAdapter} from "./utils/EverclearAdapter.sol";
import {SuperchainStandardBridgeAdapter} from "./utils/SuperchainStandardBridgeAdapter.sol";
import {ArbitrumGatewayAdapter} from "./utils/ArbitrumGatewayAdapter.sol";
import {ERC7201Helper} from "./utils/ERC7201Helper.sol";

/// @title Performs repayment to Liquidity Pools on same/different chains.
Expand All @@ -28,7 +29,8 @@ contract Repayer is
AcrossAdapter,
StargateAdapter,
EverclearAdapter,
SuperchainStandardBridgeAdapter
SuperchainStandardBridgeAdapter,
ArbitrumGatewayAdapter
{
using SafeERC20 for IERC20;
using BitMaps for BitMaps.BitMap;
Expand Down Expand Up @@ -95,13 +97,15 @@ contract Repayer is
address wrappedNativeToken,
address stargateTreasurer,
address optimismBridge,
address baseBridge
address baseBridge,
address arbitrumGatewayRouter
)
CCTPAdapter(cctpTokenMessenger, cctpMessageTransmitter)
AcrossAdapter(acrossSpokePool)
StargateAdapter(stargateTreasurer)
EverclearAdapter(everclearFeeAdapter)
SuperchainStandardBridgeAdapter(optimismBridge, baseBridge, wrappedNativeToken)
ArbitrumGatewayAdapter(arbitrumGatewayRouter)
{
ERC7201Helper.validateStorageLocation(
STORAGE_LOCATION,
Expand Down Expand Up @@ -225,6 +229,17 @@ contract Repayer is
DOMAIN,
$.inputOutputTokens[address(token)]
);
} else
if (provider == Provider.ARBITRUM_GATEWAY) {
initiateTransferArbitrum(
token,
amount,
destinationPool,
destinationDomain,
extraData,
DOMAIN,
$.inputOutputTokens[address(token)]
);
} else {
// Unreachable atm, but could become so when more providers are added to enum.
revert UnsupportedProvider();
Expand Down
41 changes: 41 additions & 0 deletions contracts/interfaces/IArbitrumGatewayRouter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.28;

/**
* @title Interface for Arbitrum Gateway Router
*/
interface IArbitrumGatewayRouter {

event TransferRouted(
address indexed token,
address indexed _userFrom,
address indexed _userTo,
address gateway
);

/**
* @notice For new versions of gateways it's recommended to use outboundTransferCustomRefund() method.
* @notice Some legacy gateways (for example, DAI) don't have the outboundTransferCustomRefund method
* @notice so using outboundTransfer() method is a universal solution
*/
function outboundTransfer(
address _token,
address _to,
uint256 _amount,
uint256 _maxGas,
uint256 _gasPriceBid,
bytes calldata _data
) external payable returns (bytes memory);

/**
* @notice Calculate the address used when bridging an ERC20 token
* @dev the L1 and L2 address oracles may not always be in sync.
* For example, a custom token may have been registered but not deploy or the contract self destructed.
* @param l1ERC20 address of L1 token
* @return L2 address of a bridged ERC20 token
*/
function calculateL2TokenAddress(address l1ERC20) external view returns (address);

function getGateway(address _token) external view returns (address gateway);
}
3 changes: 2 additions & 1 deletion contracts/interfaces/IRoute.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ interface IRoute {
ACROSS,
STARGATE,
EVERCLEAR,
SUPERCHAIN_STANDARD_BRIDGE
SUPERCHAIN_STANDARD_BRIDGE,
ARBITRUM_GATEWAY
}

enum PoolType {
Expand Down
42 changes: 42 additions & 0 deletions contracts/testing/TestArbitrum.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.28;

import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IArbitrumGatewayRouter} from "../interfaces/IArbitrumGatewayRouter.sol";

contract TestArbitrumGatewayRouter is IArbitrumGatewayRouter {

address public immutable LOCAL_TOKEN;
address public immutable L2_TOKEN;

error InvalidToken();
error SimulatedRevert();

constructor(address _localtoken, address _l2token) {
LOCAL_TOKEN = _localtoken;
L2_TOKEN = _l2token;
}

function calculateL2TokenAddress(address) external view override returns (address) {
return L2_TOKEN;
}

function getGateway(address) external view returns (address gateway) {
return address(this);
}

function outboundTransfer(
address _token,
address _to,
uint256 _amount,
uint256,
uint256,
bytes calldata
) external payable returns (bytes memory) {
require(_token == LOCAL_TOKEN, InvalidToken());
require(_amount != 2000, SimulatedRevert());
SafeERC20.safeTransferFrom(IERC20(LOCAL_TOKEN), msg.sender, address(this), _amount);
emit TransferRouted(LOCAL_TOKEN, msg.sender, _to, address(this));
return "GATEWAY_DATA";
}
}
6 changes: 4 additions & 2 deletions contracts/testing/TestRepayer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ contract TestRepayer is Repayer {
address wrappedNativeToken,
address stargateTreasurer,
address optimismBridge,
address baseBridge
address baseBridge,
address arbitrumGatewayRouter
) Repayer(
localDomain,
assets,
Expand All @@ -25,7 +26,8 @@ contract TestRepayer is Repayer {
wrappedNativeToken,
stargateTreasurer,
optimismBridge,
baseBridge
baseBridge,
arbitrumGatewayRouter
) {}

function domainCCTP(Domain destinationDomain) public pure override returns (uint32) {
Expand Down
57 changes: 57 additions & 0 deletions contracts/utils/ArbitrumGatewayAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.8.28;

import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IArbitrumGatewayRouter} from ".././interfaces/IArbitrumGatewayRouter.sol";
import {AdapterHelper} from "./AdapterHelper.sol";

abstract contract ArbitrumGatewayAdapter is AdapterHelper {
using SafeERC20 for IERC20;

IArbitrumGatewayRouter immutable public ARBITRUM_GATEWAY_ROUTER;

event ArbitrumERC20TransferInitiated(bytes gatewayData);

constructor(
address arbitrumGatewayRouter
) {
// No check for address(0) to allow deployment on chains where Arbitrum Bridge is not available
ARBITRUM_GATEWAY_ROUTER = IArbitrumGatewayRouter(arbitrumGatewayRouter);
}

function initiateTransferArbitrum(
IERC20 token,
uint256 amount,
address destinationPool,
Domain destinationDomain,
bytes calldata extraData,
Domain localDomain,
mapping(bytes32 => BitMaps.BitMap) storage outputTokens
) internal {
// We are only interested in fast L1->L2 bridging, because the reverse is slow.
require(localDomain == Domain.ETHEREUM, UnsupportedDomain());
require(destinationDomain == Domain.ARBITRUM_ONE, UnsupportedDomain());
IArbitrumGatewayRouter router = ARBITRUM_GATEWAY_ROUTER;
Comment thread
lastperson marked this conversation as resolved.
require(address(router) != address(0), ZeroAddress());
(address outputToken, uint256 maxGas, uint256 gasPriceBid, bytes memory data) =
abi.decode(extraData, (address, uint256, uint256, bytes));

_validateOutputToken(_addressToBytes32(outputToken), destinationDomain, outputTokens);
// Get output token from the gateway
address gatewayOutputToken = router.calculateL2TokenAddress(address(token));
// Check that output tokens match
require(gatewayOutputToken == outputToken, InvalidOutputToken());
address gateway = router.getGateway(address(token));
token.forceApprove(gateway, amount);
bytes memory gatewayData = router.outboundTransfer{value: msg.value}(
address(token),
destinationPool,
amount,
maxGas,
gasPriceBid,
data
);
emit ArbitrumERC20TransferInitiated(gatewayData);
}
}
8 changes: 4 additions & 4 deletions coverage-baseline.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"lines": "96.88",
"functions": "98.57",
"branches": "87.76",
"statements": "96.88"
"lines": "96.82",
"functions": "98.58",
"branches": "87.96",
"statements": "97.29"
}
21 changes: 16 additions & 5 deletions network.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export enum Provider {
EVERCLEAR = "EVERCLEAR",
STARGATE = "STARGATE",
SUPERCHAIN_STANDARD_BRIDGE = "SUPERCHAIN_STANDARD_BRIDGE",
ARBITRUM_GATEWAY = "ARBITRUM_GATEWAY",
}

export enum Token {
Expand Down Expand Up @@ -170,6 +171,7 @@ export interface NetworkConfig {
EverclearFeeAdapter?: string;
OptimismStandardBridge?: string;
BaseStandardBridge?: string;
ArbitrumGatewayRouter?: string;
Tokens: {
[Token.USDC]: string;
[Token.USDT]?: string;
Expand Down Expand Up @@ -220,6 +222,7 @@ export const networkConfig: NetworksConfig = {
EverclearFeeAdapter: "0xd0185bfb8107c5b2336bC73cE3fdd9Bfb504540e",
OptimismStandardBridge: "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1",
BaseStandardBridge: "0x3154Cf16ccdb4C6d922629664174b904d80F2C35",
ArbitrumGatewayRouter: "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef",
Tokens: {
USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
USDT: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
Expand Down Expand Up @@ -263,7 +266,13 @@ export const networkConfig: NetworksConfig = {
Provider.SUPERCHAIN_STANDARD_BRIDGE,
Provider.STARGATE,
],
[Network.ARBITRUM_ONE]: [Provider.CCTP, Provider.ACROSS, Provider.EVERCLEAR, Provider.STARGATE],
[Network.ARBITRUM_ONE]: [
Provider.CCTP,
Provider.ACROSS,
Provider.EVERCLEAR,
Provider.STARGATE,
Provider.ARBITRUM_GATEWAY
],
[Network.BASE]: [
Provider.CCTP,
Provider.ACROSS,
Expand All @@ -282,7 +291,7 @@ export const networkConfig: NetworksConfig = {
Provider.EVERCLEAR,
Provider.SUPERCHAIN_STANDARD_BRIDGE
],
[Network.ARBITRUM_ONE]: [Provider.CCTP, Provider.ACROSS, Provider.EVERCLEAR],
[Network.ARBITRUM_ONE]: [Provider.CCTP, Provider.ACROSS, Provider.EVERCLEAR, Provider.ARBITRUM_GATEWAY],
[Network.BASE]: [Provider.CCTP, Provider.ACROSS, Provider.EVERCLEAR, Provider.SUPERCHAIN_STANDARD_BRIDGE],
},
},
Expand Down Expand Up @@ -318,6 +327,7 @@ export const networkConfig: NetworksConfig = {
EverclearFeeAdapter: "0xd0185bfb8107c5b2336bC73cE3fdd9Bfb504540e",
OptimismStandardBridge: "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1",
BaseStandardBridge: "0x3154Cf16ccdb4C6d922629664174b904d80F2C35",
ArbitrumGatewayRouter: "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef",
Tokens: {
USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
USDT: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
Expand Down Expand Up @@ -360,7 +370,7 @@ export const networkConfig: NetworksConfig = {
[LiquidityPoolAaveUSDCV4]: {
SupportsAllTokens: true,
Domains: {
[Network.ARBITRUM_ONE]: [Provider.CCTP, Provider.ACROSS, Provider.EVERCLEAR],
[Network.ARBITRUM_ONE]: [Provider.CCTP, Provider.ACROSS, Provider.EVERCLEAR, Provider.ARBITRUM_GATEWAY],
[Network.BASE]: [Provider.CCTP, Provider.ACROSS, Provider.EVERCLEAR, Provider.SUPERCHAIN_STANDARD_BRIDGE],
[Network.OP_MAINNET]: [
Provider.CCTP,
Expand All @@ -384,7 +394,7 @@ export const networkConfig: NetworksConfig = {
[LiquidityPoolUSDCV4]: {
SupportsAllTokens: false,
Domains: {
[Network.ARBITRUM_ONE]: [Provider.CCTP, Provider.ACROSS, Provider.EVERCLEAR],
[Network.ARBITRUM_ONE]: [Provider.CCTP, Provider.ACROSS, Provider.EVERCLEAR, Provider.ARBITRUM_GATEWAY],
[Network.BASE]: [
Provider.CCTP,
Provider.ACROSS,
Expand All @@ -402,7 +412,7 @@ export const networkConfig: NetworksConfig = {
[LiquidityPoolAaveUSDCLongTermV3]: {
SupportsAllTokens: true,
Domains: {
[Network.ARBITRUM_ONE]: [Provider.CCTP, Provider.ACROSS, Provider.EVERCLEAR],
[Network.ARBITRUM_ONE]: [Provider.CCTP, Provider.ACROSS, Provider.EVERCLEAR, Provider.ARBITRUM_GATEWAY],
},
},
},
Expand Down Expand Up @@ -1554,6 +1564,7 @@ export interface StandaloneRepayerConfig {
EverclearFeeAdapter?: string;
OptimismStandardBridge?: string;
BaseStandardBridge?: string;
ArbitrumGatewayRouter?: string;
// Repayer tokens are used from the general network config.
WrappedNativeToken: string;
RepayerRoutes: RepayerRoutesConfig;
Expand Down
2 changes: 2 additions & 0 deletions scripts/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const ProviderSolidity = {
STARGATE: 3n,
EVERCLEAR: 4n,
SUPERCHAIN_STANDARD_BRIDGE: 5n,
ARBITRUM_GATEWAY: 6n,
};

export const DomainSolidity = {
Expand Down Expand Up @@ -93,6 +94,7 @@ export const SolidityProvider: { [n: number]: Provider } = {
3: Provider.STARGATE,
4: Provider.EVERCLEAR,
5: Provider.SUPERCHAIN_STANDARD_BRIDGE,
6: Provider.ARBITRUM_GATEWAY,
};

export const CCTPDomain: { [n: number]: Network } = {
Expand Down
4 changes: 4 additions & 0 deletions scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ export async function main() {
if (!config.BaseStandardBridge) {
config.BaseStandardBridge = ZERO_ADDRESS;
}
if (!config.ArbitrumGatewayRouter) {
config.ArbitrumGatewayRouter = ZERO_ADDRESS;
}

let mainPool: LiquidityPool | undefined = undefined;
let aavePoolLongTerm: LiquidityPoolAaveLongTerm;
Expand Down Expand Up @@ -412,6 +415,7 @@ export async function main() {
config.StargateTreasurer,
config.OptimismStandardBridge,
config.BaseStandardBridge,
config.ArbitrumGatewayRouter,
],
[
config.Admin,
Expand Down
Loading
Loading