Skip to content
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
11e3993
test ci
waelsy123 Feb 4, 2026
4be3547
update foundry.toml
waelsy123 Feb 4, 2026
8bb6035
add master vault contracts
waelsy123 Feb 4, 2026
fb019c4
fixup! add master vault contracts
waelsy123 Feb 4, 2026
e2edd95
fix: slither reentrancy in Mastervault
waelsy123 Feb 4, 2026
2fe0126
test: add mastervault test util contracts
waelsy123 Feb 4, 2026
10a8d51
test: add mastervault tests
waelsy123 Feb 4, 2026
244c278
add yield bearing gateways
waelsy123 Feb 4, 2026
2952945
fixup! add yield bearing gateways
waelsy123 Feb 4, 2026
3f643a0
add L1YbbBridgeDeployer lib
waelsy123 Feb 5, 2026
3cef66b
introduce L1GatewayDeployer lib to reduce L1AtomicTokenBridgeCreator …
waelsy123 Feb 5, 2026
3fc04d3
fixup! introduce L1GatewayDeployer lib to reduce L1AtomicTokenBridgeC…
waelsy123 Feb 5, 2026
e114255
test: e2e Ybb deposit test
waelsy123 Feb 8, 2026
5597e1f
fix: use vault shares decimals
waelsy123 Feb 10, 2026
b26c54c
fix: initiate local roles
waelsy123 Feb 11, 2026
ce02657
fix: check 0 shares
waelsy123 Feb 11, 2026
4aadcf0
fix: add event TargetAllocationUpdated
waelsy123 Feb 11, 2026
520a371
rebalance affects profit assets too
godzillaba Feb 12, 2026
1f3e22c
test: more rebalance tests
waelsy123 Feb 13, 2026
6bf9f42
fix: remove toggle perf fee
waelsy123 Feb 13, 2026
2b0c724
fixup! test: more rebalance tests
waelsy123 Feb 13, 2026
de28387
fix: use safeIncreaseAllowance instead of safeApprove
waelsy123 Feb 13, 2026
6e0107d
refactor: use CREATE2 determinstic deployments
waelsy123 Feb 16, 2026
0f8d4d3
ybb fee-token gateways
waelsy123 Feb 17, 2026
3f49a2a
fix: defaultSubVault mint extra shares
waelsy123 Feb 18, 2026
cc6c1b5
fixup! fix: defaultSubVault mint extra shares
waelsy123 Feb 18, 2026
9654a8f
1.2.1
godzillaba Feb 19, 2026
89a4dfa
Revert "1.2.1"
godzillaba Feb 19, 2026
fc59687
properly restrict default subvault
godzillaba Feb 19, 2026
990ef5b
add mastervault role admin tests
godzillaba Feb 20, 2026
dd546bc
large mutation test file
godzillaba Feb 20, 2026
d074bf8
slight rebalance comment improvement
godzillaba Feb 23, 2026
6ab1b86
scaffold mutation test files
godzillaba Feb 23, 2026
77312db
access control tests moved
godzillaba Feb 23, 2026
b9ec81c
default subvault tests moved
godzillaba Feb 23, 2026
dde5aa9
move fees tests over
godzillaba Feb 23, 2026
4da7e91
start invariant testing
godzillaba Feb 24, 2026
e820d2f
invariant readme
godzillaba Feb 24, 2026
3dc37d0
reorganize mutation tests
godzillaba Feb 24, 2026
ae3147d
decimals test
godzillaba Feb 24, 2026
c94b7ed
no calls on zero amountToTransfer
godzillaba Feb 24, 2026
b8ac8f3
move roles tests into more comprehensive file
godzillaba Feb 24, 2026
1cad122
move only_master_vault test
godzillaba Feb 24, 2026
7044927
restore gitignore
godzillaba Feb 24, 2026
b4ae0ab
restore test files
godzillaba Feb 24, 2026
08c5321
organize tests
godzillaba Feb 24, 2026
778f1f4
remove mutation base
godzillaba Feb 24, 2026
e2e3dc5
introduce YbbVaultLib for deposit & withdraw from vault
waelsy123 Feb 24, 2026
ec1a72b
0% rebalancing special case
godzillaba Feb 24, 2026
a3149d3
refactor rebalance
godzillaba Feb 24, 2026
4eb1bf5
simpler fuzz vault
godzillaba Feb 24, 2026
4a98dfb
Merge branch 'ha/ybb-rebalance-refactor' into ha/ybb-invariant-tests
godzillaba Feb 24, 2026
51641a2
no empty expectRevert
godzillaba Feb 24, 2026
74626ed
test 0% rebalance
godzillaba Feb 25, 2026
fab56c8
Merge branch 'ha/ybb-mutation-testing' into ha/ybb-rebalance-refactor
godzillaba Feb 25, 2026
455e979
Merge branch 'ha/ybb-rebalance-refactor' into ha/ybb-invariant-tests
godzillaba Feb 25, 2026
0910434
fix stale natspec and ambiguous event
godzillaba Feb 25, 2026
e02ec94
small natspec change
godzillaba Feb 25, 2026
e274e7c
Merge branch 'ha/ybb-rebalance-refactor' into ha/ybb-invariant-tests
godzillaba Feb 25, 2026
0e61d92
rebalance to zero invariant test
godzillaba Feb 25, 2026
3801a2d
fuzz rounding error
godzillaba Feb 25, 2026
990f01a
invariant_depositRedeemNoValueExtraction
godzillaba Feb 25, 2026
b7f0905
Merge branch 'feat/yield-bearing-bridge-full' into ha/ybb-mutation-te…
godzillaba Feb 26, 2026
2a03d02
Merge branch 'ha/ybb-mutation-testing' into ha/ybb-rebalance-refactor
godzillaba Feb 26, 2026
864d982
Merge branch 'ha/ybb-rebalance-refactor' into ha/ybb-invariant-tests
godzillaba Feb 26, 2026
d5dae5b
delete readme and fuzz file
godzillaba Feb 26, 2026
50cfcdb
invariant_feeDistributionBounded
godzillaba Feb 26, 2026
0cd8883
fix conversion functions to return ideal ratios when vault is healthy
godzillaba Feb 26, 2026
5f21f5a
better invariant_feeDistributionBounded
godzillaba Feb 26, 2026
15d44f0
Merge branch 'ha/ybb-invariant-tests' into ha/ybb-ratio-drift-fix
godzillaba Feb 26, 2026
21fd31b
fix stale docs
godzillaba Feb 26, 2026
abe121b
Merge pull request #182 from OffchainLabs/ha/ybb-ratio-drift-fix
waelsy123 Feb 27, 2026
45197cc
Merge pull request #181 from OffchainLabs/ha/ybb-invariant-tests
waelsy123 Feb 27, 2026
a1ba0e6
Merge pull request #179 from OffchainLabs/ha/ybb-rebalance-refactor
waelsy123 Feb 27, 2026
b459155
Merge pull request #178 from OffchainLabs/ha/ybb-mutation-testing
godzillaba Feb 27, 2026
da59267
fix bad fsv maxWithdraw
godzillaba Feb 27, 2026
ad6630a
fix div by zero in slippage check
godzillaba Feb 27, 2026
60f4c0f
change manipulation meanings
godzillaba Feb 27, 2026
2a1b7bf
clean up fsv
godzillaba Feb 27, 2026
8f87592
rebalance idempotent
godzillaba Feb 27, 2026
0410682
invariant_redeemRateNeverAbovePar
godzillaba Feb 27, 2026
44f4973
remove confusing ghost manipulation vars
godzillaba Feb 27, 2026
15a8474
no manipulation invariants inheritance
godzillaba Feb 27, 2026
f2e4b34
invariant_rebalancePreservesTotalAssets
godzillaba Feb 27, 2026
9986968
two handlers, remove trivial tests
godzillaba Feb 27, 2026
1a0eddd
reorganize invariant tests and fix broken rebalance test
godzillaba Mar 2, 2026
618f4de
simplify handler
godzillaba Mar 2, 2026
7d5e5cd
add distribute fee value conservation test and remove useless invariants
godzillaba Mar 2, 2026
36a2a65
remove unused logstate func
godzillaba Mar 3, 2026
e96d3dc
fix visibility
godzillaba Mar 3, 2026
04fc167
fix slither
godzillaba Mar 3, 2026
8f0def3
add donation attack invariants
godzillaba Mar 3, 2026
d29598b
Merge pull request #184 from OffchainLabs/ha/ybb-more-invariant-tests
waelsy123 Mar 3, 2026
052c65d
improved donation attack tests
godzillaba Mar 3, 2026
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ Check [this doc](./docs/deployment.md) for instructions on deployment and verifi
Discord: [Arbitrum](https://discord.com/invite/5KE54JwyTs)

Twitter: [Arbitrum](https://twitter.com/arbitrum)

Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ library OrbitSalts {
bytes internal constant L1_STANDARD_GATEWAY = bytes("L1SGW");
bytes internal constant L1_CUSTOM_GATEWAY = bytes("L1CGW");
bytes internal constant L1_WETH_GATEWAY = bytes("L1WGW");
bytes internal constant L1_MASTER_VAULT_FACTORY = bytes("L1MVF");

bytes internal constant L2_PROXY_ADMIN = bytes("L2PA");
bytes internal constant L2_ROUTER = bytes("L2R");
Expand Down
311 changes: 198 additions & 113 deletions contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol

Large diffs are not rendered by default.

216 changes: 216 additions & 0 deletions contracts/tokenbridge/ethereum/L1GatewayDeployer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.4;

import {L1ERC20Gateway} from "./gateway/L1ERC20Gateway.sol";
import {L1CustomGateway} from "./gateway/L1CustomGateway.sol";
import {L1WethGateway} from "./gateway/L1WethGateway.sol";
import {L1OrbitERC20Gateway} from "./gateway/L1OrbitERC20Gateway.sol";
import {L1OrbitCustomGateway} from "./gateway/L1OrbitCustomGateway.sol";
import {L1YbbERC20Gateway} from "./gateway/L1YbbERC20Gateway.sol";
import {L1YbbCustomGateway} from "./gateway/L1YbbCustomGateway.sol";
import {L1OrbitYbbERC20Gateway} from "./gateway/L1OrbitYbbERC20Gateway.sol";
import {L1OrbitYbbCustomGateway} from "./gateway/L1OrbitYbbCustomGateway.sol";
import {IMasterVaultFactory} from "../libraries/vault/IMasterVaultFactory.sol";
import {IGatewayRouter} from "../libraries/gateway/IGatewayRouter.sol";
import {ClonableBeaconProxy} from "../libraries/ClonableBeaconProxy.sol";
import {
TransparentUpgradeableProxy
} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

/**
* @title L1GatewayDeployer
* @notice Library for deploying all L1 gateway components (standard, custom, WETH, and YBB)
*/
library L1GatewayDeployer {
// ============ Standard Gateway Structs ============

struct StandardDeploymentParams {
address inbox;
address proxyAdmin;
address upgradeExecutor;
address router;
address l2StandardGateway;
address l2CustomGateway;
address l2BeaconProxyFactory;
bool isFeeTokenBased;
}

struct StandardTemplates {
address standardGatewayTemplate;
address feeTokenBasedStandardGatewayTemplate;
address customGatewayTemplate;
address feeTokenBasedCustomGatewayTemplate;
}

struct StandardDeploymentResult {
address standardGateway;
address customGateway;
}

// ============ WETH Gateway Structs ============

struct WethDeploymentParams {
address inbox;
address proxyAdmin;
address router;
address l2WethGateway;
address l1Weth;
address l2Weth;
}

struct WethDeploymentResult {
address wethGateway;
}

// ============ YBB Gateway Structs ============

struct YbbDeploymentParams {
address inbox;
address proxyAdmin;
address upgradeExecutor;
address router;
address l2StandardGateway;
address l2CustomGateway;
address l2BeaconProxyFactory;
bool isFeeTokenBased;
}

struct YbbTemplates {
address ybbStandardGatewayTemplate;
address ybbCustomGatewayTemplate;
address feeTokenBasedYbbStandardGatewayTemplate;
address feeTokenBasedYbbCustomGatewayTemplate;
address masterVaultFactoryTemplate;
}

struct YbbDeploymentResult {
address masterVaultFactory;
address standardGateway;
address customGateway;
}

// ============ Standard Gateway Deployment ============

function deployStandardGateways(
StandardDeploymentParams memory params,
StandardTemplates memory templates,
bytes32 standardGatewaySalt,
bytes32 customGatewaySalt
) external returns (StandardDeploymentResult memory result) {
{
address template = params.isFeeTokenBased
? templates.feeTokenBasedStandardGatewayTemplate
: templates.standardGatewayTemplate;

result.standardGateway = _deployProxy(standardGatewaySalt, template, params.proxyAdmin);

L1ERC20Gateway(result.standardGateway)
.initialize(
params.l2StandardGateway,
params.router,
params.inbox,
keccak256(type(ClonableBeaconProxy).creationCode),
params.l2BeaconProxyFactory
);
}

{
address template = params.isFeeTokenBased
? templates.feeTokenBasedCustomGatewayTemplate
: templates.customGatewayTemplate;

result.customGateway = _deployProxy(customGatewaySalt, template, params.proxyAdmin);

L1CustomGateway(result.customGateway)
.initialize(
params.l2CustomGateway, params.router, params.inbox, params.upgradeExecutor
);
}

return result;
}

// ============ WETH Gateway Deployment ============

function deployWethGateway(
WethDeploymentParams memory params,
address wethGatewayTemplate,
bytes32 wethGatewaySalt
) external returns (WethDeploymentResult memory result) {
result.wethGateway = _deployProxy(wethGatewaySalt, wethGatewayTemplate, params.proxyAdmin);

L1WethGateway(payable(result.wethGateway))
.initialize(
params.l2WethGateway, params.router, params.inbox, params.l1Weth, params.l2Weth
);

return result;
}

// ============ YBB Gateway Deployment ============

function deployYbbGateways(
YbbDeploymentParams memory params,
YbbTemplates memory templates,
bytes32 masterVaultSalt,
bytes32 standardGatewaySalt,
bytes32 customGatewaySalt
) external returns (YbbDeploymentResult memory result) {
result.masterVaultFactory = _deployProxy(
masterVaultSalt, templates.masterVaultFactoryTemplate, params.proxyAdmin
);

{
address template = params.isFeeTokenBased
? templates.feeTokenBasedYbbStandardGatewayTemplate
: templates.ybbStandardGatewayTemplate;

result.standardGateway = _deployProxy(standardGatewaySalt, template, params.proxyAdmin);

L1YbbERC20Gateway(result.standardGateway)
.initialize(
params.l2StandardGateway,
params.router,
params.inbox,
keccak256(type(ClonableBeaconProxy).creationCode),
params.l2BeaconProxyFactory,
result.masterVaultFactory
);
}

{
address template = params.isFeeTokenBased
? templates.feeTokenBasedYbbCustomGatewayTemplate
: templates.ybbCustomGatewayTemplate;

result.customGateway = _deployProxy(customGatewaySalt, template, params.proxyAdmin);

L1YbbCustomGateway(result.customGateway)
.initialize(
params.l2CustomGateway,
params.router,
params.inbox,
params.upgradeExecutor,
result.masterVaultFactory
);
}

return result;
}

function initializeMasterVaultFactory(
address masterVaultFactory,
address masterVaultImplementation,
address admin,
address router
) external {
IMasterVaultFactory(masterVaultFactory)
.initialize(masterVaultImplementation, admin, IGatewayRouter(router));
}

// ============ Internal ============

function _deployProxy(bytes32 salt, address logic, address admin) internal returns (address) {
return address(new TransparentUpgradeableProxy{salt: salt}(logic, admin, bytes("")));
}
}
2 changes: 1 addition & 1 deletion contracts/tokenbridge/ethereum/gateway/L1ERC20Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ contract L1ERC20Gateway is L1ArbitrumExtendedGateway {
address _to,
uint256 _amount,
bytes memory _data
) public view override returns (bytes memory outboundCalldata) {
) public view virtual override returns (bytes memory outboundCalldata) {
// TODO: cheaper to make static calls or save isDeployed to storage?
bytes memory deployData = abi.encode(
callStatic(_token, ERC20.name.selector),
Expand Down
61 changes: 61 additions & 0 deletions contracts/tokenbridge/ethereum/gateway/L1OrbitYbbCustomGateway.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.0;

import {L1OrbitCustomGateway} from "./L1OrbitCustomGateway.sol";
import {L1CustomGateway} from "./L1CustomGateway.sol";
import {IMasterVault} from "../../libraries/vault/IMasterVault.sol";
import {IMasterVaultFactory} from "../../libraries/vault/IMasterVaultFactory.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
* @title Layer 1 Gateway contract for bridging Custom ERC20s with YBB enabled in ERC20-based rollup
* @notice Escrows funds into MasterVaults for yield bearing bridging.
*/
contract L1OrbitYbbCustomGateway is L1OrbitCustomGateway {
using SafeERC20 for IERC20;

/// @notice Address of the MasterVaultFactory contract
address public masterVaultFactory;

function initialize(
address _l1Counterpart,
address _l1Router,
address _inbox,
address _owner,
address _masterVaultFactory
) public virtual {
L1CustomGateway.initialize(_l1Counterpart, _l1Router, _inbox, _owner);
_setMasterVaultFactory(_masterVaultFactory);
}

function inboundEscrowTransfer(address _l1Token, address _dest, uint256 _amount)
internal
override
{
address masterVault = IMasterVaultFactory(masterVaultFactory).getVault(_l1Token);
IERC20(masterVault).safeTransfer(_dest, _amount);
}

function outboundEscrowTransfer(address _l1Token, address _from, uint256 _amount)
internal
override
returns (uint256 amountReceived)
{
uint256 prevBalance = IERC20(_l1Token).balanceOf(address(this));
IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount);
uint256 postBalance = IERC20(_l1Token).balanceOf(address(this));
amountReceived = postBalance - prevBalance;

address masterVault = IMasterVaultFactory(masterVaultFactory).getVault(_l1Token);
IERC20(_l1Token).safeIncreaseAllowance(masterVault, amountReceived);
amountReceived = IMasterVault(masterVault).deposit(amountReceived);
require(amountReceived > 0, "ZERO_SHARES");
}

function _setMasterVaultFactory(address _masterVaultFactory) internal {
require(_masterVaultFactory != address(0), "BAD_MASTER_VAULT_FACTORY");
masterVaultFactory = _masterVaultFactory;
}
Comment on lines +48 to +51
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bit of a nit, but even though it's a sortof convention in this codebase idk if an address(0) check is very useful

if we keep it though i think it should inlined in the initializer

}
91 changes: 91 additions & 0 deletions contracts/tokenbridge/ethereum/gateway/L1OrbitYbbERC20Gateway.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.0;

import {L1OrbitERC20Gateway} from "./L1OrbitERC20Gateway.sol";
import {L1ERC20Gateway} from "./L1ERC20Gateway.sol";
import {IMasterVault} from "../../libraries/vault/IMasterVault.sol";
import {IMasterVaultFactory} from "../../libraries/vault/IMasterVaultFactory.sol";
import {IERC20, ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {GatewayMessageHandler} from "../../libraries/gateway/GatewayMessageHandler.sol";
import {ITokenGateway} from "../../libraries/gateway/ITokenGateway.sol";

/**
* @title Layer 1 Gateway contract for bridging standard ERC20s with YBB enabled in ERC20-based rollup
* @notice Escrows funds into MasterVaults for yield bearing bridging.
*/
contract L1OrbitYbbERC20Gateway is L1OrbitERC20Gateway {
using SafeERC20 for IERC20;

/// @notice Address of the MasterVaultFactory contract
address public masterVaultFactory;

function initialize(
address _l2Counterpart,
address _router,
address _inbox,
bytes32 _cloneableProxyHash,
address _l2BeaconProxyFactory,
address _masterVaultFactory
) public {
L1ERC20Gateway.initialize(
_l2Counterpart, _router, _inbox, _cloneableProxyHash, _l2BeaconProxyFactory
);
_setMasterVaultFactory(_masterVaultFactory);
}

function inboundEscrowTransfer(address _l1Token, address _dest, uint256 _amount)
internal
override
{
address masterVault = IMasterVaultFactory(masterVaultFactory).getVault(_l1Token);
IERC20(masterVault).safeTransfer(_dest, _amount);
}

function outboundEscrowTransfer(address _l1Token, address _from, uint256 _amount)
internal
override
returns (uint256 amountReceived)
{
uint256 prevBalance = IERC20(_l1Token).balanceOf(address(this));
IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount);
uint256 postBalance = IERC20(_l1Token).balanceOf(address(this));
amountReceived = postBalance - prevBalance;

address masterVault = IMasterVaultFactory(masterVaultFactory).getVault(_l1Token);
IERC20(_l1Token).safeIncreaseAllowance(masterVault, amountReceived);
amountReceived = IMasterVault(masterVault).deposit(amountReceived);
require(amountReceived > 0, "ZERO_SHARES");
}

function getOutboundCalldata(
address _token,
address _from,
address _to,
uint256 _amount,
bytes memory _data
) public view override returns (bytes memory outboundCalldata) {
address vault = IMasterVaultFactory(masterVaultFactory).calculateVaultAddress(_token);

bytes memory deployData = abi.encode(
callStatic(_token, ERC20.name.selector),
callStatic(_token, ERC20.symbol.selector),
callStatic(vault, ERC20.decimals.selector)
);

outboundCalldata = abi.encodeWithSelector(
ITokenGateway.finalizeInboundTransfer.selector,
_token,
_from,
_to,
_amount,
GatewayMessageHandler.encodeToL2GatewayMsg(deployData, _data)
);
}

function _setMasterVaultFactory(address _masterVaultFactory) internal {
require(_masterVaultFactory != address(0), "BAD_MASTER_VAULT_FACTORY");
masterVaultFactory = _masterVaultFactory;
}
}
Loading
Loading