| add love contract 9bf25a7 julienbrg 10h ago | 1 | // SPDX-License-Identifier: MIT |
| 2 | pragma solidity ^0.8.30; |
| 3 | |
| Merge 5-weth-bytecode-verification into main 2f6da5e rickub 6h ago | 4 | import {IWETH} from "./IWETH.sol"; |
| 5 | import {WETHRegistry} from "./WETHRegistry.sol"; |
| make love an erc20 with public mint 4c8048e julienbrg 10h ago | 6 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; |
| Merge 3-peg-love-to-weth into main ce13509 rickub 8h ago | 7 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; |
| 8 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; |
| add love contract 9bf25a7 julienbrg 10h ago | 9 | |
| make love an erc20 with public mint 4c8048e julienbrg 10h ago | 10 | /// @title Love |
| Merge 3-peg-love-to-weth into main ce13509 rickub 8h ago | 11 | /// @author Julien Béranger |
| 12 | /// @notice An ERC-20 pegged to wETH at a fixed rate of 100000 LOVE per wETH. |
| 13 | /// Every LOVE in circulation is backed by wETH held by this contract: |
| 14 | /// supply only moves through `deposit` and `withdraw`, and both are |
| 15 | /// permissionless. |
| 16 | /// @dev There is no mint entrypoint, no owner and no upgrade path, so the peg |
| 17 | /// cannot be diluted. `totalSupply() == WETH.balanceOf(address(this)) * RATE` |
| 18 | /// holds after every call; wETH transferred straight to this contract |
| 19 | /// raises the backing and is not redeemable. |
| make love an erc20 with public mint 4c8048e julienbrg 10h ago | 20 | contract Love is ERC20 { |
| Merge 3-peg-love-to-weth into main ce13509 rickub 8h ago | 21 | using SafeERC20 for IERC20; |
| add love contract 9bf25a7 julienbrg 10h ago | 22 | |
| Merge 3-peg-love-to-weth into main ce13509 rickub 8h ago | 23 | /// @notice LOVE minted per unit of wETH, and burned per unit released. |
| 24 | uint256 public constant RATE = 100_000; |
| 25 | |
| Merge 5-weth-bytecode-verification into main 2f6da5e rickub 6h ago | 26 | /// @notice The registry this token asks which wETH to peg to. |
| 27 | /// @dev `WETHRegistry` takes no constructor arguments, so CREATE2 puts it |
| 28 | /// at this address on every chain. Asking it at construction time, |
| 29 | /// rather than taking wETH as a constructor argument, is what keeps |
| 30 | /// this contract's creation code byte-identical everywhere — and so |
| 31 | /// what gives LOVE one address on every chain instead of one per wETH |
| 32 | /// deployment. |
| 33 | /// |
| 34 | /// Derived from the registry's creation code, which means it moves if |
| 35 | /// `WETHRegistry`, the solc version or the optimizer settings change. |
| 36 | /// `LoveRegistryAddressTest` recomputes it and fails if this constant |
| 37 | /// has drifted. |
| 38 | address public constant REGISTRY = 0x9Cf17430fEdEC487518416D1Cdc849b1eE9CDbA3; |
| 39 | |
| Merge 3-peg-love-to-weth into main ce13509 rickub 8h ago | 40 | /// @notice The wETH this token is pegged to and collateralised with. |
| Merge 5-weth-bytecode-verification into main 2f6da5e rickub 6h ago | 41 | /// @dev Read once from the registry and immutable thereafter. The registry |
| 42 | /// is itself write-once, so nothing can move the token under the peg. |
| Merge 3-peg-love-to-weth into main ce13509 rickub 8h ago | 43 | IERC20 public immutable WETH; |
| 44 | |
| 45 | /// @notice Thrown when a withdrawal amount is not a multiple of `RATE`. |
| 46 | /// @param loveAmount The rejected LOVE amount. |
| 47 | /// @param rate The rate it has to be a multiple of. |
| 48 | error AmountNotDivisibleByRate(uint256 loveAmount, uint256 rate); |
| 49 | |
| Merge 5-weth-bytecode-verification into main 2f6da5e rickub 6h ago | 50 | /// @notice Thrown when there is no registry on this chain yet. |
| 51 | /// @param registry The address the registry would be at. |
| 52 | error RegistryNotDeployed(address registry); |
| 53 | |
| 54 | /// @notice Thrown when the registry exists but holds no wETH yet. |
| 55 | /// @param registry The registry that was asked. |
| 56 | error WethNotRegistered(address registry); |
| 57 | |
| Merge 3-peg-love-to-weth into main ce13509 rickub 8h ago | 58 | /// @notice Emitted when wETH is locked and LOVE minted. |
| 59 | /// @param account The depositor, who pays the wETH and receives the LOVE. |
| 60 | /// @param wethAmount The wETH pulled in. |
| 61 | /// @param loveAmount The LOVE minted, `wethAmount * RATE`. |
| 62 | event Deposit(address indexed account, uint256 wethAmount, uint256 loveAmount); |
| 63 | |
| 64 | /// @notice Emitted when LOVE is burned and wETH released. |
| 65 | /// @param account The redeemer, who burns the LOVE and receives the wETH. |
| 66 | /// @param loveAmount The LOVE burned. |
| 67 | /// @param wethAmount The wETH released, `loveAmount / RATE`. |
| 68 | event Withdraw(address indexed account, uint256 loveAmount, uint256 wethAmount); |
| 69 | |
| Merge 5-weth-bytecode-verification into main 2f6da5e rickub 6h ago | 70 | /// @notice Peg to whatever wETH the registry has accepted on this chain. |
| 71 | /// @dev Takes no arguments on purpose. Anything passed in here would land |
| 72 | /// in the creation code and give the token a different address on |
| 73 | /// every chain whose wETH sits elsewhere, which is exactly what this |
| 74 | /// design exists to avoid. |
| 75 | /// |
| 76 | /// Reverts when the registry is missing or empty, so a chain with no |
| 77 | /// reviewed wETH gets no half-configured token: deploy the registry |
| 78 | /// and register wETH first, then deploy this. |
| 79 | constructor() ERC20("Love", "LOVE") { |
| 80 | if (REGISTRY.code.length == 0) revert RegistryNotDeployed(REGISTRY); |
| 81 | |
| 82 | IWETH registered = WETHRegistry(payable(REGISTRY)).weth(); |
| 83 | if (address(registered) == address(0)) revert WethNotRegistered(REGISTRY); |
| 84 | |
| 85 | WETH = IERC20(address(registered)); |
| Merge 3-peg-love-to-weth into main ce13509 rickub 8h ago | 86 | } |
| 87 | |
| 88 | /// @notice Lock `wethAmount` wETH and mint `wethAmount * RATE` LOVE to the caller. |
| 89 | /// @dev The caller must have approved this contract for `wethAmount` first. |
| 90 | /// Reverts on overflow of `wethAmount * RATE`, and on the wETH transfer |
| 91 | /// failing for want of balance or allowance. |
| 92 | /// @param wethAmount The wETH to lock, in wei. |
| 93 | function deposit(uint256 wethAmount) external { |
| 94 | uint256 loveAmount = wethAmount * RATE; |
| 95 | |
| 96 | WETH.safeTransferFrom(msg.sender, address(this), wethAmount); |
| 97 | _mint(msg.sender, loveAmount); |
| 98 | |
| 99 | emit Deposit(msg.sender, wethAmount, loveAmount); |
| 100 | } |
| 101 | |
| 102 | /// @notice Burn `loveAmount` LOVE and release `loveAmount / RATE` wETH to the caller. |
| 103 | /// @dev Reverts with `AmountNotDivisibleByRate` unless `loveAmount` is a |
| 104 | /// multiple of `RATE`, so the peg never rounds against the caller or |
| 105 | /// the remaining holders. LOVE is burned before the wETH leaves, and |
| 106 | /// the burn already caps the amount at the caller's balance. |
| 107 | /// @param loveAmount The LOVE to burn, a multiple of `RATE`. |
| 108 | function withdraw(uint256 loveAmount) external { |
| 109 | if (loveAmount % RATE != 0) revert AmountNotDivisibleByRate(loveAmount, RATE); |
| 110 | |
| 111 | uint256 wethAmount = loveAmount / RATE; |
| 112 | |
| 113 | _burn(msg.sender, loveAmount); |
| 114 | WETH.safeTransfer(msg.sender, wethAmount); |
| 115 | |
| 116 | emit Withdraw(msg.sender, loveAmount, wethAmount); |
| add love contract 9bf25a7 julienbrg 10h ago | 117 | } |
| 118 | } |