// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "./interfaces/IWLPProgramEvents.sol"; /** * @title Chain138LPLocker * @notice Escrows DODO PMM LP ERC20 on Chain 138; releases only to BRIDGE_RELEASE_ROLE (Option A). * @dev Per-lock `lockRef` is used as the cross-chain idempotency key for destination mint. * One deployment typically corresponds to one `lpToken` (one pool’s LP token). */ contract Chain138LPLocker is AccessControl, IWLPProgramEvents { using SafeERC20 for IERC20; bytes32 public constant BRIDGE_RELEASE_ROLE = keccak256("BRIDGE_RELEASE_ROLE"); IERC20 public immutable lpToken; struct Deposit { address depositor; uint256 amount; bool released; } uint256 public lockCounter; uint256 public totalEscrowed; mapping(bytes32 => Deposit) public deposits; constructor(address lpToken_, address admin) { require(lpToken_ != address(0) && admin != address(0), "Locker: zero"); lpToken = IERC20(lpToken_); _grantRole(DEFAULT_ADMIN_ROLE, admin); } /** * @notice Lock LP into escrow; `lockRef` must be relayed to the public chain for mint. */ function deposit(uint256 amount) external returns (bytes32 lockRef) { require(amount > 0, "Locker: zero amount"); lpToken.safeTransferFrom(msg.sender, address(this), amount); lockRef = keccak256(abi.encode(block.chainid, address(this), lockCounter++, msg.sender, amount)); deposits[lockRef] = Deposit({depositor: msg.sender, amount: amount, released: false}); totalEscrowed += amount; emit LPLocked(lockRef, msg.sender, amount, address(lpToken)); } /** * @notice Release one full lock to `to` after redemption message (or operational unwind). */ function releaseLock(bytes32 lockRef, address to) external onlyRole(BRIDGE_RELEASE_ROLE) { Deposit storage d = deposits[lockRef]; require(d.amount > 0 && !d.released, "Locker: bad lock"); d.released = true; uint256 amt = d.amount; totalEscrowed -= amt; lpToken.safeTransfer(to, amt); emit LPReleased(lockRef, to, amt, address(lpToken)); } /** * @notice Aggregate release for pro-rata / FIFO policies (requires off-chain ordering). */ function releaseAmount(address to, uint256 amount) external onlyRole(BRIDGE_RELEASE_ROLE) { require(amount > 0 && amount <= totalEscrowed, "Locker: amount"); totalEscrowed -= amount; lpToken.safeTransfer(to, amount); emit LPReleased(bytes32(0), to, amount, address(lpToken)); } }