1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
// 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);
}
}
|