// SPDX-License-Identifier: MIT pragma solidity ^0.8.30; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /// @title Love /// @author Julien Béranger /// @notice An ERC-20 pegged to wETH at a fixed rate of 100000 LOVE per wETH. /// Every LOVE in circulation is backed by wETH held by this contract: /// supply only moves through `deposit` and `withdraw`, and both are /// permissionless. /// @dev There is no mint entrypoint, no owner and no upgrade path, so the peg /// cannot be diluted. `totalSupply() == WETH.balanceOf(address(this)) * RATE` /// holds after every call; wETH transferred straight to this contract /// raises the backing and is not redeemable. contract Love is ERC20 { using SafeERC20 for IERC20; /// @notice LOVE minted per unit of wETH, and burned per unit released. uint256 public constant RATE = 100_000; /// @notice The wETH this token is pegged to and collateralised with. /// @dev Immutable, and part of the creation code — two chains only give this /// contract the same CREATE2 address if they share a wETH address. IERC20 public immutable WETH; /// @notice Thrown when a withdrawal amount is not a multiple of `RATE`. /// @param loveAmount The rejected LOVE amount. /// @param rate The rate it has to be a multiple of. error AmountNotDivisibleByRate(uint256 loveAmount, uint256 rate); /// @notice Emitted when wETH is locked and LOVE minted. /// @param account The depositor, who pays the wETH and receives the LOVE. /// @param wethAmount The wETH pulled in. /// @param loveAmount The LOVE minted, `wethAmount * RATE`. event Deposit(address indexed account, uint256 wethAmount, uint256 loveAmount); /// @notice Emitted when LOVE is burned and wETH released. /// @param account The redeemer, who burns the LOVE and receives the wETH. /// @param loveAmount The LOVE burned. /// @param wethAmount The wETH released, `loveAmount / RATE`. event Withdraw(address indexed account, uint256 loveAmount, uint256 wethAmount); /// @param weth_ The wETH to peg to. Set once, never changed. constructor(IERC20 weth_) ERC20("Love", "LOVE") { WETH = weth_; } /// @notice Lock `wethAmount` wETH and mint `wethAmount * RATE` LOVE to the caller. /// @dev The caller must have approved this contract for `wethAmount` first. /// Reverts on overflow of `wethAmount * RATE`, and on the wETH transfer /// failing for want of balance or allowance. /// @param wethAmount The wETH to lock, in wei. function deposit(uint256 wethAmount) external { uint256 loveAmount = wethAmount * RATE; WETH.safeTransferFrom(msg.sender, address(this), wethAmount); _mint(msg.sender, loveAmount); emit Deposit(msg.sender, wethAmount, loveAmount); } /// @notice Burn `loveAmount` LOVE and release `loveAmount / RATE` wETH to the caller. /// @dev Reverts with `AmountNotDivisibleByRate` unless `loveAmount` is a /// multiple of `RATE`, so the peg never rounds against the caller or /// the remaining holders. LOVE is burned before the wETH leaves, and /// the burn already caps the amount at the caller's balance. /// @param loveAmount The LOVE to burn, a multiple of `RATE`. function withdraw(uint256 loveAmount) external { if (loveAmount % RATE != 0) revert AmountNotDivisibleByRate(loveAmount, RATE); uint256 wethAmount = loveAmount / RATE; _burn(msg.sender, loveAmount); WETH.safeTransfer(msg.sender, wethAmount); emit Withdraw(msg.sender, loveAmount, wethAmount); } }