Skip to content

Commit ee4dfa4

Browse files
authored
Merge pull request #110 from lista-dao/feat/lista-oft
[feat] Configure Lista OFT v2 bridge
2 parents a7d762a + 30a31e1 commit ee4dfa4

24 files changed

Lines changed: 2147 additions & 7 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ out
3232
!/broadcast
3333
/broadcast/*/31337/
3434
/broadcast/*/97/
35+
/broadcast/*/11155111/
3536
/broadcast/**/dry-run/
3637

3738
oftChains.json
455 KB
Binary file not shown.
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import { OFTAdapterUpgradeable } from "@layerzerolabs/oft-evm-upgradeable/contracts/oft/OFTAdapterUpgradeable.sol";
5+
import { EnforcedOptionParam } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppOptionsType3.sol";
6+
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
7+
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
8+
import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
9+
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
10+
11+
import { TransferLimiterV2 } from "./TransferLimiterV2.sol";
12+
13+
/**
14+
* @title ListaOFTAdapterV2
15+
* @notice Upgradeable LayerZero V2 OFT Adapter for the canonical Lista (LISTA)
16+
* ERC20 token that lives natively on BNB Chain.
17+
*
18+
* @dev Bridging model:
19+
* Native BSC LISTA --(lock)--> ListaOFTAdapterV2 --> LayerZero --> ListaOFTv2 (mint) @ ETH
20+
* and the reverse on the way back (burn @ ETH -> unlock @ BSC).
21+
*
22+
* This is a *lockbox* adapter: the canonical supply is escrowed inside this
23+
* contract and a 1:1 backed representation is minted on remote chains. Per
24+
* LayerZero rules ONLY ONE OFT Adapter may exist for a given token mesh.
25+
*
26+
* Access control:
27+
* - DEFAULT_ADMIN_ROLE : upgrade authority, role administration + setPeer
28+
* (the cross-chain trust anchor).
29+
* - MANAGER : LayerZero OApp configuration (enforced options, msg
30+
* inspector, precrime), transfer limiter
31+
* configuration and unpause().
32+
* - PAUSER : pause() the bridge in an emergency.
33+
*
34+
* Upgradeability:
35+
* - UUPS; only DEFAULT_ADMIN_ROLE may authorize an implementation upgrade.
36+
* - `token` and `lzEndpoint` are immutables baked into the implementation
37+
* bytecode by the LayerZero base contracts, hence supplied to the
38+
* constructor and reused across upgrades.
39+
*/
40+
contract ListaOFTAdapterV2 is
41+
OFTAdapterUpgradeable,
42+
AccessControlUpgradeable,
43+
PausableUpgradeable,
44+
UUPSUpgradeable,
45+
TransferLimiterV2
46+
{
47+
// @notice can pause the bridge in an emergency
48+
bytes32 public constant PAUSER = keccak256("PAUSER");
49+
// @notice can configure the OApp + transfer limiter and unpause the bridge
50+
bytes32 public constant MANAGER = keccak256("MANAGER");
51+
52+
/**
53+
* @param _token The canonical LISTA ERC20 token on BNB Chain.
54+
* @param _lzEndpoint The LayerZero V2 endpoint on BNB Chain.
55+
* @dev Disables initializers on the implementation so it can only be used
56+
* behind a proxy.
57+
*/
58+
/// @custom:oz-upgrades-unsafe-allow constructor state-variable-immutable
59+
constructor(address _token, address _lzEndpoint) OFTAdapterUpgradeable(_token, _lzEndpoint) {
60+
_disableInitializers();
61+
}
62+
63+
/**
64+
* @notice Initializes the proxy.
65+
* @param _admin The DEFAULT_ADMIN_ROLE holder (upgrade + role admin). Also set
66+
* as the Ownable owner to satisfy the few non-virtual LayerZero
67+
* owner-gated hooks.
68+
* @param _manager The MANAGER holder and LayerZero endpoint delegate.
69+
* @param _pauser The PAUSER holder.
70+
* @param _transferLimitConfigs Initial transfer limit configurations.
71+
*/
72+
function initialize(
73+
address _admin,
74+
address _manager,
75+
address _pauser,
76+
TransferLimit[] memory _transferLimitConfigs
77+
) external initializer {
78+
require(_admin != address(0), "admin cannot be zero address");
79+
require(_manager != address(0), "manager cannot be zero address");
80+
require(_pauser != address(0), "pauser cannot be zero address");
81+
82+
// sets the LayerZero endpoint delegate to the manager
83+
__OFTAdapter_init(_manager);
84+
__Ownable_init();
85+
__AccessControl_init();
86+
__Pausable_init();
87+
__UUPSUpgradeable_init();
88+
89+
// owner is used only by the non-virtual LayerZero owner hooks (eg. setDelegate)
90+
_transferOwnership(_admin);
91+
92+
_grantRole(DEFAULT_ADMIN_ROLE, _admin);
93+
_grantRole(MANAGER, _manager);
94+
_grantRole(PAUSER, _pauser);
95+
96+
_setTransferLimitConfigs(_transferLimitConfigs);
97+
}
98+
99+
// --------------------------------------------------------------------------
100+
// Transfer Limiter
101+
// --------------------------------------------------------------------------
102+
103+
/**
104+
* @notice Sets the transfer limit configurations.
105+
* @param _transferLimitConfigs An array of TransferLimit structures.
106+
*/
107+
function setTransferLimitConfigs(
108+
TransferLimit[] calldata _transferLimitConfigs
109+
) external onlyRole(MANAGER) {
110+
_setTransferLimitConfigs(_transferLimitConfigs);
111+
}
112+
113+
// --------------------------------------------------------------------------
114+
// Pause control
115+
// --------------------------------------------------------------------------
116+
117+
/// @notice Pause the bridge. Callable by PAUSER.
118+
function pause() external onlyRole(PAUSER) {
119+
_pause();
120+
}
121+
122+
/// @notice Unpause the bridge. Callable by MANAGER.
123+
function unpause() external onlyRole(MANAGER) {
124+
_unpause();
125+
}
126+
127+
// --------------------------------------------------------------------------
128+
// OFT debit / credit hooks (lock / unlock with limiter + pause guard)
129+
// --------------------------------------------------------------------------
130+
131+
/**
132+
* @dev Locks tokens on the source (this) chain when sending cross-chain.
133+
* Enforces the transfer limiter and the pause guard before locking.
134+
*/
135+
function _debit(
136+
address _from,
137+
uint256 _amountLD,
138+
uint256 _minAmountLD,
139+
uint32 _dstEid
140+
) internal virtual override whenNotPaused returns (uint256 amountSentLD, uint256 amountReceivedLD) {
141+
// remove dust before checking, mirroring the value that is actually bridged
142+
uint256 _amount = _removeDust(_amountLD);
143+
_checkAndUpdateTransferLimit(_dstEid, _amount, _from);
144+
return super._debit(_from, _amountLD, _minAmountLD, _dstEid);
145+
}
146+
147+
/**
148+
* @dev Unlocks tokens to the recipient when receiving cross-chain.
149+
* Blocked while the contract is paused.
150+
*/
151+
function _credit(
152+
address _to,
153+
uint256 _amountLD,
154+
uint32 _srcEid
155+
) internal virtual override whenNotPaused returns (uint256 amountReceivedLD) {
156+
return super._credit(_to, _amountLD, _srcEid);
157+
}
158+
159+
// --------------------------------------------------------------------------
160+
// LayerZero OApp configuration
161+
// - setPeer (the cross-chain trust anchor) is restricted to DEFAULT_ADMIN_ROLE
162+
// - enforced options / msg inspector / preCrime are restricted to MANAGER
163+
// --------------------------------------------------------------------------
164+
165+
/// @notice Sets the trusted peer for a destination endpoint. Restricted to DEFAULT_ADMIN_ROLE
166+
/// (the cross-chain trust anchor; emergency severing uses pause()).
167+
function setPeer(uint32 _eid, bytes32 _peer) public override onlyRole(DEFAULT_ADMIN_ROLE) {
168+
_getOAppCoreStorage().peers[_eid] = _peer;
169+
emit PeerSet(_eid, _peer);
170+
}
171+
172+
/// @notice Sets enforced LayerZero options. Restricted to MANAGER.
173+
function setEnforcedOptions(
174+
EnforcedOptionParam[] calldata _enforcedOptions
175+
) public override onlyRole(MANAGER) {
176+
OAppOptionsType3Storage storage $ = _getOAppOptionsType3Storage();
177+
for (uint256 i = 0; i < _enforcedOptions.length; i++) {
178+
_assertOptionsType3(_enforcedOptions[i].options);
179+
$.enforcedOptions[_enforcedOptions[i].eid][_enforcedOptions[i].msgType] = _enforcedOptions[i].options;
180+
}
181+
emit EnforcedOptionSet(_enforcedOptions);
182+
}
183+
184+
/// @notice Sets the message inspector. Restricted to MANAGER.
185+
function setMsgInspector(address _msgInspector) public override onlyRole(MANAGER) {
186+
_getOFTCoreStorage().msgInspector = _msgInspector;
187+
emit MsgInspectorSet(_msgInspector);
188+
}
189+
190+
/// @notice Sets the preCrime contract. Restricted to MANAGER.
191+
function setPreCrime(address _preCrime) public override onlyRole(MANAGER) {
192+
_getOAppPreCrimeSimulatorStorage().preCrime = _preCrime;
193+
emit PreCrimeSet(_preCrime);
194+
}
195+
196+
// --------------------------------------------------------------------------
197+
// Upgrade authorization
198+
// --------------------------------------------------------------------------
199+
200+
/// @dev Only DEFAULT_ADMIN_ROLE may upgrade the implementation.
201+
function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {}
202+
203+
uint256[50] private __gap;
204+
}

0 commit comments

Comments
 (0)